线程安全性的可见性、原子性:synchronized、volatile

一个线程对主内存的修改可以及时的被其他线程观察到

导致共享变量在线程间不可见的原因

  • 线程交叉执行
  • 重排序结合线程交叉执行
  • 共享变量更新后的值没有在工作内存与主存间及时更新

可见性之synchronized

JMM关于synchronized的规定

  • 线程解锁前,必须把共享变量的最新值刷新到主内存
  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁是同一把锁)

可见性之volatile

通过加入内存屏障禁止指令重排序优化来实现

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
  • 对volatile变量读操作时,会在读操作前加入一条load屏障指令从主内存中读取共享变量
    线程安全性的可见性、原子性:synchronized、volatile_第1张图片
    线程安全性的可见性、原子性:synchronized、volatile_第2张图片

用volatile做计数操作,看是否是线程安全的

package com.mmall.concurrency.example.count;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
@NotThreadSafe
public class CountExample4 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static volatile int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
        /*
        运行结果:
         5000
        4992
        4998
        因此volatile也是线程不安全的
        原因是:假设开始时count=5,线程1和线程2同时做count++操作时,线程1和线程2都从内存中读取到了count=5,然后线程1和线程2同时对count做加一操作,然后线程1把count的结果6由工作内存存回内存,线程2也把count的结果6由工作内存存回内存,因此运行结果小于等于5000
        */
    }

    private static void add() {
        count++;
        // 1、count
        // 2、+1
        // 3、count
    }
}

运行结果:

5000
4992
4998

因此volatile也是线程不安全的
原因是:假设开始时count=5,线程1和线程2同时做count++操作时,线程1和线程2都从内存中读取到了count=5,然后线程1和线程2同时对count做加一操作,然后线程1把count的结果6由工作内存存回内存,线程2也把count的结果6由工作内存存回内存,因此运行结果小于等于5000。

volatile使用场景

volatile适合使用在状态标识的场景中,如下实例:(volatile还可以用于检查2次的场景中)

volatile boolean inited = false;//全局变量

//线程1:
context = loadContext();
inited= true;

// 线程2:
while( !inited ){
    sleep();
}
doSomethingWithConfig(context)

理解volatile的可见性:实例

import java.util.Date;

