Java并发编程 (四) 线程安全性

时间:2023-03-09 05:35:17
Java并发编程 (四) 线程安全性

个人博客网:https://wushaopei.github.io/    (你想要这里多有)

一、线程安全性-原子性-atomic-1

1、线程安全性

定义: 当某个线程访问某个类时,不管运行时环境采用何种调度方式或者这些锦城南将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的

特点

原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作

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

有序性:一个线程观察其他线程中的指令执行顺序,由于指令 重排序的存在,该观察结果一般杂乱无序

2、原子性 - Atomic包

Atomic包下的类可以 实现线程并发的原子性,主要的类有:

① AtomicXXX : CAS 、Unsafe.compareAndSwapInt

② AtomicLong、LongAdder

3、AtomicXXX 案例实测

使用Atomic确保线程并发的原子性,代码演示:

package com.mmall.concurrency.example.count;

import com.mmall.concurrency.annoations.NoThreadSafe;
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.AtomicInteger; /**
* @ClassName CountExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/30 17:10
* @Version 1.0
*/
@Slf4j
@NoThreadSafe
public class CountExample2 { //请求总数
public static int clientTotal = 5000; // 同时并发执行的线程数
public static int threadTotal = 200; public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException {
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.get());
} private static void add(){
count.incrementAndGet();
}
}

Java并发编程 (四) 线程安全性

执行结果:

17:14:59.658 [main] INFO com.mmall.concurrency.example.count.CountExample2 - count:5000

Process finished with exit code 0

Java并发编程 (四) 线程安全性

由结果可知,无论执行多少次,count都是5000的值,说明了Atomic可以确保线程的安全性。

原理解析:

这里对add()方法所做的修改,即将count++改为count.incrementAndGet()是保证线程安全的主要原因!

源码进行分析:

点击计入incrementAndGet( )类中:

Java并发编程 (四) 线程安全性Java并发编程 (四) 线程安全性

由源码可知,incrementAndGet()方法里使用了一个叫unsafe的类,该类实现了一个getAndAddInt()的方法,

Java并发编程 (四) 线程安全性Java并发编程 (四) 线程安全性

进入getAndAddInt方法进一步分析:

Java并发编程 (四) 线程安全性Java并发编程 (四) 线程安全性

由源码可知,getAndAddInt方法内部调用了do-while循环结构,在while循环条件中,调用了compareAndSwapInt()方法,这是一个重点,进一步查看该方法:

Java并发编程 (四) 线程安全性Java并发编程 (四) 线程安全性

由图中源码可知,该方法被native所修饰,这是代表是java底层的方法,而不是通过java去实现的。

分析getAndAddInt()方法源码:

Java并发编程 (四) 线程安全性Java并发编程 (四) 线程安全性

这里传过来的var1对象值相当于案例中的 count 值,其中的var2 值则是当前的值。

compareAndSwapInt()方法的作用是,在count值时,var2=2和var5=2的值相同时,将他的值更新为后面的var5+var4的值。这里的var5是从底层取的。

安全原则示例:当var2=2,var4=4,进入do作用域中,调用this.getIntVolatile()方法进行修改,然后进入while循环,此时会对var2var5进行判断,var2是上一次修改后被返回的校验字段,而var5则是对应保存在底层的校验字段,单线程执行时,每一次var5都会在执行compareAndSwapInt()方法后进行变更,即每一次var4+var5都会++;

重点:当程序并发请求,当前线程执行var2=2时,有其他线程抢夺CPU执行权执行了一次compareAndSwapInt()方法后,当前线程再获得锁进入执行compareAndSwapInt()方法后var5发生改变,被替换为var4+var5(2+1=3)的值,var2=2和var5=3(此时底层取的是3)是不同的值,校验不通过。此时var2的值会重新从 count中取值,当var2取值为3后,再与var5=3进行比较,比较通过后,再对var4+var5进行执行,结果为3+1=4 ; 逻辑依次循环判断,不断对底层值进行覆盖,从而保证执行线程的安全。

4、AtomicLong、LongAdder 案例实测

1)AtomicLong代码实例:

package com.mmall.concurrency.example.atomic;

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.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; /**
* @ClassName AtomicExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/31 10:23
* @Version 1.0
*/
@Slf4j
@ThreadSafe
public class AtomicExample2 { //请求总数
public static int clientTotal = 5000; // 同时并发执行的线程数
public static int threadTotal = 200; public static AtomicLong count = new AtomicLong(0); public static void main(String[] args) throws InterruptedException {
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);
} private static void add(){
count.incrementAndGet();
} }

Java并发编程 (四) 线程安全性

执行结果:

10:30:44.260 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample2 - count:5000

Process finished with exit code 0

Java并发编程 (四) 线程安全性

2)LongAdder代码实例:

