线程安全,底层实现原理和JMM

让多线程下的类安全起来:无状态、加锁、让类不可变、栈封闭(方法封装) 、安全的发布对象(不暴露成员)java

死锁

必定发生在多个线程争夺多个资源里的状况下,发生的缘由是每一个线程拿到了某个(某些)资源不释放,同时等待着其余线程所持有的资源。解决死锁的原则就是确保正确的获取资源的顺序,或者获取资源时使用定时尝试机制。程序员

常见的死锁:简单顺序死锁、动态顺序死锁编程

/**
 *简单顺序死锁
 */
public class SimpleDeadLock {

    private static Object left = new Object();
    private static Object right = new Object();

    //先锁左 再右
    private static void leftToRight() throws InterruptedException {
        synchronized (left){
            System.out.println(Thread.currentThread().getName()+" get left");
            Thread.sleep(100);
            synchronized (right){
                System.out.println(Thread.currentThread().getName()+" get right");
            }
        }
    }

    //先锁左 再右(若是相反就会出现顺序死锁)
    private static void rightToLeft() throws InterruptedException {
        synchronized (left){
            System.out.println(Thread.currentThread().getName()+" get right-left");
            Thread.sleep(100);
            synchronized (right){
                System.out.println(Thread.currentThread().getName()+" get left-right");
            }
        }
    }

    private static class TestThread extends Thread{
        private String name;

        public TestThread(String name) {
            this.name = name;
        }

