高并发--卷2--可见性--volatile

什么是可见性?要知道这个我们需要先来了解一下多线程的内存模型。

多线程简易内存模型

高并发--卷2--可见性--volatile_第1张图片

如上图,可知,每个线程都有一个独立的内存空间,线程是直接到自己的工作内存中读取数据。

可见性

要理解可见性,我们先来看一个例子。

public class Test{
    public static void main(String[] args) throws InterruptedException {
        Service t1 = new Service();
        t1.start();
        Thread.sleep(500);
        t1.setFlag(false);
    }
}

class Service extends Thread{   
    private boolean flag = true;
    @Override
    public void run() {
        System.out.println("线程开始了");
        while(flag){
            //Thread.sleep(1); 加上这句会让线程内存与公共内存同步
        }
        System.out.println("线程停下来了");
    }

    public void setFlag(boolean flag){
        this.flag = flag;
    }
}

运行结果:
线程开始了

发现线程开始之后,无法正常停止,这种情况也就是32位的jvm上的-server模式,为什么不能停止线程呢?事实上,jvm对这种情况做了优化,也就是说,线程会直接到线程自身的工作内存中获取数据,而不会去公共内存中获取数据,也不会把公共内存中的数据同步到自身内存中,所以这里引出可见性。
可见性,所谓可见性,就是让某个变量在多个线程中可见,这就意味着,需要让各个线程从公共内存中获取数据,这就需要volatile关键字。

volatile关键字

引入volatile关键字后的内存读取情况。

高并发--卷2--可见性--volatile_第2张图片

引入volatile关键字后,程序修改为:

public class Test{
    public static void main(String[] args) throws InterruptedException {
        Service t1 = new Service();
        t1.start();
        Thread.sleep(500);
        t1.setFlag(false);
    }
}

class Service extends Thread{   
    volatile private boolean flag = true;
    @Override
    public void run() {
        System.out.println("线程开始了");
        while(flag){
            //Thread.sleep(1); 加上这句会让线程内存与公共内存同步
        }
        System.out.println("线程停下来了");
    }

    public void setFlag(boolean flag){
        this.flag = flag;
    }
}

输出结果:
线程开始了
线程停下来了

原子性

先来看一个例子。

public class Test{
    public static void main(String[] args) throws InterruptedException {
        Service services[] = new Service[100];
        for (int i = 0; i < services.length; i++) {
            services[i] = new Service();
        }
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
        Thread.sleep(2000);
        System.out.println(Service.count);
    }
}

class Service extends Thread{   
    public static int count = 0;
    @Override
    public void run() {
        addCount();
    }

    public void addCount(){
        for (int i = 0; i < 100; i++) {
            count++;
        }       
    }
}

输出结果:
9975

奇怪的事情发生了,明明这里是一个静态变量,即使多个线程并发,那么应该都是获取的同一个count++,那么结果应该是10000才对啊,为什么是9975呢?
事实上,这里就涉及到原子性的问题,这里count++分三步,先获取count的值,再count=count+1,再写入内存中 ,这三步并不是一个整体,那么也就意味着,当一个线程读取到count之后,然后++,而另外一个线程也读取到同样值的count,然后++,两个线程写入的值就是一样了,这就少计算了一次,从而导致count并不能正确的到10000,那么应该如何解决这个问题呢?
来看下面这段代码:

public class Test{
    public static void main(String[] args) throws InterruptedException {
        Service services[] = new Service[100];
        for (int i = 0; i < services.length; i++) {
            services[i] = new Service();
        }
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
        Thread.sleep(2000);
        System.out.println(Service.count);
    }
}

class Service extends Thread{   
    public static int count = 0;
    @Override
    public void run() {
        try {
            addCount();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void addCount() throws InterruptedException{
        for (int i = 0; i < 100; i++) {
            count++;
        }       
    }
}

发现与上面那段代码不同的地方在于,将addCount()改成了静态,也就是内存中只有这一个方法,同时在前面加上了synchronize,这就意味着,只有当一个线程count++for循环执行完成还之后,才能执行另一个线程的for。
如下这种写法根上面的写法的意思是一样的,这里都是为了争抢”aaa”常量锁,只有当count++完了,锁才会被释放,另外一个线程才能够再活动该锁,上面的写法优于下面的写法,因为他不会每循环一次就去判断一次锁的状态。

    public static void addCount() throws InterruptedException{
        for (int i = 0; i < 100; i++) {
            synchronized ("aaa") {
                count++;
            }
        }       
    }

你可能感兴趣的:(java)