多线程带来的风险
什么是线程安全
有关线程安全的定义是复杂的,但是我们通常可以这样认为:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的.
线程不安全的原因
1.原子性
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人,如果没有任何机制保证,A进入房间之后,还没有出来,B就想进去,打断A在房间里面的隐私,这就不具有原子性,但是我们在A进这个房间之后,给房间加上一把锁,那这样就保证了A的隐私,这就保证了这段代码的原子性.这种现象也叫做同步互斥,表示操作是相互排斥的.
2.可见性
以下是主内存在工作时的示意图:
为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题.
3.代码顺序性
一段代码是这样的:
(1).去菜鸟驿站取快递
(2).去图书馆学习10分钟
(3).去菜鸟驿站寄快递
如果是在单线程情况下,JVM,CPU指令集会对其进行优化,比如,按3->1->2的方式执行也是没有问题的,可以少跑一次菜鸟驿站.这叫做指令重排序.
如何解决线程不安全的问题?
1.synchronized关键字-监视器锁monitor lock
synchronized的底层时使用操作系统的mutex lock实现的.
- 当线程释放锁时,JVM会把线程对应的工作内存中的共享变量刷新到主内存中
- 当线程获取锁时,JVM会把线程对应的的本地内存置为无效.从而使得被监视器保护的临界区代码必须从主内存中读取共享变量
- synchronized用的锁是存在Java对象头里的
- synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题
- 同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入
//锁的 SynchronizedDemo 对象publicclassSynchronizedDemo{publicsynchronizedvoidmethond(){}publicstaticvoidmain(String[] args){SynchronizedDemo demo =newSynchronizedDemo();
demo.method();// 进入方法会锁 demo 指向对象中的锁;出方法会释放 demo 指向的对象中的锁}}
publicclassSynchronizedDemo{publicsynchronizedstaticvoidmethond(){}publicstaticvoidmain(String[] args){method();// 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放SynchronizedDemo.class 指向的对象中的锁
}}
publicclassSynchronizedDemo{publicvoidmethond(){// 进入代码块会锁 this 指向对象中的锁;出代码块会释放 this 指向的对象中的锁synchronized(this){}}publicstaticvoidmain(String[] args){SynchronizedDemo demo =newSynchronizedDemo();
demo.method();}}
2.volatile关键字
修饰的共享变量,可以保证可见性,部分保证顺序性
classThraedDemo{privatevolatileint n;}
对象的等待集wait set
wait()方法
wait()方法就是使线程停止运行
- wait()方法的作用是使当前代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程置入"预执行队列"中,并且在wait()所在的代码出停止执行,直到接到通知或被中断为之.
- wait()方法只能在同步方法中或同步块中调用.如果调用wait()方法时,没有持有适当的锁,就会抛出异常
- wait()方法执行之后,当前线程释放锁,线程与其他线程竞争重新获取锁.
publicstaticvoidmain(String[] args)throwsInterruptedException{Object object =newObject();synchronized(object){System.out.println("等待中...");
object.wait();System.out.println("等待已过...");}System.out.println("main方法结束...");}
运行结果:
这样在执行到Object.wait()之后就一直等待下去,但是程序肯定不能一直这么等待下去,这个时候就需要使用另一个方法(notify())来唤醒它
notify()方法
notify()方法就是时停止的线程继续执行
- notify()方法也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的那些其他线程,该方法向这些其他线程发出通知notify,并使它们重新获取该对象的对象锁,.如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程
- 在notify()方法之后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁.
- wait(),notify()必须使用在synchronized同步方法或者代码块内.
notifyAll()方法
多个线程在等待就可以用notifyAll()方法一次性唤醒所有的等待线程.
[面试题]:wait()和sleep()的对比
实际上wait()方法与sleep()方法是完全没有可比性的,前者用于线程之间的通信,后者是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间,说白了就是放弃线程执行知识wait的一小段现象.具体总结如下:
- wait()方法执行前需要请求锁,而wait()执行时会先释放锁,等被唤醒时再重新请求锁,这个锁是wait()对象上的monitor lock.
- sleep()方法是无视锁的存在的,即之前请求的锁不会释放,即使没有锁也不会去请求锁.
- wait()方法是Object的方法.
- sleep()方法是Thread的静态方法.
版权归原作者 风口上的猪A 所有, 如有侵权,请联系我们删除。