Java多线程--线程可见性

1 可见性:一个线程对共享变量值的修改,能够及时的被其他线程看到。
2 共享变量:如果一个变量在多个线程的工作内存中都能存在副本,那么这个变量就是这几个线程的共享变量。

所有的变量都存在主内存中,
每个变量都有自己独立的工作内存,里面保存该线程使用到的变量的副本。
这里写图片描述
线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读取。
不同线程之间不能互相访问变量,只能通过主内存来访问。

共享变量的可见性原理,
线程1对共享变量的修改要想被线程2及时的看到,由线程1将共享变量刷新到主内存中,然后主内存将最新的共享变量的值更新到工作内存2中。

Synchronized能够实现:
原子性,
线程解锁时,必须把共享变量的值刷新到主内存中。
线程解锁时,将清空工作内存中共享变量的值,从而使用共享变量时从主内存中获取最新的值。

线程执行互斥代码的过程:
1、获得互斥锁,
2、清空工作内存,
3、从主内存中拷贝变量到工作内存中。
4、执行代码。
5 、将更改后的共享变量的值刷新到主内存中。
6、释放互斥锁。

可见性

volatile关键字:
能够保证volatile变量的可行性
不能保证volatile变量复合操作的原子性

通过加入内存屏障和禁止重排序优化来实现的。
对volatile变量执行写操作时,会在写操作后加入一条store屏障指令。
对volatile变量执行读操作时,会在读操作前加入一条load屏障指令。

通俗的讲,volatile变量在每次被线程访问时,都强迫从主内存中重新读取该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存中,这样任何时刻,都能保证不同的线程看到的都是最新的值。

线程写volatile变量的过程:
1 改变线程工作内存中volatile变量的值,
2 将改变后的副本的值从工作内存刷新到主内存。

线程读volatile变量的过程:
1 从主内存中读取volatile变量的最新值到线程的工作线程中。
2 从工作内存中读取volatile变量的副本。

public class VolatileDemo {

    private volatile Integer number = 0;

    public Integer getNumber() {
        return this.number;
    }

    public void increase() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.number++;
        //上面操作应该分三部分
        //读取number的值,然后加1,然后写入到主内存中
        //
    }

    public static void main(String[] args) {

        final VolatileDemo volatileDemo = new VolatileDemo();
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    volatileDemo.increase();
                }
            }).start();
        }

        // 如果还有子线程在运行,主线程就会让出CPU资源,
        // 直到所有的子线程都运行完,主线程再继续往下执行
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("number:" + volatileDemo.getNumber());
    }
}

可能出现小于100的情况
1 线程A读取number的值
2 线程B读取number的值,
3 线程B执行加1的操作。
4 线程B写入最新的number的值。
主内存number-6,
线程B工作内存:number=6
线程B工作内存:number=5
原因:不是原子操作
解决方案:
1 加同步关键字:

public class VolatileDemo {

    private Integer number = 0;

    public Integer getNumber() {
        return this.number;
    }

    public void increase() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        **//同步代码块
        synchronized (this) {
            this.number++;
        }**
    }

    public static void main(String[] args) {

        final VolatileDemo volatileDemo = new VolatileDemo();
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    volatileDemo.increase();
                }
            }).start();
        }

        // 如果还有子线程在运行,主线程就会让出CPU资源,
        // 直到所有的子线程都运行完,主线程再继续往下执行
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("number:" + volatileDemo.getNumber());
    }
}

2加锁

public class VolatileDemo {

    private Lock lock=new ReentrantLock();

    private Integer number = 0;

    public Integer getNumber() {
        return this.number;
    }

    public void increase() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //加锁
        lock.lock();
        try{
            this.number++;
        }finally{
            //释放锁 在finally中防止出现异常。
            lock.unlock();
        }
    }

    public static void main(String[] args) {

        final VolatileDemo volatileDemo = new VolatileDemo();
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    volatileDemo.increase();
                }
            }).start();
        }

        // 如果还有子线程在运行,主线程就会让出CPU资源,
        // 直到所有的子线程都运行完,主线程再继续往下执行
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("number:" + volatileDemo.getNumber());
    }
}

对volatile变量的安全操作,必须满足:
1 对变量的写入操作不能依赖当前值
不满足:number++、count=count*5等
满足:boolean变量,记录温度变化的变量
2 该变量没有包含在具有其他变量的不变式中,
不满足:不变是low

public class TestMain {

    private static boolean ready;

    private static Integer number;

    private static class ReadThread extends Thread {
        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new ReadThread().start();
        number = 42;
        ready = true;
    }
}

猜你喜欢

转载自blog.csdn.net/BtWangZhi/article/details/80808954