第 15章
线程
– 线程入门
– 同步化议题
– concurrent套件新增类别继承 Thread
继承 java.lang.Thread类别,并重新定义
run()方法
实例化您自定义的 Thread类别
使用 start()方法启动线程继承 Thread
public class EraserThreadextends Thread {
private boolean active;
private String mask;

//重新定义 run()方法
public void run () {
while(isActive()) {
System.out.print(mask);
try {
//暂停目前的线程 50毫秒
Thread.currentThread().sleep(50);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
继承 Thread
//启动 Eraser线程
EraserThread eraserThread = new EraserThread('#');
eraserThread.start();
String password = scanner.next();
eraserThread.setActive(false);
在 Java SE 6中可以使用 System.console()
来取得 java.io.Console物件
使用 Console物件的 readPassword()方法,
就可以避免输入的密码被窥视的问题实作 Runnable界面
如果您的类别已经要继承某个类别,那么您就不能继承 Thread类别
继承了 Thread类别,您就不能再继承其它类别
实作 java.lang.Runnable接口来定义具线程功能的类别
Runnable接口中定义一个 run()方法要实作
在实例化一个 Thread对象时,可以传入一个实作 Runnable接口的对象作为自变量实作 Runnable界面
public class EraserimplementsRunnable { //实作 Runnable
private boolean active;
private String mask;

//重新定义 run()方法
public void run () {
while(isActive()) {
System.out.print(mask);
try {
//暂停目前的线程 50毫秒
Thread.currentThread().sleep(50);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
实作 Runnable界面
//Eraser实作 Runnable界面
Eraser eraser = new Eraser('#');
//启动 Eraser线程
Thread eraserThread = new Thread(eraser);
eraserThread.start();
String password = scanner.next();
eraser.setActive(false);
Daemon线程
一个 Daemon线程是一个在背景执行服务的线程
如果所有的非 Daemon的线程都结束了,则
Daemon线程自动就会终止
从 Main方法开始的是一个非 Daemon线程
如果希望某个线程在产生它的线程结束后跟着终止,要将它设为 Daemon线程
Daemon线程
Thread thread = new Thread(
//这是匿名类别的写法
new Runnable() {
public void run() {
while(true) {
System.out.print("T");
}
}
});
//设定为 Daemon线程
thread.setDaemon(true);
thread.start();
使用 setDaemon()方法来设定一个线程是否为 Daemon线程
预设所有从 Daemon线程产生的线程也是
Daemon线程线程生命周期
执行 start()之后,线程进入 Runnable状态,
此时线程尚未真正开始执行
必须等待排班器( Scheduler)的排班线程生命周期
线程有其优先权,由 1
( Thread.MIN_PRIORITY)到 10
( Thread.MAX_PRIORITY)
优先权越高,排班器越优先排入执行,如果优先权相同,则输流执行( Round-robin
方式)
线程生命周期
如果您想要让目前线程礼让一下其它线程,
让它们有机会取得执行权,您可以呼叫绪行绪的 yield()方法
//,....
Thread thread = new Thread(new Runnable() {
public void run() {
//,...
while(true) {
//,...
yield(); //暂时让出执行权
}
}
});
thread.start(); //,...
线程生命周期
有几种状况会让线程进入 Blocked状态
– 等待输入输出完成
– 呼叫 sleep()方法
– 尝试取得对象锁定
– 呼叫 wait()方法线程生命周期
进入 Blocked状态,以下的几个对应情况让线程回到 Runnable状态
– 输入输出完成
– 呼叫 interrupt()
– 取得对象锁定
– 呼叫 notify()或 notifyAll()
线程生命周期
Thread thread = new Thread(new Runnable() {
public void run() {
try {
//暂停 99999毫秒
Thread.sleep(99999);
}
catch(InterruptedException e) {
System.out.println("I'm interrupted!!");
}
}
});
thread.start();
thread.interrupt(); // interrupt it right now
线程的加入( join)
当线程使用 join()加入至另一个线程时,另一个线程会等待这个被加入的线程工作完毕,然后再继续它的动作
join()的意思表示将线程加入成为另一个线程的流程之一线程的加入( join)
Thread threadB = new Thread(new Runnable() {
public void run() {
try {

}
catch(InterruptedException e) {
e.printStackTrace();
}
}
});
threadB.start();
try {
// Thread B加入 Thread A
threadB.join();
}
catch(InterruptedException e) {
e.printStackTrace();
}
线程的停止
不建议使用 stop()来停止一个线程的运行
public class SomeThread implements Runnable {
private boolean isContinue = true;
public void terminate() {
isContinue = false;
}
public void run() {
while(isContinue) {
//,.,some statements
}
}
}
线程的停止
不建议使用 stop()来停止一个线程的运行
Thread thread = new Thread(new SomeThread());
thread.start();
thread.interrupt();
ThreadGroup
每一个线程产生时,都会被归入某个线程群组
如果没有指定,则归入产生该子线程的线程群组中
可以自行指定线程群组,线程一但归入某个群组,就无法更换群组
ThreadGroup
java.lang.ThreadGroup类别正如其名,可以统一管理整个群组中的线程
ThreadGroup中的某些方法,可以对所有的线程产生作用
– interrupt()方法可以 interrupt群组中所有的线程
– setMaxPriority()方法可以设定群组中线程所能拥有的最大优先权
ThreadGroup threadGroup1 = new ThreadGroup("group1");
ThreadGroup threadGroup2 = new ThreadGroup("group2");
Thread thread1 = new Thread(threadGroup1,"group1's member");
Thread thread2 = new Thread(threadGroup2,"group2's member");
ThreadGroup
想要一次取得群组中所有的线程来进行某种操作,可以使用 enumerate()方法
Thread[] threads = new Thread[threadGroup1.activeCount()];
threadGroup1.enumerate(threads);
ThreadGroup
uncaughtException()方法是当群组中某个线程发生非受检例外
( Uncheckedexception)时,由执行环境呼叫进行处理
ThreadGroup threadGroup1 =
//这是匿名类别写法
new ThreadGroup("group1") {
//继承 ThreadGroup并重新定义以下方法
//在线程成员丢出 unchecked exception
//会执行此方法
public void uncaughtException(Thread t,Throwable e) {
System.out.println(t.getName() + ","
+ e.getMessage());
}
};
UncaughtExceptionHandler
可以让您的例外处理类别实作
Thread.UncaughtExceptionHandler界面,
并实现其 uncaughtException()方法
public class ThreadExceptionHandler
implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t,Throwable e) {
System.out.println(t.getName() + ","
+ e.getMessage());
}
}
thread1.setUncaughtExceptionHandler(handler);
同步化
如果一个对象所持有的数据可以被多线程同时共享存取时,您必须考虑到「数据同步」的问题
数据同步指的是两份数据整体性、一致性同步化同步化
数据的不同步而可能引发的错误通常不易察觉
可能是在您程序执行了几千几万次之后,
才会发生错误
这通常会发生在您的产品已经上线之后,
甚至是程序已经执行了几年之后同步化
public void setNameAndID(String name,String id) {
this.name = name;
this.id = id;
if(!checkNameAndIDEqual()) {
System.out.println(count +
") illegal name or ID.....");
}
count++;
}
同步化
使用 "synchronized"关键词
publicsynchronized void setNameAndID(String name,String id) {
this.name = name;
this.id = id;
if(!checkNameAndIDEqual()) {
System.out.println(count +
") illegal name or ID.....");
}
count++;
}
同步化
物件的锁定( lock)观念同步化
使用 "synchronized"关键词
public void setNameAndID(String name,String id) {
synchronized(this) {
this.name = name;
this.id = id;
if(!checkNameAndIDEqual()) {
System.out.println(count +
") illegal name or ID.....");
}
count++;
}
}
//arraylist参考至一个 ArrayList的一个实例
synchronized(arraylist) {
arraylist.add(new SomeClass()
);
同步化
同步化确保数据的同步,但所牺性的就是在于一个线程取得对象锁定而占据同步化区块,而其它线程等待它释放锁定时的延迟
wait(),notify()
wait(),notify()与 notifyAll()是由 Object类别所提供的方法
宣告為 "final"
在同步化的方法或区块中呼叫 wait()方法
当物件的 wait()方法被调用,目前的线程会被放入对象的等待池中,线程归还对象的锁定
其它的线程可竞争对象的锁定
wait(),notify()
wait(),notify()
当物件的 notify()被调用,它会从目前对象的等待池中通知「一个」线程加入回到锁定池的 Blocked状态
被通知的线程是随机的,被通知的线程会与其它线程共同竞争对象的锁定
如果您呼叫 notifyAll(),则「所有」在等待池中的线程都会被通知回到锁定池的
Blocked状态
wait(),notify()
当线程呼叫到对象的 wait()方法时,表示它要先让出对象的锁定并等待通知,或是等待一段指定的时间
被通知或时间到时再与其它线程竞争对象的锁定
如果取得锁定了,就从等待点开始执行
wait(),notify()
public synchronized void setProduct(int product) {
if(this.product != -1) {
try {
//目前店员没有空间收产品,请稍候!
wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
this.product = product;
System.out.printf("生产者设定 (%d)%n",this.product);
//通知等待区中的一个消费者可以继续工作了
notify();
}
wait(),notify()
public synchronized int getProduct() {
if(this.product == -1) {
try {
//缺货了,请稍候!
wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
int p = this.product;
System.out.printf("消费者取走 (%d)%n",this.product);
this.product = -1; //取走产品,-1表示目前店员手上无产品
//通知等待区中的一个生产者可以继续工作了
notify();
return p;
}
容器类的线程安全
可以使用 java.util.Collections的
synchronizedXXX()等方法来传回一个同步化的容器对象
使用 Iterator遍访对象时,您仍必须实作同步化
List list = Collections.synchronizedList(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
Iterator i = list.iterator();
while (i.hasNext()) {
foo(i.next());
}
}
ThreadLocal类别
尝试从另一个角度来思考多线程共享资源的问题
共享资源这么困难,那么就干脆不要共享
使用 java.lang.ThreadLocal,为每个线程创造一个资源的复本
ThreadLocal类别
public T get() {
//取得目前执行 get()方法的线程
Thread current = Thread.currentThread();
//根据线程取得线程自有的资源
T t = storage.get(current);
//如果还没有线程专用的资源空间
//则建立一个新的空间
if(t == null &&
!storage.containsKey(current)) {
t = initialValue();
storage.put(current,t);
}
return t;
}
public void set(T t) {
storage.put(Thread.currentThread(),t);
}
ThreadLocal类别
public static SomeResource getResource() {
//根据目前线程取得专属资源
SomeResource resource = threadLocal.get();
//如果没有取得目前专属资源
if(resource == null) {
//建立一个新的资源并存入 ThreadLocal中
resource = new SomeResource();
threadLocal.set(resource);
}
return resource;
}
BlockingQueue
如果 BlockingQueue的内容为空,而有个线程试图从 Queue中取出元素,则该线程会被 Block,直到 Queue有元素时才解除 Block
如果 BlockingQueue满了,而有个线程试图再把资料填入 Queue中,则该线程会被
Block,直到 Queue中有元素被取走后解除
Block
BlockingQueue
方 法 说 明
add() 加入元素,如果队列是满的,则丢出 IllegalStateException
remove() 传回并从队列移除元素,如果队列是空的,则丢出
NoSuchElementException
element() 传回元素,如果队列是空的,则丢出
NoSuchElementException
offer() 加入元素并传回 true,如果队列是满的,则传回 false
poll() 传回并从队列移除元素,如果队列是空的,则传回 null
peek() 传回元素,如果队列是空的,则传回 null
put() 加入元素,如果队列是满,就 block
take() 传回并移除元素,如果队列是空的,就 block
BlockingQueue
ArrayBlockingQueue指定容量大小来建构
LinkedBlockingQueue默认没有容量上限,
但也可以指定容量上限
PriorityBlockingQueue严格来说不是 Queue,
因为它是根据优先权( Priority)来移除元素
Callable与 Future
可以协助您完成 Future模式
– http://caterpillar.onlyfun.net/Gossip/DesignPat
tern/FuturePattern.htm
Executors
可以使用 Executors来建立线程池方 法 说 明
newCachedThreadPool() 建立可以快取的线程,每个线程预设可
idle的时间为 60秒
newFixedThreadPool() 包括固定数量的线程
newSingleThreadExecutor() 只有一个线程,循序的执行指定给它的每个任务
newScheduledThreadPool() 可排程的线程
newSingleThreadScheduledExecutor() 单一可排程的线程