package com.mmall.concurrency.example.atomic;

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.AtomicLong;
import java.util.concurrent.atomic.LongAdder; /**
* @ClassName AtomicExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/31 10:23
* @Version 1.0
*/
@Slf4j
@ThreadSafe
public class AtomicExample3 { //请求总数
public static int clientTotal = 5000; // 同时并发执行的线程数
public static int threadTotal = 200; public static LongAdder count = new LongAdder(); public static void main(String[] args) throws InterruptedException {
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);
} private static void add(){
count.increment();
} }

Java并发编程 (四) 线程安全性

执行结果:

10:32:09.818 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample3 - count:5000

Process finished with exit code 0

Java并发编程 (四) 线程安全性

知识点:

对于普通类型的long、double变量,JVM允许将64位的读操作或写操作拆成两个32位的操作。

LongAdder的实现是基于什么思想?

LongAdder的核心是将核心数据分离;比如LongAdder内部的value分离成为一个数组,每个线程访问时通过hash等算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,其中热点单元的数据会被分离为多个单元的shell,每个shell独自维护内部的值,当前对象的值由所有shell累计而成。这样的话,热点就进行了有效的分离并提高了并行度,这样一来LongAdder相当于在AtomicLong的基础上将单点的更新压力分散到各个节点上,在低并发的时候通过对base的值进行更新可以很好的保障和Atomic的性能基本一致,而在高并发的时候则通过分散提高了性能。

注意:实际在处理并发更新统计时,优先使用LongAdder。并发要求低时使用AtomicLong,效率高。

二、线程安全性-原子性-atomic-2

1、AtomicReference代码实例

package com.mmall.concurrency.example.atomic;

/**
* @ClassName AtomicExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/31 10:23
* @Version 1.0
*/
@Slf4j
@ThreadSafe
public class AtomicExample4 { private static AtomicReference<Integer> count = new AtomicReference<>(0); public static void main(String[] args) {
count.compareAndSet(0,2); // 2
count.compareAndSet(0,1); // no
count.compareAndSet(1,3); // no
count.compareAndSet(2,4); // 4
count.compareAndSet(3,5); // no
log.info("count:{}",count.get());
} }

Java并发编程 (四) 线程安全性

执行结果:

10:55:53.811 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample4 - count:4

Process finished with exit code 0

Java并发编程 (四) 线程安全性

2、AtomicIntegerFieldUpdater代码实例

package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReference; /**
* @ClassName AtomicExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/31 10:23
* @Version 1.0
*/
@Slf4j
@ThreadSafe
public class AtomicExample5 { @Getter
public volatile int count = 100; private static AtomicExample5 example5 = new AtomicExample5(); private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count"); public static void main(String[] args) {
if (updater.compareAndSet(example5,100,120)){
log.info("update success 1, {}",example5.getCount());
}
if (updater.compareAndSet(example5,100,120)){
log.info("update success 2, {}",example5.getCount());
}else {
log.info("update failed,{}",example5.getCount());
}
} }

Java并发编程 (四) 线程安全性

执行结果:

11:02:48.170 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update success 1, 120
11:02:48.177 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update failed,120 Process finished with exit code 0

Java并发编程 (四) 线程安全性

分析执行结果:

AtomicIntegerFieldUpdater的核心是根据原子性去更新某个类的实例,当前案例修改的是AtomicExample5 类的example5 的某一个字段 count;这里要求count 必须被volatile进行修饰;执行第一个compareAndSet方法时,compareAndSet方法中是将第二个变量100修改为第三个变量120,此时example5通过getCount()更新成了120;再执行第二个compareAndSet方法时,example5与100校验不相同,不执行当前compareAndSet方法,所以没有日志打印,流程进入到else中,打印 “update failed ,120 ”。

3、AtomicStampReference:CAS的ABA问题

关于ABA问题:

ABA问题是指在CAS操作的时候,其他线程将变量的值A改成了B,但是又改回了A,本线程使用期望值A与当前变量进行比较的时候,发现A变量没有变,于是CAS就将A值进行了交换操作,这个时候,其实该值已经被其他线程改变过,这与设计思想是不符合的。

因此,ABA问题的解决思路是:每次变量更新的时候,把变量的版本号加一,那么之前那个A改成B,再改成A,就会变成A变成1版本,改成B变成2版本,再改回A变成3版本,这个时候只要变量被某一个线程修改过,该变量对应的版本号就会发生过递增变化。从而解决了ABA问题。

package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.Getter;
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;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLong; /**
* @ClassName AtomicExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/31 10:23
* @Version 1.0
*/
@Slf4j
@ThreadSafe
public class AtomicExample6 { //请求总数
public static int clientTotal = 5000; // 同时并发执行的线程数
public static int threadTotal = 200; public static AtomicBoolean isHappened = new AtomicBoolean(false); public static void main(String[] args) throws InterruptedException {
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());
} private static void test(){
if (isHappened.compareAndSet(false,true)){
log.info("execute");
}
} }

