1.线程状态回顾
- NEW:Thread对象有了,内核中还没有该线程
- TERMINATED:内核中的线程没了,Thread对象还在
- RUNNABLE:就绪状态(和运行)
- TIMED_WAITING:因为sleep进入阻塞状态
- BLOCKED:因为等待锁引入了阻塞状态
- WAITING:因为wait进入了阻塞状态
2.Java 标准库中的线程安全类
Java 标准库中很多都是线程不安全的.这些类可能会涉及到多线程修改共享数据,又没有任何加锁措施.
对于这些类,在多线程中使用的时候要注意安全问题,合理运用synchronized,和良好的代码逻辑来避免线程安全问题.
- ArrayList
- LinkedList
- HashMap
- TreeMap
- HashSet
- TreeSet
- StringBuilder
还有一些是线程安全的. 使用了一些锁机制来控制.
虽然这些类是线程安全的,但是有的是不推荐使用的,因为这些类的底层虽然加了synchronized来保证安全,但是他们属于无脑加synchronized锁(每个方法都给加上锁).所以会大大降低效率,在工作中建议使用不安全的类,自己按照逻辑对不安全的代码加锁就好.
- Vector (不推荐使用)
- HashTable (不推荐使用)
- ConcurrentHashMap
- StringBuffer
3.wait 和 notify
由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.这时候就要用到我们的 wait 和 notify 方法了
首先需要知道的是: wait, notify, notifyAll 都是 Object 类的方法.所以每个对象都有该方法.
- wait() / wait(long timeout): 让当前线程进入等待状态.
- notify() / notifyAll(): 唤醒在当前对象上等待的线程.
wait
先观察代码
publicclassTest{publicstaticvoidmain(String[] args)throwsInterruptedException{Object object =newObject();synchronized(object){System.out.println("等待中");
object.wait();System.out.println("等待结束");}}}
- wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
- wait 会使当前执行代码的线程进行等待. (把线程放到等待队列中)
- wait 还会释放当前线程占用的锁(synchronized的锁)
- 满足一定条件时被唤醒后, 重新尝试获取这个锁(synchronized).
notify
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁.
- 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程.(并没有 “先来后到”)
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁.
观察 wait 和 notify 的配合使用
publicclassTest{privatestaticObject locker;publicstaticvoidmain(String[] args)throwsInterruptedException{Thread t1 =newThread(()->{synchronized(locker){System.out.println("wait:前");try{
locker.wait();//调用等待方法,等待notify方法唤醒.}catch(InterruptedException e){
e.printStackTrace();}System.out.println("wait:后");}});
t1.start();Thread.sleep(3000);Thread t2 =newThread(()->{synchronized(locker){System.out.println("notify:前");
locker.notify();System.out.println("notify:后");}});
t2.start();}}
notify 和 notifyAll 的区别
当有多个线程对同一个对象wait的时候
- 在代码中调用notify后,就是随机唤醒一个线程.等待notify的synchronized代码块执行完之后释放锁.让唤醒的线程获取锁.
- 如果调用的是notifyAll,就是将等待在该锁对象的锁全部唤醒.等待notifyAll的synchronized代码块执行完之后释放锁,让他们重新去竞争锁资源.
可以看出notify和notifyAll的最终结果都是使一个线程获取到锁资源.所以一般都是使用notify方法.
死锁
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进.
例如如下代码.
classCounter{//死锁publicint count =0;synchronizedvoidincrease(){//死锁synchronized(this){
count++;}}}
如果不是在Java环境中,调用了上述的increase方法后,就会发生死锁.因为synchronized首先在increase方法上加了锁.锁对象也是this.在进入到里面synchronized代码块的时候,就会阻塞等待(this)锁的释放.这种情况下就会发生死锁.
Java中这里不会发生死锁的原因是Java中的特殊锁机制–>可重入锁(这就属于一种外力).
类似于这种死锁,还有很多情况.最经典的问题就是哲学家进餐问题.
死锁的原因和解决方法
死锁的四个必要条件
- 互斥使用 一个锁被线程占用之后,其他线程不可占用
- 不可抢占 一个锁被占用之后,其他线程不可以抢走这个锁
- 请求和保持 当一个线程占据了多把锁之后,除非显示的释放锁,否则这些琐事中都是被该线程持有的
- 环路等待 等待的关系构成了环路, 比如:a等b,b等c,c又等a
而前三种都是锁本身的特点.所以开发中要避免死锁就要从第四点入手.
- 加锁的时候对获取的资源进行排序,使得获取资源有固定的顺序,所有线程都遵守同样的规则顺序,就不会出现环路等待.
版权归原作者 魚小飛 所有, 如有侵权,请联系我们删除。