java----并发编程散记

2019-03-15  本文已影响0人  不过意局bugyj

JMM,即java内存模型,是在硬件基础上抽象出来的模型,不是实际存在的。其将内存主要分为两个部分:主内存和工作内存。前者为所有线程共享区域,后者为线程私有。

线程被创建出来,就会有一个工作内存区域,其所需要的一些数据也会从主内存中拷贝至私有内存中,而不是直接在主内存中操作。线程与线程之间的数据交换也是通过主内存来传递的。

并发编程的三个概念:可见性、原子性、有序性。并发程序的正确执行需要确保这三个性质的正确性。

缓存一致性问题:即可见性问题。前面所说线程不会直接在主内存中工作,而是从主内存中拷贝一份至私有内存。当电脑为单核时这样做不会影响可见性,可如今电脑多为多核,在每个cpu中都有一个线程执行。多核就有多线程并行。当一个线程修改了主内存的值时,另一个线程使用的确实修改前的值。这就是缓存一致性问题。以下测试缓存一致性代码:

public class TestVisibility {

    /**
     *如果没有加volatile,下面的第一个启动的子线程就会一直呈现阻塞状态。
     * 这也就体现了volatile可以解决缓存一致性问题!
     */
    private static volatile boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            int i = 0;
            while (!stop) {
                i++;
            }
        }).start();
//        测试可见性下面的延时必不可少,我们知道,原子性无法保证,上面的对于stop变量还需要进行load操作
//        只有当stop被上面的线程导入进了线程工作区才可体现可见性问题
        TimeUnit.SECONDS.sleep(1);
        System.out.println("线程启动成功!");
        new Thread(() -> {
            stop = true;
        }).start();
    }
}

如何保证可见性呢?
解决缓存一致性问题有两种方式:总线锁和缓存锁。我们知道cpu从主存中读取数据必通过总线,总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号,其他处理器的请求将被阻塞,那么该处理器就可以独占共享锁。但是显然这样效率就会很低了!
缓存锁即在主内存中变量行加锁,遵循MESI协议,加锁后数据既有四种状态:S:此变量已被多核拷贝。E:此变量被单核拷贝。M:此变量被修改。I:如此数据被某个线程修改,其他线程中拷贝的值失效。 缓存锁保证了当一个线程修改了某个变量后即修改锁至M,此后其他线程想使用这个变量时,检测到主内存中的变量值已被修改,私有工作内存中的变量失效,重新载入新的值!

volatile为什么不能保证原子性?
答:因为volatile算能在读取内存中的数据会加一个锁,其他线程在使用期间会察觉出变量的改变。但是线程从主内存中载入数据和修改数据值后更新主内存中数据的操作期间其他线程修改了值是察觉不到的。

如何保证原子性?
答:使用synchronized/Atomic/java.Util.Concurrent.Lock。
synchronized在域中所有数据加锁,这些数据每次只能有一个线程使用。
Lock效果1.能够去把当前处理器缓存的数据写到系统内存。2.是其他处理器缓存行数据失效。岂能出发总线锁或缓存锁没具体取决于cpu自己!以下是测试原子性问题代码:

public class TestAtomic {
    private static class innerClass {
        public static int count = 0;

        private static void sleep(long millisecond) {
            try {
                Thread.sleep( millisecond);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public static synchronized void inc() {
            //加上synchronized然后注释掉sleep就可以实现每次获取的结果为1000,否则为4,想想为什么!
//            sleep(1000);
            count++;
        }


        private static Lock lock = new ReentrantLock();

        /**
         * 使用java.util.Concurrent包中的lock也能解决原子性问题!
         */
        public static void incWithLock() {
            lock.lock();
            count++;
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                innerClass.incWithLock();
            }).start();
        }
        innerClass.sleep(4000);

        //得到的值可能为1000也可能是小于1000,这就证明了count++不是原子性操作!
        System.out.println("count :" + innerClass.count);
    }
}

上一篇下一篇

猜你喜欢

热点阅读