多线程笔记: volatile、synchronized、Monitor等

  1. 为什么非volatile变量也有线程可见性?
  2. 不加volatile也可以看到变量变化是为什么?
  3. Thread.sleep() 和 System.out.println() 与内存可见性的影响
  4. Thread.sleep() 对线程可见性的影响?
  5. 为什么volatile保证不了线程安全
  6. Java中的Monitor监视器是什么?

1、测试Volatile是否能保证线程可见性

@Slf4j
public class Volatile {

    // ReentrantLock reentrantLock = new ReentrantLock();




    public static void main(String[] args) {
        // 创建任务线程
        Task task = new Task();
        // 启动子线程
        new Thread(task, "子线程1")
                .start();


        try {
            // 主线程修改共享变量,  查看是否能停止子线程
            Thread.sleep(3000);
            task.buffer = false;


            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程: " + Thread.currentThread().getName() + " ---> 结束");
    }



    static class Task implements Runnable{

        // public volatile Boolean buffer = true;
        public Boolean buffer = true;

        // 非volatile的共享变量
        // 
        //    当不使用volatile时,它仍然可以看到其他线程发出的更改 ?
        //        原因1:  System.out.println()会执行一段synchronized同步代码,而synchronized会让当前线程读取到最新的高速缓存值
        //        原因2: Thread.sleep结束后, 会重新从主内存获取最新值load到线程工作内存
        //     https://www.jianshu.com/p/163b4832b3e0
        // 


        public Task() {
        }

        @Override
        public void run() {
            int offset = 0;

            // 检查状态 :  
            // (1) 如果是非volatile变量, 子线程只会从工作内存获取, 不会去主内存获取; 
            // (2) 如果使用了System.out.println,  由于println的实现类PrintStream中使用了synchronized, 则会从主存刷新数据到工作内存, 保证线程可见性
            // (3) 如果使用了Thread.sleep, 当子线程休眠结束, 会重新从主内存获取数据到工作内存,从而达到刷新左右,保证线程可见性
            while (buffer){
//                try {
//                    Thread.sleep(200);


//                    // System.out.println("线程: " + Thread.currentThread().getName() + "======> " + (++offset));
//                    //log.info("线程: " + Thread.currentThread().getName() + "======> " + (++offset));
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
            // System.out.println("线程: " + Thread.currentThread().getName() + " ---> 结束");
            log.info("线程: " + Thread.currentThread().getName() + " ---> 结束");
        }
    }
}

2、Volatile + CAS, 在不加锁的情况下, 实现线程安全

/**
     * Volatile + CAS, 在不加锁的情况下, 实现线程安全
     */
    static class Volatile_AND_CAS {

        /*
         * 1、volatile只能保证线程可见性, 不能保证线程安全性;
         *   原因是i++不是原子的, 存在多个步骤:
         *      1) 从主内存load最新的i值0 ,  加载到线程A的工作内存
         *      2) 读取线程A工作内存, i=0, 然后放到操作数栈进行  temp  = 0 + 1;
         *      3) 此时线程A失去cpu时间片,  同时线程B执行了i++, 将主内存的i值变为1;
         *      4) 此时线程A获取cpu时间片, 继续执行, 由于i是volatile标识的变量, 会从主内存获取最新值 i=1;  temp  = 1;
         *      5) 由于已经执行过了tem = 0 + 1,  则最后线程A将temp = 1 save到主内存中,  出现了两次执行, i却不等于2的情况;
         */
        // private static volatile int i = 0;


        /*
         * 2、无法保证线程可见性、无法保证线程安全(操作原子性)
         */
        // private static int i = 0;


        /**
         * 3、java.util.concurrent.atomic.AtomicInteger
         *   1) 它的内部的成员变量value是由volatile修饰, 可以获取到最新值;
         *   2) do - while 死循环, 进行CAS操作(compare and set);
         *   3) do里面获取的最新值的version, 然后while里面执行操作compareAndSwapInt = (i+1, 且version = 刚才获取的值的version), 
         *   4) 如果线程A执行compareAndSwapInt操作之前, 线程B将 int volatile value修改了, version值则加1, 之后线程A执行compareAndSwapInt会失败, 因为version错误, 而会继续死循环获取最新值;
         */
        private static AtomicInteger i = new AtomicInteger(0);


        /**
         * 测试
         */
        public static void main(String[] args) {
            CountDownLatch countDownLatch = new CountDownLatch(2);

            // 启动线程1
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    // i++;
                    i.getAndIncrement();

                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                countDownLatch.countDown();
            }).start();

            // 启动线程2
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    // i++;
                    i.getAndIncrement();

                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                countDownLatch.countDown();
            }).start();

            try {
                // 主线程等待2个线程执行完毕
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("最终结果: " + i);
        }
    }

你可能感兴趣的:(笔记,java)