目录
一、什么是线程安全问题
在操作系统中,线程的调度是随机的(抢占式执行),由于这种随机性就会导致线程在执行的过程中由于执行顺序问题引发bug,这个bug就是线程的安全问题。所以也说:线程安全问题本质上就是由于线程的执行顺序不确定产生的。
二、产生线程安全问题的原因
产生线程安全的原因有以下几个方面:
- 线程是抢占式执行的
- 多个线程同时修改同一个变量
- 修改操作不是原子的
- 内存可见性问题
- 指令重排序
三、解决线程安全问题的方法
3.1 join()等待
join()等待方法可以在很大程度上解决线程的安全问题,因为join()等待方法是将多线程串行化执行了,必须等待某一线程执行完才能将另一个线程执行完。由于join()方法是将整体的线程串行化了,可能会降低执行的效率。
3.2 synchronized加锁
采取synchronized加锁可以保证线程的执行顺序,但是这个前提是多个线程要使用同一个锁对象,如果不是同一个锁对象加锁就是没有意义的。当使用同一对象加锁之后,如果一个线程正在执行任务,另一个线程想要执行就必须阻塞等待,只有等前一个线程执行完释放锁,另一个线程才能去执行。
由于synchronized加锁并不是将整个多线程加锁,它可以只对小部分加锁,所以不会很大程度的影响执行效率。
3.3 wait()和notify()
多线程的执行是随机的,但是某些特定的情况下也需要按照一定的顺序去执行代码,这里引入wait()和notify()来调整执行的顺序。
wait()是让程序阻塞等待,这里的前提是wait()需要拿到锁,没有锁自然不能进行阻塞等待,所以wait()的使用条件也需要在synchronized加锁的条件下才能使用。
notify()是唤醒在wait()的线程,notify()没有强制要求必须在synchronized的条件下,但是一般还是选择在synchronized的条件下使用。
wait()与notify()都必须在拿锁对象去使用。
3.4 volatile关键字
由于编译器在某些情况下会对代码进行优化,这里就会产生内存可见性的问题和指令重排序的问题,从而使得多线程产生安全问题。使用volatile关键字可以解决内存可见性问题和指令重排序的问题。