关键字:等待唤醒机制、Lock、Condition、停止线程、守护线程、join方法、优先级、yeild
线程间的通信
notify唤醒的是线程池中的线程,通常唤醒第一个等待的线程。notifyAll();全部唤醒。
wait、notify、notifyaAll
都使用在同步中。因为要对持有锁的线程操作,所以要使用在同步中,因为同步才有锁。也就是说,这些方法必须标示所属线程使用的锁,例如r.wait(),意为持有r这个锁的线程等待。因为同步会出现嵌套,会有2个锁,不标示的话,不知道让哪个线程等待。同理适用于notify。
这些方法定义在Object类中的原因。因为这些方法在操作同步中的线程时,都必须要标识他们所操作线程的唯一锁。只有同一个锁上被等待线程,可以被同一个锁上另外的线程notify唤醒。也就是说,等待和唤醒必须是同一个锁的2个线程之间的行为。
因为锁可以是任意对象,所以这些方法可以被任意对象调用,所以这些方法都定义在了Object类中。
多个线程操作同一个数据,但是动作不同,可以使用等待唤醒机制,交替执行。例如,
public synchronized void set(String name)//保证线程会执行完代码后才放弃资格。
关键点是循环判断while(flag)和唤醒对象线程this.notifyAll();,主要是考虑线程较多,需要挨个交替执行。
JDK1.5提供了多线程升级解决方案,Lock接口和Condition接口。将同步synchronized替换为显式Lock操作(显式的锁机制);将Object中的wait、notify、notifyAll替换为Condition对象(显式的等待、唤醒操作机制),该对象可以对Lock锁进行获取。synchronized中对锁的操作,例如,获取锁、获取锁,都是隐式操作。lock没有新的功能,只是将对锁的操作公开化、显式,获取锁lock()和释放所unlock()。将等待、唤醒操作封装进Condition对象,使得一个锁可以对应多个对象。例如。
private Lock lock = new ReentrantLock();//新建锁的对象
private Condition condition_pro = lock.newCondition();//新建对象,封装锁的使用。
private Condition condition_con = lock.newCondition();
public void set(String name) throws InterruptedException
{
}
public void out() throws InterruptedException
{
}
优势在于,一个锁里可以绑定多个Condition对象。以前同步里面多个线程拥有一个锁,绑定一个对象,等待唤醒操作也只能由这个对象决定;现在锁可以绑定多个对象,进而有多个等待唤醒操作,操作更加灵活,只需要区分Condition对象即可。最大的优点是可以做到,本方只唤醒对方的操作。
可以在嵌套中使用,不会出现死锁的情况。
锁需要绑定一个对象,这个对象可以是任意对象,可以和锁没有关系。现在,一个锁可以和多个对象绑定,也就意味着,一些线程和一个Condition产生联系,线程的等待唤醒由这个对象决定,例如,condition_pro.await();,condition_pro对象使得线程等待,那么无论在任何地方,只要condition_pro.signal()就能唤醒线程,一般都是唤醒对方的线程。
停止线程
原理:run方法结束。注意:是停止,不是暂停(冻结)。
1. 定义循环结束标记
多线程运行,代码一般都是循环结构,只要控制循环,就可以让run方法结束,也就是线程结束。一次性执行的话,单线程即可。
2. 使用interrupt(中断)方法
结束线程的冻结状态,使线程回到运行状态中。
还有stop方法,但1.5版本后不再使用,会报错。但是老版本有此方法。在线程还处于冻结状态,就将其终止,有安全隐患。
特殊情况
当现场处于冻结状态,就不会读取到标记,现场就不会结束,只能是暂停,只是丧失了运行资格而已。
中断状态不是停止状态,是冻结状态或暂停状态。interrupt方法,将处于冻结状态的线程强制的恢复到运行状态中来。只有恢复到运行状态,才能读取标记,使得run方法结束。
当没有指定方式让冻结的线程恢复到冻结状态时,需要对冻结状态进行清除,强制让线程恢复到运行状态中来。这样就可以通过操作标记让线程结束,线程运行完毕后自动结束。Thread类中提供了interrupt方法,满足该需求。
守护线程
守护线程,也叫用户线程,本质是后台线程。大部分线程都是前台线程。标为后台线程后,开启后和前台线程一样,抢劫CPU的运行权;当所有的前台线程都结束后,后台线程会自动结束,甚至无限循环也会结束。
特点:
1. 必须在守护线程前调用。
2. 所有前台线程都结束,只剩下守护线程时,守护线程结束,虚拟机退出,程序结束。
join方法
join方法,抢先获得CPU的执行权,先于主线程执行,例如,t1.join();。此时,主线程处于冻结状态,只有t1可以运行;只有t1运行结束,主线程才可以运行,等待让主线程让出执行权的线程死亡。一般用于临时加入线程。
join方法的位置会有影响,如果放在多个线程开启的下面,例如,t1.start();t2.start();t1.join();,t1和t2会交替执行,因为t1抢的是主线程的运行权,和t2无关。主线程依然在t1结束后运行,和t2的执行无关。
当A现场执行到了B线程的join方法时,A线程就会等待,等B线程都执行完,A线程才会执行。可以用来临时加入线程执行。可以嵌套使用。
如果t1等待,会造成主线程无法运行,所以使用interrupt方法来结束冻结状态,继续运行。因为是强制恢复运行,所以可能会出现异常,所以join方法会抛出InterruptedException异常。
开启线程的线程组,称为主线程组,里面的成员构成一个线程组。也可以通过ThreadGroup创建新的对象,封装进其他的组。很少用。
优先级:代表抢资源的频率,设置越高,执行越频繁,优先执行。所有线程的优先级,包括主线程,优先级默认设置为5,共有10级,数字越大,越优先执行。如果数字相差不大,优先程度几乎没有差别,只有1、5、10之间最明显,分别为min、norm、max。虽然可以设为最高级,也只是相对高而已,不可能只执行一个线程。
yield
多个线程运行时,临时强制释放线程的执行权,使得其他线程可以执行。可以减缓线程的运行,使得线程都有机会运行。
开发时如何编写线程
当某些代码需要同时运行时,用单独的线程进行封装。可以封装成类,或者对象。例如。
new Thread()//通过Thread类建立匿名类
{
}.start();
Runnable r = new Runnable()//通过Runnable接口建立匿名类对象
{
};
new Thread(r).start();