        @Override
        public void run(){
            try {
                rightToLeft();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("Main");
        TestThread  testThread = new TestThread("testThread");
        testThread.start();
        try {
            leftToRight();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
=========== 
运行结果  
死锁时=============
Main get left
Thread-0 get right-left
......

正常时=====================
Main get left
Main get right
Thread-0 get right-left
Thread-0 get left-right

动态顺序死锁示例数组

/**
 * 账号信息类
 */
public class Account {
    private long number;
    private final String name;
    private int money;
    private final Lock lock = new ReentrantLock();

    public Lock getLock() {
        return lock;
    }

    public Account(String name, int amount) {
        this.name = name;
        this.money = amount;
    }
    
    //加钱
    public void addMoney(int amount){
        money = money + amount;
    }
    //减钱
    public void flyMoney(int amount){
        money = money - amount;
    }

    public String getName() {
        return name;
    }

    public int getAmount() {
        return money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }


}
/**
 * 接口
 */
public interface ITransfer {

    void transfer(Account from, Account to, int amount) throws InterruptedException;
}
public class NormalTransfer implements ITransfer{
    @Override
    public void transfer(Account from, Account to, int amount)
            throws InterruptedException {
        synchronized (from){
            System.out.println(Thread.currentThread().getName()+" get "+from.getName());
            Thread.sleep(100);
            synchronized (to){
                System.out.println(Thread.currentThread().getName()
                        +" get "+to.getName());
                from.flyMoney(amount);
                to.addMoney(amount);
            }
        }
    }
}
/**
 * 银行业务类
 */
public class Bank {

    private static class TransferThread extends Thread{
        private String name;
        private Account from;
        private Account to;
        private int amount;
        private ITransfer transfer;

        public TransferThread(String name, Account from, Account to,
                              int amout,ITransfer transfer) {
            this.name = name;
            this.from = from;
            this.to = to;
            this.amount = amout;
            this.transfer = transfer;
        }

        @Override
        public void run() {
            Thread.currentThread().setName(name);
            try {
                transfer.transfer(from,to,amount);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Bank bank = new Bank();
        Account zhangsan = new Account("zhangsan",20000);
        Account Lisi = new Account("lisi",20000);
        ITransfer transfer = new NormalTransfer();//运行后出现动态顺序死锁

        TransferThread zsToLisi = new TransferThread("zsToLisi",zhangsan,Lisi,
                2000,transfer);
        TransferThread lisiTozs = new TransferThread("lisiTozs",Lisi,zhangsan,
                4000,transfer);
        zsToLisi.start();
        lisiTozs.start();

    }

解决动态顺序死锁缓存

/**
 * 动态顺序锁  比较hash大小
 *  identityHashCode  返回hashcode 无论给定的对象曾经是否复写过hashcode 
 */
public class SafeTransfer implements ITransfer {

    private static Object tieLock = new Object();

    @Override
    public void transfer(Account from, Account to, int amount)
            throws InterruptedException {

        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);

        if(fromHash<toHash){
            synchronized (from){
                System.out.println(Thread.currentThread().getName()+" get "+from.getName());
                Thread.sleep(100);
                synchronized (to){
                    System.out.println(Thread.currentThread().getName()
                            +" get "+to.getName());
                    from.flyMoney(amount);
                    to.addMoney(amount);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }else if(toHash<fromHash){
            synchronized (to){
                System.out.println(Thread.currentThread().getName()+" get "+to.getName());
                Thread.sleep(100);
                synchronized (from){
                    System.out.println(Thread.currentThread().getName()
                            +" get "+from.getName());
                    from.flyMoney(amount);
                    to.addMoney(amount);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }else{
            synchronized (tieLock){
                synchronized (to){
                    System.out.println(Thread.currentThread().getName()+" get "+from.getName());
                    Thread.sleep(100);
                    synchronized (from){
                        System.out.println(Thread.currentThread().getName()
                                +" get "+to.getName());
                        from.flyMoney(amount);
                        to.addMoney(amount);
                    }
                }
            }
        }
    }
}
/**
 * 定时轮询获取锁
 */
public class TryLockTransfer implements ITransfer {
    @Override
    public void transfer(Account from, Account to, int amount)
            throws InterruptedException {
        Random r = new Random();
        while(true){
            if(from.getLock().tryLock()){
                try{
                    System.out.println(Thread.currentThread().getName()
                            +" get from "+from.getName());

                    if(to.getLock().tryLock()){
                        try{
                            System.out.println(Thread.currentThread().getName()
                                    +" get to "+to.getName());
                            from.flyMoney(amount);
                            to.addMoney(amount);
                            System.out.println(from);
                            System.out.println(to);
                            break;
                        }finally {
                            to.getLock().unlock();
                        }
                    }

                }finally {
                   from.getLock().unlock();
                }
            }
            Thread.sleep(r.nextInt(5));//防止产生活锁
        }
    }
}

干货:查看线程中栈状况安全

jdkbin下的jstack.exe

进入bin目录

执行 jps -m 找到当前程序id号 

再执行 jstack id号 查看

解决死锁:性能优化

保证正确的加锁、使用定时锁多线程

其余活跃性危险:其余类型的死锁(资源争夺)、饥饿(长久拿不到锁没法工做)、糟糕的响应性能(多个应用不一样业务,其中一个业务占有运行资源)、活锁(尝试获取时发生碰撞,又释放又碰撞,能够经过sleep来减小次数)并发

对性能的思考

  1. 程序的安全性优于性能的提高
  2. 使用多线程会带来额外的性能开销,滥用线程,有可能致使得不偿失。
  3. 所谓性能,包含多个指标。例如“多快”:服务时间、等待时间、延迟时间;例如“多少”:吞吐量,例如可伸缩性等等。
  4. 性能的各个指标方面,是彻底独立的,有时候甚至是相互矛盾。
  5. 因此性能的提高是个包括成本在内多方面权衡和妥协的结果。

性能优化的黄金原则:app

首先保证程序正确,而后再提升运行速度(若是有确切的证据代表程序确实慢)。

Amdahl定律

F :程序中的串行部分,是个百分比(100%-1%),

N:cpu的个数

Speedup:指在增长cpu的状况下,程序的加速比

线程引入的开销

上下文的切换

内存同步

阻塞

减小锁的竞争

快进快出,缩小锁的范围,将与锁无关的,有大量计算或者阻塞操做的代码移出同步范围。

减少锁的粒度,多个相互独立的状态变量可使用多个锁来保护,每一个锁只保护一个变量。

锁的分段,例如ConcurrentHashMap中的实现。

减小独占锁的使用,例如读多写少的状况下,用读写锁替换排他锁。

 

安全的单例模式

/**
 * 饿汉式单例
 */
public class SingleEHan {
    public static SingleEHan singleEHan = new SingleEHan();
    private SingleEHan(){};

}
/**
 * 懒汉式单例-双重检查(不推荐使用了)  
 */
public class SingleDcl {

    private volatile static SingleDcl single;
    private SingleDcl(){}

    public static SingleDcl getInstance(){
        if(null==single){
            synchronized (SingleDcl.class){
                if(single==null){
                    single = new SingleDcl();
                }
            }
        }
        return single;
    }
}
/**
 * 延迟类占位符
 */
public class SingleClassInit {
    private String name;
    private SingleClassInit(){}

    private static class InstanceHolder{
        public static SingleClassInit instance = new SingleClassInit();
    }

    public static SingleClassInit getInstance(){
        return InstanceHolder.instance;
    }
}

 

内存模型的抽象结构

物理计算机中的并发问题,物理机遇到的并发问题与虚拟机中的状况有很多类似之处,物理机对并发的处理方案对于虚拟机的实现也有至关大的参考意义。

“让计算机并发执行若干个运算任务”与“更充分地利用计算机处理器的效能”之间的因果关系,看起来瓜熟蒂落,实际上它们之间的关系并无想象中的那么简单,其中一个重要的复杂性来源是绝大多数的运算任务都不可能只靠处理器“计算”就能完成,处理器至少要与内存交互,如读取运算数据、存储运算结果等,这个I/O操做是很难消除的(没法仅靠寄存器来完成全部运算任务)。因为计算机的存储设备与处理器的运算速度有几个数量级的差距,因此现代计算机系统都不得不加入一层读写速度尽量接近处理器运算速度的高速缓存(Cache)来做为内存与处理器之间的缓冲:将运算须要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了。

基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,可是也为计算机系统带来更高的复杂度,由于它引入了一个新的问题:缓存一致性(Cache Coherence)。在多处理器系统中,每一个处理器都有本身的高速缓存,而它们又共享同一主内存(MainMemory)。当多个处理器的运算任务都涉及同一块主内存区域时,将可能致使各自的缓存数据不一致,若是真的发生这种状况,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,须要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操做。所谓的“内存模型”一词,能够理解为在特定的操做协议下,对特定的内存或高速缓存进行读写访问的过程抽象。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每一个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其余的硬件和编译器优化。

在JMM中若是线程A与线程B之间要通讯的话,必需要经历下面2个步骤。

1)线程A把本地内存A中更新过的共享变量刷新到主内存中去。

2)线程B到主内存中去读取线程A以前已更新过的共享变量。

 

volatile的实现原理

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。

可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。若是volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,由于它不会引发线程上下文的切换和调度。

volatile是如何来保证可见性的呢?是由于在编译的时候使用了一个Lock前缀的指令,Lock前缀的指令在多核处理器下会引起了两件事情,

1)将当前处理器缓存行的数据写回到系统内存。

2)这个写回内存的操做会使在其余CPU里缓存了该内存地址的数据无效。

为了提升处理速度,处理器不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其余)后再进行操做,但操做完不知道什么时候会写到内存。若是对声明了volatile的变量进行写操做,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。可是,就算写回到内存,若是其余处理器缓存的值仍是旧的,再执行计算操做就会有问题。因此,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每一个处理器经过嗅探在总线上传播的数据来检查本身缓存的值是否是过时了,当处理器发现本身缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操做的时候,会从新从系统内存中把数据读处处理器缓存里。

 

synchronized的实现原理

synchronized一直是元老级角色,不少人都会称呼它为重量级锁。可是,随着Java SE 1.6对synchronized进行了各类优化以后,有些状况下它就并不那么重了。

Java中的每个对象均可以做为锁。具体表现为如下3种形式。

·对于普通同步方法,锁是当前实例对象。

·对于静态同步方法,锁是当前类的Class对象。

·对于同步方法块,锁是Synchonized括号里配置的对象。

当一个线程试图访问同步代码块时,它首先必须获得锁,退出或抛出异常时必须释放锁。那么锁到底存在哪里呢?锁里面会存储什么信息呢?

synchronized用的锁是存在Java对象头里的。若是对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,若是对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节。

Java SE 1.6为了减小得到锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争状况逐渐升级。锁能够升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提升得到锁和释放锁的效率

 

偏向锁

HotSpot的做者通过研究发现,大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要进行CAS操做来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。若是测试成功,表示线程已经得到了锁。若是测试失败,则须要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):若是没有设置,则使用CAS竞争锁;若是设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操做把获取到这个锁的线程的ID记录在对象的Mark Word之中,若是CAS操做成功,持有偏向锁的线程之后每次进入这个锁相关的同步块时,虚拟机均可以再也不进行任何同步操做(例如Locking、Unlocking及对Mark Word的Update等)。当有另一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态

 

轻量级锁

(1)轻量级锁加锁

线程在执行同步块以前,JVM会先在当前线程的栈桢中建立用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。而后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程便尝试使用自旋来获取锁。

(2)轻量级锁解锁

轻量级解锁时,会使用原子的CAS操做将Displaced Mark Word替换回到对象头,若是成功,则表示没有竞争发生。若是失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

轻量级锁是在无竞争的状况下使用CAS操做去消除同步使用的互斥量,那偏向锁就是在无竞争的状况下把整个同步都消除掉,连CAS操做都不作了。

 

重量级锁

重量锁在JVM中又叫对象监视器(Monitor),除了具有Mutex(0|1)互斥的功能,它还负责实现了Semaphore(信号量)的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责作互斥,后一个用于作线程同步。

 

锁的优缺点对比

优势

缺点

适用场景

偏向锁

加锁和解锁不须要额外的消耗,和执行非同步方法比仅存在纳秒级的差距

若是线程间存在锁竞争,会带来额外的锁撤销的消耗

适用于只有一个线程访问同步块场景

轻量级锁

竞争的线程不会阻塞,提升了程序的响应速度

若是始终得不到锁竞争的线程使用自旋会消耗CPU

追求响应时间,锁占用时间很短

重量级锁

线程竞争不使用自旋,不会消耗CPU

线程阻塞,响应时间缓慢

追求吞吐量,锁占用时间较长

 

原子操做的实现原理

使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操做。首先处理器会自动保证基本的内存操做的原子性。处理器保证从系统内存中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其余处理器不能访问这个字节的内存地址。可是复杂的内存操做处理器是不能自动保证其原子性的。处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操做的原子性。

(1)使用总线锁保证原子性

第一个机制是经过总线锁保证原子性。若是多个处理器同时对共享变量进行读改写操做(i++就是经典的读改写操做),那么共享变量就会被多个处理器同时进行操做,这样读改写操做就不是原子的,操做完以后共享变量的值会和指望的不一致。处理器使用总线锁就是来解决这个问题的。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其余处理器的请求将被阻塞住,那么该处理器能够独占共享内存。

(2)使用缓存锁保证原子性

在同一时刻,咱们只需保证对某个内存地址的操做是原子性便可,但总线锁定把CPU和内存之间的通讯锁住了,这使得锁按期间,其余处理器不能操做其余内存地址的数据,因此总线锁定的开销比较大,目前处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。缓存锁定就是当某块CPU对缓存中的数据进行操做了以后,就通知其余CPU放弃储存在它们内部的缓存,或者从主内存中从新读取。

处理器提供了不少Lock前缀的指令来实现。例如,位测试和修改指令:BTS、BTR、BTC;交换指令XADD、CMPXCHG。JVM中的CAS操做正是利用了处理器提供的CMPXCHG指令实现的。Java中能够经过锁和循环CAS的方式来实现原子操做。

重排序

在执行程序时,为了提升性能,编译器和处理器经常会对指令作重排序。重排序分3种类型。

1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序。

2)指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。若是不存在数据依赖性,处理器能够改变语句对应机器指令的执行顺序。

3)内存系统的重排序。因为处理器使用缓存和读/写缓冲区,这使得加载和存储操做看上去多是在乱序执行。

无论怎么重排序(编译器和处理器为了提升并行度),程序的执行结果不能被改变。编译器、runtime和处理器都必须遵照as-if-serial语义。为了遵照as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操做作重排序,由于这种重排序会改变执行结果。可是,若是操做之间不存在数据依赖关系,这些操做就可能被编译器和处理器重排序。

例如:

double pi = 3.14; // A

double r = 1.0; // B

double area = pi * r * r; // C

A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。所以在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。但A和B之间没有数据依赖关系,编译器和处理器能够重排序A和B之间的执行顺序。

 

happens-before

为了保证内存可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序,为此jvm中提出了happens-before的概念来指定两个操做之间的执行顺序。因为这两个操做能够在一个线程以内,也能够是在不一样线程之间。JMM能够经过happens-before关系向程序员提供跨线程的内存可见性保证(若是A线程的写操做a与B线程的读操做b之间存在happensbefore关系,尽管a操做和b操做在不一样的线程中执行,但JMM向程序员保证a操做将对b操做可见)。

happens-before关系的定义以下。

1)若是一个操做happens-before另外一个操做,那么第一个操做的执行结果将对第二个操做可见。

2)两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必需要按照happens-before关系指定的顺序来执行。若是重排序以后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM容许这种重排序)。