Java并发编程 (四) 线程安全性

执行结果:

11:25:55.399 [pool-1-thread-1] INFO com.mmall.concurrency.example.atomic.AtomicExample6 - execute
11:25:55.429 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample6 - isHappened:true

Java并发编程 (四) 线程安全性

结果分析:为什么声明了5000次线程,却只执行了一次?

因为AtomicBoolean的compareAndSet具有原子性,它可以保证从false变成true只有一次,之后的4999次在compareAndSet的判断当前值为true后,不会再执行变成true的操作。

使用场景: 让某一段代码只执行一次,而不会发生重复。

三、线程安全性-原子性-synchronized

原子性的实现是 加锁;而加锁的方式有两种:synchronized 、Lock

区别:

synchronized : 依赖JVM

Lock : 依赖特殊的CPU指令,代码实现,ReentrantLock

1、原子性 - synchronized

1)synchronized  的使用:

  • 修饰代码块: 大括号括起来的代码,作用于调用的对象
  • 修饰方法:整个方法,作用于调用的对象
  • 修饰静态方法:整个静态方法,作用于所有对象
  • 修饰类:括号括起来的部分,作用于所有对象

同步操作:

修饰代码块也叫同步代码块

修饰方法也叫同步方法

synchronized关键字代码演示:

2、同一对象的两个线程调用同步代码块:

package com.mmall.concurrency.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* @ClassName SynchronizedExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/31 14:11
* @Version 1.0
*/
@Slf4j
public class SynchronizedExample1 { //修饰一个代码块
//作用的对象是调用的对象
public void test1(){
synchronized (this){
for (int i = 0 ; i < 10 ; i ++){
log.info("test1 - {}",i);
}
}
} //修饰一个方法
public synchronized void test2(){
for (int i = 0 ; i < 10 ; i ++){
log.info("test - {}",i);
}
} public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
//在不使用线程池的情况下,本身就是同步执行,synchronized修饰意义不大,
//使用线程池后,存在两条线程,同时执行代码,在没有synchronized修饰时是并发异步的执行,交替穿插打印结果
//使用synchronized修饰后,由于锁定的是当前对象example1,所以只有第一个线程执行完,第二个线程才会执行
executorService.execute(()->{
example1.test2();
});
executorService.execute(()->{
example1.test2();
});
}
}

Java并发编程 (四) 线程安全性

执行test1()同步方法,结果:

14:21:22.164 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 0
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 1
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 2
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 3
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 4
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 5
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 6
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 7
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 8
14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 9
14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 0
14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 1
14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 2
14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 3
14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 4
14:21:22.168 [pool-1-thread-2] INFO

Java并发编程 (四) 线程安全性

由结果可知,先执行完线程1的test1-0到test1-9,再执行线程2的test0到test9

执行test2()同步方法

    executorService.execute(()->{
example1.test2();
});
executorService.execute(()->{
example1.test2();
});

Java并发编程 (四) 线程安全性

运行test2()方法,打印结果:

14:24:17.003 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 0
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 1
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 2
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 3
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 4
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 5
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 6
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 7
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 8
14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 9
14:24:17.008 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 0
14:24:17.008 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 1
14:24:17.008 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 2

Java并发编程 (四) 线程安全性

由结果可知,先执行完线程1的test2-0到test2-9,再执行线程2的test2-0到test2-9

3、不同对象调用同步代码块:

public void test1(int j){
synchronized (this){
for (int i = 0 ; i < 10 ; i ++){
log.info("test1 {} - {}",j,i);
}
}
} public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(()->{
example1.test1(1);
});
executorService.execute(()->{
example2.test1(2);
});
}

Java并发编程 (四) 线程安全性

执行结果:

14:31:42.315 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 0
14:31:42.315 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 0
14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 1
14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 1
14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 2
14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 3
14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 2
14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 4
14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 3
14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 5
14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 4
14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 5
14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 6
14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 6
14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 7
14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 7
14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 8
14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 8
14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 9
14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 9

Java并发编程 (四) 线程安全性

由结果可知,两个不同的对象调用同步代码块时,它们的结果是互相不影响的。

执行test2()同步方法,

//修饰一个方法
public synchronized void test2(int j){
for (int i = 0 ; i < 10 ; i ++){
log.info("test2 {} - {}",j,i);
}
} executorService.execute(()->{
example1.test2(1);
});
executorService.execute(()->{
example2.test2(2);
});

Java并发编程 (四) 线程安全性

执行结果:

14:37:14.704 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 0
14:37:14.704 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 0
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 1
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 1
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 2
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 2
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 3
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 3
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 4
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 4
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 5
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 5
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 6
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 6
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 7
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 8
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 7
14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 9
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 8
14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 9

