03 java线程安全与通讯
1 synchronized
synchronized关键字可以实现一个简单的策略来防止线程干扰和内存一致性错误。如果一个对象对于多个线程是可见的,那么该对象的所有读写都将通过同步的方式进行,具体表现如下
- synchronized关键字提供了一种锁的机制,确保共享变量的互斥访问,防止数据不一致问题的出现
- synchronized关键字包括monitor enter 和monitor exit两个jvm指令,能保证在任何时候,任何线程执行到monitor enter成功之间都从主内存中获取数据,而不是从缓存中。在monitor exit 运行成功后,共享变量被更新后的值必须刷入主内存。
- synchronized的指令严格准守java happens-before 规则, 一个monitor exit 指令之间必定要有一个monitor enter
1.1 This Monitor
synchronized关键字修饰了同一个实例对象的两个不同方法,争抢的是同一个monitor的lock,而与之关联的引用则是ThisMonitor的实例引用。
如下代码示例:
运行上面测试类后,执行结果只有method1方法运行,另外一个方法method2根本没有执行,分析线程的堆栈信息,执行jdk自带的jstack pid名称,如下图:
1.2 Class Monitor
一个类上有2个静态方法分别使用synchronized对其进行同步,只有一个方法被调用,另一个方法并没有被调用。
如下代码示例:
分析线程堆栈信息如下:
2 死锁
- 交叉锁可导致程序出现死锁
线程A持有R1的锁等待获取R2的锁,
线程B持有R2的锁等待获取R1的锁。
- 内存不足
线程T1, 已经获取了10M内存,还需要20M内存
线程T2,已经获取了20M内存,还需要10M内存
两个线程都需要等待彼此能够释放内存资源。才能继续执行
- 一问一答的数据交换
服务端开启某个端口,等待客户端访问,客户端发送请求立即等待接收,由于某种原因服务端错过了客户端的请求。
仍然在等待一问一答式的数据交换,此时服务端和客户端都在等待着对方发送数据
- 数据库锁
无论是数据库表级别的锁,还是行级别的锁,比如某个线程执行了for update 语句退出了事务,其他线程访问该数据库时都将陷入死锁。
- 文件锁
某线程获得了文件锁,意外退出,其他读取该文件的线程也将会进入死锁,知道系统释放文件句柄资源
- 死循环引起的死锁
程序由于代码原因或者对某些异常处理不当,进入死循环。查看线程堆栈信息不会发现任何死锁的迹象,但是程序不工作。cpu居高不下
这种死锁一般称为系统假死。排查较困难。
3 线程通信
3.1 wait、notify、notifyAll
线程间的协作通过wait、notify和notifyAll来实现的,
- wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
- 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
- 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
- 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效
3.2 线程休息室wait set
若干个线程调用了wait方法之后被加入与monitor关联的wait set中,待另外一个线程调用该monitor的notify方法之后,其中一个线程会从wait set中弹出,至于是随机弹出还是以先进先出的方式弹出,虚拟机规范没有给出强制要求