临界区
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区。
竞态条件
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。
Synchronized(对象锁)
实例(给对象上锁)
static int counter = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(()->{
for(int i=0;i<5000;i++){
synchronized(room){
counter++;
}
}
},"t1");
Thread t2 = new Thread(()->{
for(int i=0;i<5000;i++){
synchronized(room){
counter--;
}
}
},"t2");
t1.start();
t2.start();
}
实例(锁对象面向对象改进)
class Room{
private int counter=0;
public void increment(){
synchronized(this){
counter++;
}
}
public void increment(){
synchronized(this){
counter--;
}
}
public void getCounter(){
synchronized(this){
return counter;
}
}
}
实例(加在成员方法上)
class Room{
private int counter=0;
public synchronized void increment(){
counter++;
}
public synchronized void increment(){
counter--;
}
public synchronized void getCounter(){
return counter;
}
}
局部变量线程安全分析
- 局部变量是线性安全的(存储在线程栈的栈帧中)。
- 但局部变量引用的对象未必是线程安全的(引用的堆对象)。
- String类、Integer类,StringBuffer类,Random类,HashTable类(加了synchronized关键字)等是线程安全的,这指的是多个线程调用它们同一个实例的某个方法都是线程安全的。
Monitor机制
每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的MarkWord中就被设置指向Monitor对象的指针。
synchronized优化
轻量级锁:
- 如果一个对象虽然有多线程访问,但多线程访问的时间是错开的,那么可以用轻量级锁来优化。轻量级锁对使用者是透明的,语法仍是synchronized。
- 创建锁记录(LockRecord)对象,每个线程栈的栈帧都会包含一个锁记录的结果,内部可以存储锁定对象的markword。
锁膨胀:
- 为对象申请monitor锁,让对象Object指向重量级锁地址,然后自己进入到monitor的entryList。
- 当获取轻量级锁的线程退出同步块解锁时,使用cas将markword的值恢复给对象头,失败。这是会进入到重量级锁的流程,即按照monitor地址找到monitor对象,设置owner为空,唤醒entrylist中blocked线程。
自旋优化:
- 重量级锁竞争的时候,还可以进行使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程一级退出了同步块,释放了锁)这是当前线程就可以避免堵塞(避免上下文切换开销)。
- 自旋锁是自适应的(java7以后不能控制是否开启)。
偏向锁:
- 只有第一次使用cas将线程id设置到对象的markword头,之后发现这个线程id是自己的就表示没有竞争。
- 如果开启了偏向锁,那么对象创建后,markword值最后3位为101,这时它的thread、epoch、age都为0。
- 偏向锁是默认是延迟的,不会在程序启动时立即生效。
wait
- obj.wait()、obj.notify()、obj.notifyAll()都属于Object对象的方法,必须获得此对象的锁,才能调用这几个方法。
同步模式之保护性暂停
即Guarded Suspension,用在一个线程等待另一个线程的执行结果。
要点
- 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject。
- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
- JDK中,join的实现、Future的实现,采用的就是此模式。
- 因为要等待另一方的结果,因此归类到同步模式。
实例
class GuardObject{
//结果
private Object response;
//获取结果
public Object get(){
synchronized(this){
//没有结果
while(response==null){
try{
this.wait();
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
return response;
}
//产生结果
public void complete(Object response){
synchronized(this){
//给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
join原理
底层也是用wait等待实现的。
扩展
如果需要在多个类之间使用GuardedObject对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。
图中Futures就好比居民楼一层的信箱(每个信箱有房间编号),左侧的t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员。
实例
class Mailboxes{
private Map<Integer,GuardedObject> boxes = new Hashtable<>();
private static int id = 1;
private static synchronized int generateId(){
return id++;
}
public static GuardedObject createGuardedObject(){
GuardedObject go = new GuardedObject(generateId());
boxes.put(go.getId(),go);
return go;
}
public static Set<Integer> getIds(){
return boxes.keySet();
}
}
class GuardObject{
//标识Guarded Object
private int id;
public int GuardObject(int id){
this.id=id;
}
public int getId(){
return id;
}
//结果
private Object response;
//获取结果
public Object get(){
synchronized(this){
//没有结果
while(response==null){
try{
this.wait();
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
return response;
}
//产生结果
public void complete(Object response){
synchronized(this){
//给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
异步模式-生产者/消费者
要点:
- 与前面的保护性暂停不同,不需要产生结果和消费结果的线程一一对应。
- 消费队列可以用来平衡生产和消费的线程资源。
- 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据。
- 消息队列是有容量限制的,满时不会再加入数据。
- JDK中的各种堵塞队列采用的就是这种模式。
实例
class MessageQueue{
//消息队列的集合
private LinkedList<Message> list = new LinkedList<>();
//队列容器
private int capcity;
//获取消息
public Message take(){
//检查对象是否为空
synchronized(list){
while(list.isEmpty()){
try{
list.wait();
} catch (InterruptedException e){
e.printStackTrace();
}
}
//从队列头部获取消息并返回
Message message = list.removeFirst();
list.notifyAll();
return message;
}
}
//存入消息
public void put(Message message){
//检查对象是否以满
synchronized(list){
while(list.size()==capcity){
try{
list.wait();
} catch (InterruptedException e){
e.printStackTrace();
}
}
//从队列加入队列尾部
list.notifyAll();
return list.addLast(message);
}
}
}
final class Message{
private int id;
private Object value;
public Message(int id,Object value){
this.id=id;
this.value=value;
}
...
//get set toString
}