前言
当线程在系统内运行时,程序无法精准控制线程轮换执行,Java提供了一些机制来保证线程协调运行。
synchronized
线程通信相关方法
借助Object
类提供的wait()
,notify()
,notifyAll()
三个方法(不属于Thread类),但这三个必须由同步监视器调用,这可以分成以下情况:
- 对于同步方法,该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法。
- 对于同步代码块,同步监视器是
synchronized
后括号里的对象,所以要使用该对象调用。
关于这三个方法的解释:
wait()
:让当前线程等待,直到其他线程调用该同步监视器的notify()
或notifyAll()
方法来唤醒线程。无参则一直等待,带参则等待long millis
毫秒时间自动唤醒。notify()
:唤醒在此同步监视器上等待的单个线程,如果有多个线程在等待同步监视器,则随机唤醒其中一个线程。只有当前线程主动放弃锁,被唤醒线程才获得执行。notifyAll()
:唤醒在此同步监视器上等待的所有线程。
使用Condition
的线程通信
如果不使用synchronized
关键字保证线程同步,而使用Lock
对象保证线程同步,则系统中不存在隐式的同步监视器,也就不能使用wait()
,notify()
,notifyAll()
方法进行线程通信。因此,当使用Lock
对象来保证同步时,Java提供了Condition
类来保证协调,使用Condition
可以让那些已经得到Lock
对象却无法执行的线程释放Lock
对象,同时也能唤醒其他处于等待的线程。
Condition
将同步监视器的方法(wait(),notify(),notifyAll())分成不同的对象,便于与Lock
对象结合,Lock
代替了同步方法和代码块,Condition
代替了同步监视器功能。Condition
实例被绑定在Lock
对象上,想要获得Lock
对象的Condition
实例,就要调用Lock
对象的newCondition()
方法。
Condition
类提供如下方法:
await()
:类似隐式同步监视器的wait()
方法,导致当前线程等待,直到其他线程调用该Condition
的signal()
或signalAll()
方法来唤醒该线程。衍生的方法有很多,awaitUninterruptibly()
,awaitUntil(Date deadline)
。signal()
:唤醒在此Lock
对象上等待的单个线程,若有多个线程等待,则随机唤醒其中一个。signalAll()
:唤醒所有在此Lock
对象等待的所有线程,只有当前线程放弃对该Lock
对象的锁定后才可以执行被唤醒的线程。
使用阻塞队列(BlockingQueue
)的线程通信
Java5提供了一个BlockingQueue
接口,是Queue
的子接口,但它的主要途径并不是容器
,而是作为线程同步的工具。BlockingQueue
具有一个特征:当生产者线程试图向BlockingQueue
中放入元素时,如果该队列已满,则该线程被阻塞,当消费者线程试图从BlockingQueue
中取出元素时,如果该队列为空,则该线程被阻塞。
程序的两个线程交替向BlockingQueue
中放入,取出元素,就能控制线程通信。BlockingQueue
提供了如下两个方法:
put(T t)
:把T元素放入BlockingQueue
中,如果该队列已满,则阻塞该线程。take()
:从BlockingQueue
的头部取出元素,如果该队列的元素已空,则阻塞该线程。
BlockingQueue
继承了Queue
接口,当然也可以使用Queue
接口中的方法。
- 在队尾插入元素。包括
add(T t)
,offer(T t)
,put(T t)
方法,当队列已满,这三个方法分别会抛出异常,返回false,阻塞队列。 - 在队首删除并返回元素。包括
remove()
、poll()
、take()
方法,当队列为空时,这三个方法分别会抛出异常,返回false,阻塞队列。 - 在队首仅仅取出元素。包括
element()
、peek()
方法,当队列为空,方法分别抛出异常和返回false。
可用如下表格表示:
抛出异常 | 返回false | 阻塞线程 | 指定超时时长 | |
---|---|---|---|---|
队列已满时,队尾插入元素 | add() | offer() | put() | offer(e,time,unit) |
队列已空时,队首删除元素 | remove() | poll() | take() | poll(time,unit) |
队列已空时,队首取出元素 | element() | peek() |
BlockingQueue
包含以下几个实现类:
ArrayBlockingQueue
:基于数组实现的BlockingQueue
队列。LinkedBlockingQueue
:基于链表实现的BlockingQueue
队列。SynchronousQueue
:同步队列,对该队列的存取必须交替进行。PriorityBlockingQueue
:不是标准的阻塞队列,与PriorityQueue
类似,该队列调用remove()
,poll()
,take()
方法取出元素时,并不是取出队列中存在时间最长的元素,而是最小的元素。判断元素大小可根据元素(实现Comparable
接口)的本身大小进行自然排序,也可使用Comparator
自定义排序。DelayQueue
:底层基于PriorityBlockingQueue
实现,要求元素都实现Delay
接口,接口里只有一个long getDelay()
方法,DelayQueue
根据元素的getDelay()
方法的返回值进行排序。
1 | public class BlockingQueueTest{ |
线程组和未处理异常
Java使用ThreadGroup
来表示线程组,它可以对一批线程进行管理,允许程序直接对线程进行控制。如果没有显式指定创建的线程属于哪个线程组,则属于默认线程组。默认情况下,子线程和创建它的线程属于同一线程组。一旦线程加入了指定的线程组,则该线程一直属于该线程组,直至死亡不能改变。
Thread
类提供了如下几个构造器来设置创建的线程属于哪个线程组:
Thread(ThreadGroup group,Runnable target)
:以target的run方法作为线程执行体创建新的线程,属于group
线程组Thread(ThreadGroup group,Runnable target,String name)
:同样的,只不过指定了创建的线程名字。Thread(ThreadGroup group,String name)
:创建新线程,指定线程名。
虽然不能改变所在指定的线程组,但可以通过getTreadGroup()
方法返回所属线程组,返回值是ThreadGroup
对象。而ThreadGroup
类提供了两个构造器创建相应实例:
ThreadGroup(String name)
:以指定的线程组名字来创建新的线程组。ThreadGroup(ThreadGroup parent,String name)
:以指定的的名字和父线程组创建新的线程组。
通过以上构造器可以发现,线程组必然有一个名字,这个名字可以通过ThreadGroup
的getName()
方法获取,但是不允许改变线程组的名字,也就没有set方法。除了构造器,ThreadGroup
还提供了几个方法来操作线程组里的所有线程:
activeCount()
:返回线程组中活动线程的数目。interrupt()
:中断此线程组的所有线程。isDaemon()
:判断该线程组是否是后台线程组。setDaemon()
:把线程组设置为后台线程组,若后台线程组的最后一个线程死亡,则线程组自动毁灭。setMaxPriority(int p)
:设置线程组的最高优先级。
此外,线程组对于出现的异常也提供了一个方法void uncaughtException(Thread t,Throwable e)
,该方法可以处理该线程组内的任意线程所抛出的未处理异常。如果线程抛出一个异常,JVM会在线程结束前自动查找对应的Thread.UncaughtExceptionHandler
对象,如果找到该异常处理器对象,则会调用该对象的uncaughtException(Thread t,Throwable e)
方法处理异常。
Thread.UncaughtExceptionHandler
是Thread
类的一个静态内部接口,里面只有一个方法uncaughtException(Thread t,Throwable e)
,t
代表异常的线程,e
代表抛出的异常。Thread
类提供了如下方法来设置异常处理器:
static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler h)
:为该线程类的所有实例设置默认异常处理器。setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler h)
:为线程实例设置异常处理器。
ThreadGroup
类实现了Thread.UncaughtExceptionHandler
接口,所以每个线程的线程组
都将作为默认的异常处理器
。当一个线程抛出异常时,JVM会先查找线程实例指定的异常处理器,否则会调用所属线程组对象的uncaughtException()方法
(默认的异常处理器
)处理异常。
线程组默认异常处理器处理过程如下:
- 如果该线程组有父线程组,则调用父线程组的
uncaughtException()
方法处理。 - 如果该线程所属的默认线程组有默认异常处理器,那么就调用该异常处理器处理。
- 如果该
异常
是ThreadDeath
的对象,则不做任何处理,否则将异常跟踪栈的信息打印到System.err
输出流,并结束该线程。
1 | Class MyHandler implements Thread.UncaughtExceptionHandler{//自定义处理器 |
上诉代码虽然捕获了异常,但是出现不会正常结束。因为与try...catch
异常捕获不同,异常处理器对异常进行处理后会将异常抛给上一级调用者,而catch
捕获异常不会。