两个操做之间具备happens-before关系,并不意味着前一个操做必需要在后一个操做以前执行!happens-before仅仅要求前一个操做(执行的结果)对后一个操做可见。

上面计算圆的面积的示例代码存在3个happens-before关系,以下。

·A happens-before B。

·B happens-before C。

·A happens-before C。

在3个happens-before关系中,2和3是必需的,但1是没必要要的。

Java内存模型下一些先行发生关系。若是两个操做之间的关系不在此列,而且没法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机能够对它们随意地进行重排序。

程序次序规则(Program Order Rule:在一个线程执行一个方法时,按照程序代码顺序,书写在前面的操做先行发生于书写在后面的操做。准确地说,应该是控制流顺序而不是程序代码顺序,由于要考虑分支、循环等结构。

管程锁定规则(Monitor Lock Rule):一个unlock操做先行发生于后面对同一个锁的lock操做。这里必须强调的是同一个锁,而“后面”是指时间上的前后顺序。

volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操做先行发生于后面对这个变量的读操做,这里的“后面”一样是指时间上的前后顺序。

线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每个动做。

线程终止规则(Thread Termination Rule):线程中的全部操做都先行发生于对此线程的终止检测,咱们能够经过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。

线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,能够经过Thread.interrupted()方法检测到是否有中断发生。

对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

传递性(Transitivity):若是操做A先行发生于操做B,操做B先行发生于操做C,那就能够得出操做A先行发生于操做C的结论。

例如代码:

显示的是一组再普通不过的getter/setter方法,假设存在线程A和B,线程A先(时间上的前后)调用了“setValue(1)”,而后线程B调用了同一个对象的“getValue()”,那么线程B收到的返回值是什么?

咱们依次分析一下先行发生原则中的各项规则,因为两个方法分别由线程A和线程B各自run方法中调用,不在一个线程中,因此程序次序规则在这里不适用;因为没有同步块,天然就不会发生lock和unlock操做,因此管程锁定规则不适用;因为value变量没有被volatile关键字修饰,因此volatile变量规则不适用;后面的线程启动、终止、中断规则和对象终结规则也和这里彻底没有关系。由于没有一个适用的先行发生规则,因此最后一条传递性也无从谈起,所以咱们能够断定尽管线程A在操做时间上先于线程B,可是没法肯定线程B中“getValue()”方法的返回结果,换句话说,这里面的操做不是线程安全的。

那怎么修复这个问题呢?咱们至少有两种比较简单的方案能够选择:要么把getter/setter方法都定义为synchronized方法,这样就能够套用管程锁定规则;要么把value定义为volatile变量,这样就能够套用volatile变量规则来实现先行发生关系。

 

volatile的内存语义

理解volatile特性的一个好方法是把对volatile变量的单个读/写,当作是使用同一个锁对这些单个读/写操做作了同步。

假设有多个线程分别调用上面程序的3个方法,这个程序在语义上和下面程序等价。

volatile写的内存语义以下。

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

volatile读的内存语义以下。

当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

 

锁的内存语义

锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可让释放锁的线程向获取同一个锁的线程发送消息。

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。

当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

相关文章
相关标签/搜索