问题整理(线程安全关键字)
1、数据一致性如何保证 (线程的安全和线程同步)
线程安全在三个方面体现:
1.原子性:提供互斥访问,串行线程(atomic,synchronized);
2.可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则 A操作一定在B操作之前,而是A操作的影响能被操作B观察到)
2、synchronized (线程互斥)
synchronized可以修饰在 普通方法中、静态方法中、代码块,
synchronized是内置的语言实现,jvm编译器(monitor)去保证锁的加锁和释放 ,synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生
解决多线程并发访问共享数据的竞争问题
特点:
保证了原子性、可见性、有序性
可重入锁 既同一线程中内部方法推出不用再进入该线程自动获取锁
synchronized使用的锁对象是存储在Java对象头的Mark Word内,Mark Word存储对象的HashCode、分代年龄、锁
其中锁分为:偏向锁、轻量级锁、自旋锁
jdk1.6以后对synchronized做了优化
一个线程获得了锁,进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,即可获取锁,但锁竞争比较激烈的场合,偏向锁就失效
转换为轻量级锁,不存在竞争, 轻量级锁失败后
转换为自旋锁,若干次循环后 去竞争锁
3、ReentrantLock(可重入锁)
ReentrantLock是JDK自带的,实现了Lock接口
比synchronized更加灵活,支持一个线程对资源重复加锁,需要手动释放锁, 可以查看获得锁的状态 也可以中断锁(lockInterruptibly()),同时也支持公平锁与非公平锁
公平与非公平指的是在请求先后顺序上,先对锁进行请求的就一定先获取到锁,那么这就是公平锁,反之就是非公平锁 (默认使用非公平锁)
ReentrantLock有3个内部类,分别是Sync、NonfairSync、FairSync ,其中Sync继承AQS框架核心类,而NonfairSync(非公平锁)、 FairSync(公平锁)则继承自Sync
AQS的内部类 获取锁时 线程获取锁成功时,对同步状态执行CAS操作,尝试把state的状态从0设置为1,
获取锁失败时,AQS会将该线程以及相关等待信息包装成一个节点(Node)并将其加入同步队列
公平锁在线程请求到来时先会判断同步队列是否存在结点,如果存在先执行同步队列中的结点线程,当前线程将封装成node加入同步队列等待
非公平锁当线程请求到来时,不管同步队列是否存在线程结点,直接尝试获取同步状态,获取成功直接访问共享资源
AQS还有Condition内部类等待队列。。。
4、volatile
变量定义为 volatile 之后 具备两种特性:保证此变量对所有的线程的可见性;禁止指令重排序优化
一、可见性:保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新
多线程给变量赋值会出现线程安全问题i++;操作并不具备原子性
例 T1.load(i)
T1.sore(i+1) 被缓存锁定其他CPU无法读写i (MESI缓存一致性协议) , T2的缓存区数据无效了 执行 + 1操作 返回结果还是1
二、volatile禁止重排优化 (有序性)
为了提高性能,编译器和cpu处理器的常常会对指令做重排 ,可能无法保证多线程变量的一致性
volatile变量通过内存屏障是一个CPU指令,指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序
5、atomic
6、ThreadLocal
ThreadLocal会为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
它只是一个线程的局部变量(其实就是一个Map)(以空间换时间)