并发编程三大特性
1.1 原子性
原子性的定义:原子性是指一个操作(多条指令)是不可分割的。 在一个线程在执行某一段指令时,其他的线程如果也想执行,需要等待前一个线程执行完毕后才能执行。
原子性可以解决线程安全问题。在多个线程在同时对一个共享资源(共享变量)进行操作时,出现的问题。
在Java端保证原子性一般有三种方式:
CAS、synchronized、ReentrantLock
代码实操
java
public class CompanyTest {
private static int count;
// 如果方法不追加synchronized,会导致200次++操作结束后,结果不是200
// 如果方法追加上了synchronized,200次++的操作结束后,结果就是预期的200了。
@SneakyThrows
public static synchronized void increment() {
TimeUnit.MILLISECONDS.sleep(100);
count++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
1.2 可见性
可见性的问题

可见性问题如何解决。需要提到JMM(Java内存模型)

JMM,用于屏蔽掉硬件和各个操作系统之间内存访问的差异。
而在Java代码层面上,如果要实现这种可见性,有几种方式:
volatile关键字,synchronized,Lock锁(本质也是volatile)
代码实现,认证可见性问题的存在
ini
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(flag){
}
System.out.println("t1线程结束!");
});
t1.start();
Thread.sleep(100);
flag = false;
System.out.println("main线程将flag改为false");
}
基于volatile的方式,来实现可见性的效果
ini
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(flag){
}
System.out.println("t1线程结束!");
});
t1.start();
Thread.sleep(100);
flag = false;
System.out.println("main线程将flag改为false");
}
基于synchronized实现内存可见性
ini
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(flag){
// 这里的println操作中,涉及到了synchronized操作,间接实现了可见性
System.out.println(1);
}
System.out.println("t1线程结束!");
});
t1.start();
Thread.sleep(100);
flag = false;
System.out.println("main线程将flag改为false");
}
基于lock锁的方式,实现内存可见性,本质其实是修改volatile修饰的数据实现的
java
private static boolean flag = true;
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(flag){
lock.lock();
lock.unlock();
}
System.out.println("t1线程结束!");
});
t1.start();
Thread.sleep(100);
flag = false;
System.out.println("main线程将flag改为false");
}
lock锁的本质是基于对volatile修饰的变量做读写实现的,咱们可以自己来实现这个效果
ini
private static boolean flag = true;
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(flag){
count++;
}
System.out.println("t1线程结束!");
});
t1.start();
Thread.sleep(100);
flag = false;
System.out.println("main线程将flag改为false");
}
1.3 有序性
在Java中,.java文件在被编译后,会生成多条指令,这些指令需要CPU去执行。CPU在执行这些指令时,就会在一定程度上对这些指令做重新排序,在不影响最终结果的前提下,对指令做一些重新排序。
在Java做编译时,JVM内部也提供了一个优化,JIT,在JIT优化时,也会在一定程度上对指令做重新排序。
搞个Java程序,验证一下指令重排序的存在。
ini
static int a, b, x, y;
/**
正常情况下,x和y应该是有三种结果,11,10,01这种情况
但是咱们判断的是x和y同时都是0的情况,如果出现这种情况,说明t1或者t2的两个操作,可能出现了指令重排序
*/
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
a = 0;
b = 0;
x = 0;
y = 0;
Thread t1 = new Thread(() -> {
a = 1;
x = b;
});
Thread t2 = new Thread(() -> {
b = 1;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
if(x == 0 && y == 0){
System.out.println("第" + i + "次循环中,x = " + x + ",y = " + y);
}
}
}
指令重排序出现的问题。单例模式。懒汉式。
如下代码,这种单纯基于DCL实现线程安全的懒汉模式时,会出现一个问题。
new对象时,存在三个操作
- 开辟空间
- 初始化属性
- 引用赋值
这三个操作是可能出现指令重排序的情况,可能就会造成,test != null,但是还没用执行第二步的初始化属性,导致其他线程拿着一个还未初始化完成的,或者说一个半成品对象去操作,这会带来一些线程安全的问题。
csharp
public class CompanyTest {
private static CompanyTest test;
private CompanyTest(){}
// DCL Double Check Lock
public static CompanyTest getInstance(){
if(test == null) {
synchronized (CompanyTest.class) {
if(test == null) {
test = new CompanyTest();
}
}
}
return test;
}
}
在Java中,解决指令重排的方式很简单,可以给涉及到指令重排的属性追加上一个关键字 volatile
csharp
public class CompanyTest {
// 追加volatile关键字,确保操作test属性时,不会出现指令重排的问题,保证了有序性。
private static volatile CompanyTest test;
private CompanyTest(){}
// DCL Double Check Lock
public static CompanyTest getInstance(){
if(test == null) {
synchronized (CompanyTest.class) {
if(test == null) {
test = new CompanyTest();
}
}
}
return test;
}
}