第一节:等待/通知机制
- 等待/通知机制出现的意义:减少CPU的资源浪费,而且还可以实现在多个线程间通信;
- 线程等待方法:Object类的wait()方法,将当前线程置入“预执行队列”,必须在synchronized方法或者代码块中,如果不加入同步块就会出现这个异常:java.lang.IllegalMonitorStateException;;执行后,当前线程释放锁,如果调用wait方法时没有持有适当的锁,抛出异常IllegalMonitorStateException,(RuntimeException)的一个子类,所以不需要try…catch语句进行捕获异常;
- 线程唤醒:Object类的notify方法,如果没有持有当前线程的锁也会抛出异常IllegalMonitorStateException,
执行该方法后,当前线程不会立即释放该对象锁,呈wait状态的线程也并不会马上获取该对象锁,要等待执行notify方法的线程将程序执行完,也就是退出同步块之后线程才会释放锁。 - 每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列;就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程;
- 小总结: * notify方法执行完同步代码块就会释放对象锁 *
执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放; *
执行了wait方法后,该线程会释放对象锁,并且进入线程等待池中,等待被唤醒; - 当线程呈wait状态时,调用线程对象的interrupt方法会出现InterruptedException异常
- notify()方法仅可以随机唤醒一个线程;而notifyAll()方法可以唤醒全部等待中的线程;
- 方法wait(long):等待long时间内是否有线程对锁唤醒,如果超过这个时间则自动唤醒;当然在这个时间内也可以由其他线程所唤醒;
- 两个线程,若是A线程先调用了notify方法,然后B线程再调用wait方法,那么B方法是不能被唤醒的,B方法会一直呈wait状态;
- 生产者消费者模型:练习;
- 通过管道进行线程间通信;管道流(pipeStream),用于在不同线程间之间传送数据。一个线程从输入管道中读取数据,通过使用管道,实现不同线程间的通信,从而无须借助类似临时文件之类的东西;
第二节:方法join的使用;
-
join的由来:等待调用join这个方法的线程执行完毕后再进行下面的代码;
-
join的作用:使所属线程对象x正常执行run方法里的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码,;使线程排队运行,有些类似同步的运行效果;
-
join与synchronized的区别:join在内部使用wait方法进行等待,而synchronized关键字使用的是“对象监视器”原理作为同步;
-
3、方法join遇到中断时的情况,启动的同时调用interrupt方法来中断线程,
会抛出异常java.lang.InterruptedException
举个例子:
/**
1. 观察方法join与异常的发生
2. 如果当前线程对象被中断,则当前线程出现异常
3. 调用interrupt方法之后
*/
class MyThread3A extends Thread{
@Override
public void run() {
for (int i = 0;i < 10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前数字"+(i+1));
}
}
}
//定义两个线程
class MyThread3B extends Thread{
@Override
public void run() {
try{
MyThread3A a = new MyThread3A();
a.start();
a.join();
System.out.println("线程B在最后打印了---");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
class MyThread3C extends Thread{
private MyThread3B myThread3B;
public MyThread3C(MyThread3B myThread3B) {
this.myThread3B = myThread3B;
}
@Override
public void run() {
myThread3B.interrupt();
}
}
public class Test3 {
public static void main(String[] args) {
try{
MyThread3B myThread3B = new MyThread3B();
myThread3B.start();
Thread.sleep(500);
MyThread3C myThread3C = new MyThread3C(myThread3B);
myThread3C.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
出现异常的原因是线程B已经停止,但是线程A并未出现异常,A在线程B里面调用了join方法,依旧是正常执行的状态直到执行完毕;
4. 区别一下join(long)跟sleep(long)的区别
区别一下join(long)跟sleep(long)的区别:
第三节:类ThreadLocal的使用:
- ThreadLocal实现了每一个线程都有自己的共享变量,绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有变量;ThreadLocal类解决了变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程中的值可以放入ThreadLocal类中进行保存;
- ThreadLocal类的对象初始化默认值为null;
- 解决第一次get返回null的问题: 在ThreadLocal类中有一个子类专用的方法:这个方法就是初始化值得,子类覆写这个方法,return一个初始值;
protected T initialValue() {
return null;
}
- 子线程和父线程同样拥有各自得私有值;
- 类InheritableThreadLocal:使用该类可以让子线程从父线程中取得值;
- 值继承再修改:类InheritableThreadLocal提供的修改父线程值得方法;
protected T childValue(T parentValue) {
return parentValue;
}
- 此处注意一下,在使用InheritableThreadLocal类需要注意一点:如果子线程在取得值得同时,主线程将InheritableThreadLocal中得值进行修改,那么子线程取得的值还是旧值;