Java并发编程 (四) 线程安全性

有结果可知,两个不同的对象调用同步方法时,它们的结果是互相不影响的。

4、静态同步方法、静态同步代码块:

package com.mmall.concurrency.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* @ClassName SynchronizedExample1
* @Description TODO
* @Author wushaopei
* @Date 2019/10/31 14:11
* @Version 1.0
*/
@Slf4j
public class SynchronizedExample2 { //修饰一个代码块
//作用的对象是调用的对象
public static void test1(int j){
synchronized (SynchronizedExample2.class){
for (int i = 0 ; i < 10 ; i ++){
log.info("test1 {} - {}",j,i);
}
}
} //修饰一个静态方法
public static synchronized void test2(int j){
for (int i = 0 ; i < 10 ; i ++){
log.info("test2 {} - {}",j,i);
}
} public static void main(String[] args) {
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
example1.test2(1);
});
executorService.execute(()->{
example2.test2(2);
});
}
}

Java并发编程 (四) 线程安全性

静态同步代码块测试结果:

14:46:52.930 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 0
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 1
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 2
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 3
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 4
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 5
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 6
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 7
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 8
14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 9
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 0
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 1
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 2
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 3
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 4
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 5
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 6
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 7
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 8
14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 9

Java并发编程 (四) 线程安全性

静态同步方法测试结果:

14:43:19.042 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 0
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 1
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 2
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 3
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 4
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 5
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 6
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 7
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 8
14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 9
14:43:19.046 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 0
14:43:19.046 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 1
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 2
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 3
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 4
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 5
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 6
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 7
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 8
14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 9 Process finished with exit code 0

Java并发编程 (四) 线程安全性

5、对并发执行增加操作线程使用synchronized修饰:

@Slf4j
@ThreadSafe
public class CountExample3 { //请求总数
public static int clientTotal = 5000; // 同时并发执行的线程数
public static int threadTotal = 200; public static int count = 0; public static void main(String[] args) throws InterruptedException {
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);
} //同步方法锁 --- synchronized
private synchronized static void add(){
count ++;
}
}

Java并发编程 (四) 线程安全性

14:49:18.636 [main] INFO com.mmall.concurrency.example.count.CountExample3 - count:5000

Process finished with exit code 0

Java并发编程 (四) 线程安全性

由执行结果可知,count符合执行的要求,说明当前程序是线程安全的

6、原子性  - 对比

synchronized : 不可中断锁,适合竞争不激烈,可读性好

Lock : 可中断锁,多样化同步,竞争激烈时能维持常态

Atomic : 竞争激烈时能维持常态,比Lock性能好;只能同步一个值

四、线程安全性-可见性

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

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

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

保证线程可见性的方法 —— synchronized 、volatile

2、JMM关于synchronized的两条规定

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

3、可见性 - volatile

volatile实现内存可见性的原理通过加入内存屏障和禁止重排序优化来实现

Java并发编程 (四) 线程安全性Java并发编程 (四) 线程安全性

① 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存

Java并发编程 (四) 线程安全性Java并发编程 (四) 线程安全性

② 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量

4、Volatile 代码示例

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 InterruptedException {
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);
} private static void add(){
count ++;
}
}

Java并发编程 (四) 线程安全性

执行结果:

15:04:05.623 [main] INFO com.mmall.concurrency.example.count.CountExample4 - count:4922

Process finished with exit code 0

Java并发编程 (四) 线程安全性

由结果可知,count小于5000,说明当前线程依旧是不安全的

结果说明了直接使用volatile做加操作,是线程不安全的;同时也说明了volatile不具有原子性。

5、Volatile适合什么样的场景?

使用volatile必须具备两个条件:

  1. 对变量的写操作不依赖于当前值;
  2. 该变量没有包含在具有其他变量的独变量的式子中。

6、volatile代码片段分析:

 volatile boolean inited = false;

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

Java并发编程 (四) 线程安全性

分析:当线程1初始化完成后,inited变为true,此时线程2的while会!Inited变为false,知道了线程1初始化已经完成,可以继续从主内存读取变量值并执行相应的业务。这时候线程2去使用初始化好的context就不会出问题了。

五、线程安全性-有序性与总结

1、有序性

定义: Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性;

实现方式: volatile 、synchronized、Lock

2、有序性 - happens - before原则

程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;

锁定规则: 一个unlOCK操作先行发生于后面对同一个锁的lock操作

volatile变量你规则: 对一个变量的写操作先行发生于后面对这个变量的读操作

传递规则:如果操作A线性发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C

线程启动规则: Thread对象的start()方法先行于发生于此线程的每一个动作

线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束 、 Thread.isAlive()的返回值手段检测到线程已经终止执行

对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始

小结 :

原子性 : Atomic包、CAS算法、synchronized、Lock

可见性:synchronized 、volatile

有序性:happens-before