JUC并行开发03-共享模式与异步模式

临界区

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区。

竞态条件

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

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
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值