线程安全性
定义:当多个线程访问某个类或方法时,不管运行时环境采用何种调度方式或者这些进行如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类或方法是线程安全的。
线程安全性体现在三个方面:
1、原子性
提供了互斥访问,保证同一时刻只能有一个线程来对它进行操作。
2、可见性
一个线程对主内存的修改可以及时的被其他线程观察到。
3、有序性
一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。
原子性-JDK中的Atomic包
AtomicXXX:CAS、Unsafe.compareAndSwapInt
代码举例(通过使用AtomicInteger包来使之前线程不安全的计数器变成线程安全):
package com.concurrency.example.example.atomic;
import com.concurrency.example.annotations.ThreadSafe;
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;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:13:53
*/
@Slf4j
@ThreadSafe
public class AtomicInteger {
//总请求数
private static int clientTotal = 5000;
//同时并发执行的线程数
private static int threadTotal = 200;
//使用JDK中自带的线程安全的AtomicInteger类
private static java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(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);
}
//对计数器进行+1操作
private static void add(){
count.incrementAndGet();
//count.getAndIncrement();
}
}
原子包中的原子类实现线程安全的方法是通过compareAndSwapXXX的方式将传过来的值与底层(工作内存)的值相比较,如果相同执行操作,不相同取底层的值再进行比较,直到相同时使用最新的相同的值进行操作,即CAS操作。
与AtomicInteger同理,还有AtomicLong:
package com.concurrency.example.example.atomic;
import com.concurrency.example.annotations.ThreadSafe;
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;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:13:53
*/
@Slf4j
@ThreadSafe
public class AtomicLong {
//总请求数
private static int clientTotal = 5000;
//同时并发执行的线程数
private static int threadTotal = 200;
//使用JDK中自带的线程安全的AtomicInteger类
private static java.util.concurrent.atomic.AtomicLong count = new java.util.concurrent.atomic.AtomicLong(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);
}
//对计数器进行+1操作
private static void add(){
count.incrementAndGet();
//count.getAndIncrement();
}
}
JDK 1.8中,还有一个与AtomicLong很相似的类LongAdder:
package com.concurrency.example.example.atomic;
import com.concurrency.example.annotations.ThreadSafe;
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;
import java.util.concurrent.atomic.LongAdder;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:13:53
*/
@Slf4j
@ThreadSafe
public class LongAdderExample {
//总请求数
private static int clientTotal = 5000;
//同时并发执行的线程数
private static int threadTotal = 200;
//使用JDK中自带的线程安全的LongAdded类
private static LongAdder count = new LongAdder();
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);
}
//对计数器进行+1操作
private static void add(){
count.increment();
}
}
AtomicLong与LongAdder的区别:
AtomicLong的实现原理是在一个死循环内多次比较并最终修改,竞争不激烈时这种方式很快就可以修改成功,但当竞争比较激烈时,循环会执行多次(不停地取底层的值与传的值比较),性能会急剧下降。
JVM允许将Long及Double的64位的读、写操作拆分成两个32位的操作,LongAdder操作的核心是将热点数据分离,比如说可以将AtomicLong的内部核心数据Value分离成一个数组,每个线程访问时通过Hash等算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,这样热点就进行了有效的分离并提高了并行度。LongAdder相当于是在AtomicLong的基础上,将单点的更新压力分散到多点上,再低并发时,通过对base的直接更新可以很好的保证与AtomicLong的性能基本一致,高并发时通过分散的功能提高性能。
LongAdder的缺点:在同期时,如果有并发更新,可能导致统计的数据有误差。
在使用时,高并发场景使用LongAdder可提高性能,在序列号生成等要求准确性的场景时全局唯一的AtomicLong才是正确的选择。
JDK提供的Atomic包下的类:
AtomicReference类:
package com.concurrency.example.example.atomic;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:15:10
*/
@Slf4j
@ThreadSafe
public class AtomicReferenceExample {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0,2); //更新成2
count.compareAndSet(0,1); //不执行
count.compareAndSet(1,3); //不执行
count.compareAndSet(2,4); //更新成4
count.compareAndSet(3,5); //不执行,因此结果为上一步的4
log.info("count:{}",count);
}
}
AtomicIntegerFieldUpdater的作用与AtomicReference相似,但是是原子性的操作某一个类中的某个字段(字段不能是静态(static)的且必须被valitile修饰):
package com.concurrency.example.example.atomic;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:15:15
*/
@Slf4j
@ThreadSafe
public class AtomicIntegerFieldUpdaterExample {
//count传到newUpdater的第二个参数,必须加volatile且不能是静态
@Getter
public volatile int count = 100;
//newUpdater的第一个参数为传的类,第二个参数为要操作的字段名
private static AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterExample> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdaterExample.class,"count");
public static void main(String[] args) {
AtomicIntegerFieldUpdaterExample example = new AtomicIntegerFieldUpdaterExample();
if (updater.compareAndSet(example,100,120)){
log.info("update success1,{}",example.getCount());
}
if (updater.compareAndSet(example,100,120)){
log.info("update success2,{}",example.getCount());
}
else {
log.info("update failed,{}",example.getCount());
}
}
}
AtomicStampReference类:
核心是要解决CAS的ABA问题,ABA问题即在CAS操作时,其他线程将变量的值从A改成了B然后又改成了A,本线程使用期望值A与当前变量进行比较时,认为变量的值没有变还是为A,但此时,该值已被其他线程改变过,与设计不符。
ABA问题的解决思路:每次更新一个值时,同时更新与之对应的版本号。
AtomicStampReference中的compareAndSet方法中多了一个stamp即为版本号。
AtomicLongArray(原子数组)
AtomicBoolean类:
package com.concurrency.example.example.atomic;
import com.concurrency.example.annotations.ThreadSafe;
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;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:15:36
*/
@Slf4j
@ThreadSafe
public class AtomicBooleanExample {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
//总请求数
private static int clientTotal = 5000;
//同时并发执行的线程数
private static int threadTotal = 200;
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();
test();
semaphore.release();
}
catch (Exception e){
log.error("exception:",e);
}
//计数器进行减一操作
countDownLatch.countDown();
});
}
//确保全部请求执行完
countDownLatch.await();
//关闭线程池
executorService.shutdown();
//输出计数器的值
log.info("isHappened:{}",isHappened.get());
}
//test方法
private static void test(){
if(isHappened.compareAndSet(false,true)){
log.info("execute");
}
}
}
能保证原子性的除了Atomic包下的类之外还有锁,JDK中的锁包括两种:
1、synchronized:依赖JVM实现,因此在这个关键字作用对象的作用范围内可实现原子性。
2、Lock接口及其实现类:依赖特殊的CPU指令,代码实现,代表为ReentrantLock。
synchronized作用对象:
1、修饰代码块,作用于调用的对象。
2、修饰方法,被修饰的方法被称为同步方法,作用于调用的对象。
3、修饰静态方法:作用于类的所有对象。
4、修饰类:作用于类的所有对象。
修饰代码块:
package com.concurrency.example.example.sync;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:15:50
*/
@Slf4j
@ThreadSafe
public class SynchronizedCodeblock1 {
//修饰一个代码块
public void CodeBlock(){
synchronized (this){
for (int i = 0; i<10;i++){
log.info("CodeBlock - {}",i);
}
}
}
public static void main(String[] args) {
SynchronizedCodeblock1 example = new SynchronizedCodeblock1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() ->{
example.CodeBlock();
});
executorService.execute(() ->{
example.CodeBlock();
});
}
}
运行结果:
CodeBlock - 0
CodeBlock - 1
CodeBlock - 2
CodeBlock - 3
CodeBlock - 4
CodeBlock - 5
CodeBlock - 6
CodeBlock - 7
CodeBlock - 8
CodeBlock - 9
CodeBlock - 0
CodeBlock - 1
CodeBlock - 2
CodeBlock - 3
CodeBlock - 4
CodeBlock - 5
CodeBlock - 6
CodeBlock - 7
CodeBlock - 8
CodeBlock - 9
因为同步代码块作用于调用的对象,所以,使用同一个对象example先后开启两个线程调用代码块所在的方法CodeBlock并不会导致交叉两次输出交叉输出,而是等第一个线程调用输出0-9完毕后第二个线程再调用CodeBlock方法输出0-9。
反面测试(声明两个类对象,在两个线程里分别调用测试方法):
package com.concurrency.example.example.sync;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 反面测试同步代码块作用于调用对象
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:15:50
*/
@Slf4j
@ThreadSafe
public class SynchronizedCodeblock2 {
//修饰一个代码块
public void CodeBlock(){
synchronized (this){
for (int i = 0; i<10;i++){
log.info("CodeBlock - {}",i);
}
}
}
public static void main(String[] args) {
SynchronizedCodeblock2 example1 = new SynchronizedCodeblock2();
SynchronizedCodeblock2 example2 = new SynchronizedCodeblock2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() ->{
example1.CodeBlock();
});
executorService.execute(() ->{
example2.CodeBlock();
});
}
}
输出结果(交叉执行,证明只作用于调用对象):
CodeBlock - 0
CodeBlock - 0
CodeBlock - 1
CodeBlock - 1
CodeBlock - 2
CodeBlock - 2
CodeBlock - 3
CodeBlock - 4
CodeBlock - 5
CodeBlock - 6
CodeBlock - 3
CodeBlock - 7
CodeBlock - 4
CodeBlock - 8
CodeBlock - 5
CodeBlock - 9
CodeBlock - 6
CodeBlock - 7
CodeBlock - 8
CodeBlock - 9
测试同步方法:
package com.concurrency.example.example.sync;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:18:51
*/
@Slf4j
@ThreadSafe
public class SynchronizedMethod1 {
//修饰一个方法
public synchronized void function(){
for (int i = 0; i<10;i++){
log.info("function - {}",i);
}
}
public static void main(String[] args) {
SynchronizedMethod1 synchronizedMethod1 = new SynchronizedMethod1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() ->{
synchronizedMethod1.function();
});
executorService.execute(() ->{
synchronizedMethod1.function();
});
}
}
输出结果:
function - 0
function - 1
function - 2
function - 3
function - 4
function - 5
function - 6
function - 7
function - 8
function - 9
function - 0
function - 1
function - 2
function - 3
function - 4
function - 5
function - 6
function - 7
function - 8
function - 9
反面测试(声明两个类对象并分别在两个线程里同时调用测试方法):
package com.concurrency.example.example.sync;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 反面验证同步方法作用于调用对象
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:18:51
*/
@Slf4j
@ThreadSafe
public class SynchronizedMethod2 {
//修饰一个方法
public synchronized void function(){
for (int i = 0; i<10;i++){
log.info("function - {}",i);
}
}
public static void main(String[] args) {
SynchronizedMethod2 synchronizedMethod1 = new SynchronizedMethod2();
SynchronizedMethod2 synchronizedMethod2 = new SynchronizedMethod2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() ->{
synchronizedMethod1.function();
});
executorService.execute(() ->{
synchronizedMethod2.function();
});
}
}
输出结果(无序,交叉进行):
function - 0
function - 0
function - 1
function - 1
function - 2
function - 2
function - 3
function - 3
function - 4
function - 5
function - 6
function - 7
function - 8
function - 9
function - 4
function - 5
function - 6
function - 7
function - 8
function - 9
修饰一个静态方法:
package com.concurrency.example.example.sync;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:18:59
*/
@Slf4j
@ThreadSafe
public class SynchronizedStaticMethod {
//修饰一个静态方法
public static synchronized void Staticfunction(){
for (int i = 0; i<10;i++){
log.info("SynchronizedStaticMethod - {}",i);
}
}
public static void main(String[] args) {
SynchronizedStaticMethod synchronizedStaticMethod1 = new SynchronizedStaticMethod();
SynchronizedStaticMethod synchronizedStaticMethod2 = new SynchronizedStaticMethod();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() ->{
synchronizedStaticMethod1.Staticfunction();
});
executorService.execute(() ->{
synchronizedStaticMethod2.Staticfunction();
});
}
}
输出结果(有序):
SynchronizedStaticMethod - 0
SynchronizedStaticMethod - 1
SynchronizedStaticMethod - 2
SynchronizedStaticMethod - 3
SynchronizedStaticMethod - 4
SynchronizedStaticMethod - 5
SynchronizedStaticMethod - 6
SynchronizedStaticMethod - 7
SynchronizedStaticMethod - 8
SynchronizedStaticMethod - 9
SynchronizedStaticMethod - 0
SynchronizedStaticMethod - 1
SynchronizedStaticMethod - 2
SynchronizedStaticMethod - 3
SynchronizedStaticMethod - 4
SynchronizedStaticMethod - 5
SynchronizedStaticMethod - 6
SynchronizedStaticMethod - 7
SynchronizedStaticMethod - 8
SynchronizedStaticMethod - 9
修饰一个类:
package com.concurrency.example.example.sync;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 同步类
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:19:01
*/
@Slf4j
@ThreadSafe
public class SynchronizedClass {
//修饰一个类
public static void testSynchronizedClass(){
synchronized (SynchronizedClass.class){
for (int i = 0; i<10;i++){
log.info("SynchronizedClass - {}",i);
}
}
}
public static void main(String[] args) {
SynchronizedClass synchronizedClass1 = new SynchronizedClass();
SynchronizedClass synchronizedClass2 = new SynchronizedClass();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() ->{
synchronizedClass1.testSynchronizedClass();
});
executorService.execute(() ->{
synchronizedClass2.testSynchronizedClass();
});
}
}
输出结果(有序):
SynchronizedClass - 0
SynchronizedClass - 1
SynchronizedClass - 2
SynchronizedClass - 3
SynchronizedClass - 4
SynchronizedClass - 5
SynchronizedClass - 6
SynchronizedClass - 7
SynchronizedClass - 8
SynchronizedClass - 9
SynchronizedClass - 0
SynchronizedClass - 1
SynchronizedClass - 2
SynchronizedClass - 3
SynchronizedClass - 4
SynchronizedClass - 5
SynchronizedClass - 6
SynchronizedClass - 7
SynchronizedClass - 8
SynchronizedClass - 9
总结:可见,同步代码快和同步方法作用于调用的对象,当声明多个类的对象时,同时调用每个对象其中的同步方法和或同步代码块并不能保证有序。而静态同步方法和静态类作用于整个类对象,可以保证有序。
所以对于开头的计数器,又有了一个可以保证准确性的方法,使用Synchronied关键字修饰add操作:
package com.concurrency.example.example.count;
import com.concurrency.example.annotations.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;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:13:53
*/
@Slf4j
@ThreadSafe
public class SynchronizedCountExample {
//总请求数
private static int clientTotal = 5000;
//同时并发执行的线程数
private static int threadTotal = 200;
private static 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);
}
//对计数器进行+1操作
private synchronized static void add(){
count++;
}
}
这样,计数器功能就没有问题了,每次执行的结果都是5000;
Synchronized与Lock、Atomic的对比:
synchonized:不可中断锁,适合竞争不激烈,竞争激烈时性能下降的非常快,可读性好。
Lock:可中断锁(unlock方法),多样化同步,竞争激烈时可维持常态。
Atomic:竞争激烈时能维持常态,比Lock性能好;只能同步一个值。
JMM关于Synchronized关键字的规定(保障数据准确):
1、线程解锁前,必须把共享变量的最新值刷新到主内存。
2、线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(对于内部锁Synchronized来说,加锁与解锁是同一把锁)。
2、可见性
导致共享变量在线程间不可见的原因:
1、线程交叉执行。
2、重排序结合线程交叉执行。
3、共享变量更新后的值没有在工作内存与主存间及时更新。
volatile关键字的实现原理:
通过加入内存屏障和禁止重排序优化来实现
1、对volatile变量写操作时,会在写操作后加入一条store屏障指令,将工作内存中的共享变量值刷新到主内存。
2、对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。
Synchronized与volatile区别:
Synchronized可同时保障原子性和可见性,volitile不能保障原子性,只能保障可见性。
用之前的计数器举例,在add操作上加上volatile关键字:
package com.concurrency.example.example.count;
import com.concurrency.example.annotations.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;
/**
* Created with IDEA
* author:LuXiaoWei
* Date:2018/5/5
* Time:13:53
*/
@Slf4j
@NotThreadSafe
public class VolatileCountExample {
//总请求数
private static int clientTotal = 5000;
//同时并发执行的线程数
private static int threadTotal = 200;
private 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);
}
//对计数器进行+1操作
private static void add(){
count++;
}
}
输出结果:
4997
原因是虽然保障了每个线程对count进行操作时取到的值都是最新的,但因为没有保障原子性,导致其他线程中的+1操作结果被忽略。
count++实际运行中分三步:取得count的值,在取到的值的基础上+1,将+1之后的值赋给count,所以实际运行时会出现多个线程同时对同一个count进行了+1操作,比如同时取到3000并+1,但写回count时,前后两次都是赋了3001,实际上却是加了两次,比预期结果少了1。
volatile适合作为状态标识量比如判断初始化是否完成(volatile boolean inited =false;),当一个线程里初始化完成后,将inited设为true,另一个执行具体任务的线程里的判断语句立刻就能知道以初始化完成并执行相应的操作。
3、有序性
定义:JMM中,允许编译器和处理器对指令进行重排序,但是重排序虽然不会影响单线程程序的执行,却会影响多线程程序的执行。
volatile可在一定程度上保障有序性、synchronized、Lock因为强制让某一个时刻只能有一个线程执行对应代码也间接保障了有序性。