/**
 * 

Volatile不是坑

* * @author wuyidi * @version v2.0.1 2018年07月05日 10:33 wuyidi */ public class VolatileTestSample { /** * 非volatile变量,当前线程如果不刷新句柄,则永远不可见 * 也就是说,o的句柄一直是在Cache中 */ //private Object o = null; /** * 当变量为volatile时,另一根线程对其改动,会立即可见 */ private volatile Object o = null; private boolean flag = false; public void methodA() { while (true) { if (o != null && flag == false) { System.out.println("Handler o detected changed."+new Date()); flag = true; System.exit(0); } } } public void methodB() { o = new Object(); System.out.println("Handler o changed!"+new Date()); } public static void main(String[] args) { System.out.println("当前时间是:"+new Date()); VolatileTestSample volatileTestSample = new VolatileTestSample(); Thread threadHandlerListener = new Thread(new Runnable() { @Override public void run() { volatileTestSample.methodA(); } }); threadHandlerListener.start(); try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } volatileTestSample.methodB(); } }

运行结果:
线程安全性的可见性、原子性:synchronized、volatile_第3张图片

线程安全性的可见性、原子性:synchronized、volatile_第4张图片下面内容转自:
https://www.jianshu.com/p/895950290179

原子性 - Synchronize

  • 修饰一个代码块:作用区域是调用方法的对象 synchronized (this)不同对象之间互不影响
  • 修饰一个方法:作用区域是调用方法的对象不同对象之间互不影响
  • 修饰一个类synchronized (SynchronizedTest2.class)作用区域是是所有对象
  • 修饰一个静态方法作用区域是是所有对象

代码示例
Synchronize修饰一个代码块和修饰一个方法,表现一样

package com.mmall.concurrency.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
/*
Synchronize修饰一个代码块和修饰一个方法,表现一样
* */
public class Synchronized Test1 {
    // 修饰一个代码块:作用区域是调用方法的对象,不同对象之间互不影响
    public synchronized void test1(int j) {
        for (int i = 0; i < 10; i++) {
            System.out.println("test1() " + j + " " + i);
        }
    }

    // 修饰一个方法:作用区域是调用方法的对象,不同对象之间互不影响
    // 如果此类是父类,子类要调用父类的时候,是不包含synchronized的,原因是synchronized不是方法声明的一部分。  如果子类也需要synchronized的话,需要声明synchronized。
    public void test2(int j) {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.println("test2() " + j + " " + i);
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedTest1 example1 = new SynchronizedTest1();
        SynchronizedTest1 example2 = new SynchronizedTest1();
        ExecutorService executorService = Executors.newCachedThreadPool();

        //2个对象调用方法1
        executorService.execute(() -> {
            // 运行结果是:test1 1和test1 2从0-9交叉出现
            example1.test1(1);
        });
        executorService.execute(() -> {
            example1.test1(2);
        });

        //2个对象调用方法2
        executorService.execute(() -> {
            // 运行结果也是:test1 1和test1 2从0-9交叉出现
            example1.test2(1);
        });
        executorService.execute(() -> {
            example2.test2(2);
        });
    }
}

运行结果:test1 1和test1 2从0-9交叉出现

test2() 1 0
test1() 2 0
test2() 1 1
test2() 1 2
test1() 2 1
test2() 1 3
test1() 2 2
test2() 1 4
test1() 2 3
test1() 2 4
test2() 1 5
test1() 2 5
test2() 1 6
test1() 2 6
test2() 1 7
test1() 2 7
test2() 1 8
test1() 2 8
test2() 1 9
test1() 2 9
test2() 2 0
test2() 2 1
test2() 2 2
test2() 2 3
test2() 2 4
test2() 2 5
test2() 2 6
test2() 2 7
test2() 2 8
test2() 2 9
test1() 1 0
test1() 1 1
test1() 1 2
test1() 1 3
test1() 1 4
test1() 1 5
test1() 1 6
test1() 1 7
test1() 1 8
test1() 1 9

Synchronize修饰一个和修饰一个静态方法,表现一样

package com.mmall.concurrency.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
/*
Synchronize修饰一个类和修饰一个静态方法,表现一样
* */
public class SynchronizedTest2 {
    // 修饰一个类:作用区域是是所有对象
    public static void test1(int j) {
        synchronized (SynchronizedTest2.class) {
            for (int i = 0; i < 10; i++) {
                System.out.println("test1() " + j + " " + i);
            }
        }
    }

    // 修饰一个静态方法:作用区域是是所有对象
    public static synchronized void test2(int j) {
        for (int i = 0; i < 10; i++) {
            System.out.println("test2() " + j + " " + i);
        }
    }

    public static void main(String[] args) {
        SynchronizedTest2 example1 = new SynchronizedTest2();
        SynchronizedTest2 example2 = new SynchronizedTest2();
        ExecutorService executorService = Executors.newCachedThreadPool();

        //2个对象调用方法2
//        executorService.execute(() -> {
//            // 运行结果是:test1 1和test1 2分别从0-9出现(不交叉)
//            example1.test2(1);
//        });
//        executorService.execute(() -> {
//            example2.test2(2);
//        });

        //2个对象调用方法1
        executorService.execute(() -> {
            // 运行结果也是:test1 1和test1 2分别从0-9出现(不交叉)
            example1.test1(1);
        });
        executorService.execute(() -> {
            example2.test1(2);
        });
    }
}

运行结果:test1 1和test1 2分别从0-9出现(不交叉)

test1() 1 0
test1() 1 1
test1() 1 2
test1() 1 3
test1() 1 4
test1() 1 5
test1() 1 6
test1() 1 7
test1() 1 8
test1() 1 9
test1() 2 0
test1() 2 1
test1() 2 2
test1() 2 3
test1() 2 4
test1() 2 5
test1() 2 6
test1() 2 7
test1() 2 8
test1() 2 9

Synchronize修饰一个方法时,如果此类是父类,子类要调用父类的时候,是不包含synchronized的,原因是synchronized不是方法声明的一部分。 如果子类也需要synchronized的话,需要声明synchronized。

你可能感兴趣的:(线程,并发,定时任务)