并发问题的源头–原子性,可见性,有序性
/**
* 原子性验证
*/
public class AtomicTest {
public static int count = 0;
public static void methodSleep() {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//不是一个原子性操作getstatic iadd putstatic
count++;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(Demo1::methodSleep).start();
System.out.println("-->"+count);
}
Thread.sleep(4000);// 保证上面的线程都执行完
System.out.println(count);
}
}
// 控制台终端使用javap -v xxx.class 命令
public static void methodSleep();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=0
0: ldc2_w #2 // long 2l
3: invokestatic #4 // Method java/lang/Thread.sleep:(J)V
6: goto 14
9: astore_0
10: aload_0
11: invokevirtual #6 // Method java/lang/InterruptedException.printStackTrace:()V
14: getstatic #7 // Field count:I
17: iconst_1
18: iadd
19: putstatic #7 // Field count:I
22: return
发现count++不是一个原子性操作 实际分为三步;所以在多线程的操作之下,都有可能取到同一个值,或者加了但是还未putstatic情况发生
有序性:Java程序源码 再被编译器编译之后的 可能会发生指令的重排序
可见性:指在硬件层面的造成的不同情况下的不可见性
Java的内存模型Java Memory Model
点击深入理解JMM 真正理解了JMM就学习JVM事半功倍
同步关键字synchronized
- 对于普通同方法 锁的是当前实例对象
- 对于静态同步方法 锁的是当前类的class对象
- 对于同步代码块 锁的是synchronized括号里面的对象
public class SynchronizedTest {
//对象锁:同一个对象内有效
public synchronized void thisTest(){}
//对于静态的方法->类级别的锁 SynchronizedTest.class
public synchronized static void classTest(){}
public void thisTest1(){
//TODO
synchronized (this){}
//TODO
}
public void classTest1(){
//TODO
synchronized (SynchronizedTest.class){}
//TODO
}
/**
* synchronized:底层实现原理-->monitorenter(获得锁) monitorexit(释放锁)
*/
public static void main(String[] args) {
SynchronizedTest st = new SynchronizedTest();
SynchronizedTest st1 = new SynchronizedTest();
new Thread(st::thisTest).start();
new Thread(st1::thisTest).start();
new Thread(st::classTest1).start();
new Thread(SynchronizedTest::classTest).start();
}
}
//
public void classTest1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // class threadsecurity/SynchronizedTest
2: dup
3: astore_1
4: monitorenter // 加了synchronized之后 会有
5: aload_1
6: monitorexit // 加了synchronized之后 会有
7: goto 15
10: astore_2
11: aload_1
12: monitorexit // 如果异常了也会释放锁
13: aload_2
14: athrow
15: return
JDK1.6以后对synchronized进行了优化
- 自适应自旋锁
- 偏向锁 轻量级锁
- 锁消除 锁粗化
volatile
- 怎么保证可见性?
加了volatile之后 对于的变量汇编指令会有一个lock:使得当前处理器的缓存写入内存 同时其他的处理器的缓存无效
触发了禁用缓存
- volatile怎么解决有序性问题?
/**
* CPU层面的内存屏障: 解决了指令重排序的问题
* store barrier, 写屏障 保证在该指令之后的写指令 后执行{先把该指令之前的 写操作写到CPU缓存里面}
* load barrier, 读屏障 保证在该指令之后的读指令 后执行{先把该指令之前的 读操作先读出来}
* full barrier, 集合了前面两个指令的功能
*/
/**
* 在Java层面:4种屏障
* LoadLoad Barrier:
* StoreStore Barrier:
* LoadStore Barrier:
* StoreLoad Barrier:
* 本质上 volatile 实际上是通过内存屏障来防止指令重排序
* 以及禁止CPU高速缓存来解决可见性问题
*/
final
- final是Java的关键字,可以声明成员变量,方法,类;一旦将这个引用声明final就不能改变这个引用了
/**
* @Description: 写final域的重排序规则
*/
public class FinalDemo {
int i;
final int j;
static FinalDemo finalDemo;
public FinalDemo(){
i=1;
j=2;
}
public static void write(){
finalDemo = new FinalDemo();
}
public static void read(){
FinalDemo obj = finalDemo;
System.out.println(obj.i);
System.out.println(obj.j);
}
public static void main(String[] args) {
Thread thread = new Thread(()->{
FinalDemo::write();
});
Thread thread1 = new Thread(()->{
FinalDemo::read();
});
//可能会出现j=2;但是i=0的情况
//在指令重排序之后 会出现普通变量i没来得及 被初始化,但是final修饰的j一定会在前面被初始化
thread.start();
thread1.start();
/**
* final与线程安全有什么关系? 对于final域,编译器和处理器要遵循两个重排序规则
* 1.在构造函数内对于一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量, 这两个操作之间不能重排序-->保证先写再读
* 2.初次读一个包含final域的对象的引用, 与随后初次读这个final域,这两个操作之间不能重排序-->保证两个线程之间不能重排序,按照先后顺序来
*
* 前提 是不能出现"逃逸"的情况-->就是不能在构造函数里面 初始化实例 object = this;-->指令重排序大于 初始化变量的指令,会是的final变量
* 没来得及被初始化 就被可见 read!
*/
}
}
happens-before规则
6种happens-before规则
- 1.程序顺序规则(一般是从前往后), as-if-serial,对于单线程程序来说 不管怎么重排序 程序运行结果不会变
- 2.监视器锁规则:一个锁的解锁 会先于 后续这个锁的加锁monitorenter(获得锁) monitorexit(释放锁)
- 3.volatile 变量规则: volatile的变量的写 先于 后续任意对于它的读
- 4.传递性: A>B, B>C–>A>C
- 5.start()规则: A线程 来执行操作 ThreadB.start(),那么A线程的ThreadB.start()操作先于 线程B中的其他任意操作
- 6.join()规则: A线程 来执行操作ThreadB.join(),那么线程B的任意操作先于线程A从ThreadB.join()操作成功返回.
原子类atomic
- Unsafe类
- CAS(Compare And Swap)
public class AtomicDemo {
public static int count = 0;
public static AtomicInteger atomicInteger = new AtomicInteger(0);
//加上synchronized也可以实现原子性
public static void methodSleep() {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//不是一个原子性操作getstack add putstack
atomicInteger.incrementAndGet();
System.out.println(atomicInteger.get());
// count++;
// System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(AtomicDemo::methodSleep).start();
Thread.sleep(1);
}
Thread.sleep(4000);
System.out.println(atomicInteger.get());
// System.out.println(count);
}
}
ThreadLocal实现原理分析
public class ThreadLocalDemo {
//会出现线程之间相互影响 不独立
private static Integer num;
//在使用threadLocal之后 每个线程会相互独立
public static final ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
num = local.get();
num += 10;
local.set(num);
System.out.println(Thread.currentThread().getName() + "-->" + num);
}, "Thread-" + (i + 1));
}
for (Thread thread : threads) {
thread.start();
}
}
}
// print result
Thread-1-->10
Thread-9-->10
Thread-8-->10
Thread-3-->10
Thread-2-->10
Thread-4-->10
Thread-7-->10
Thread-6-->10
Thread-10-->10
Thread-5-->10