线程安全的三大特性
**原子性:**一个或多个操作,要么全部执行并且执行过程不被任何因素打断,要么不执行。
**可见性:**一个线程对某个共享变量修改,另外的线程可以立即看到。
**有序性:**程序执行的顺序按照代码的先后顺序进行。
volatile关键字
Volatile是一种轻量级的同步机制
1.保证可见性
2.不保证原子性 (例如 i++)
3.禁止指令重排序
防重排序
实例化一个对象可以分三个步骤:
- 分配内存空间
- 初始化对象
- 将内存空间的地址赋值给对应的引用
但操作系统可以对指令进行重排序:
- 分配内存空间
- 将内存空间的地址赋值给对应的引用
- 初始化对象
若是此流程,多线程下可能将一个未初始化的对象引用暴露出来。
Volatile的原理和实现机制
volatile变量的内存可见性是基于内存屏障实现的,内存屏障又称内存栅栏,是cpu的指令。在程序运行时,为了提高执行率,编译器和处理器会对指令重排序。为了保证在不同编译环境下又相同结果,通过加入特定类型的内存屏障来禁止重排序。
加入volatile关键字和没加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀的指令。
0x000000000295157f: and $0x37f,%rax
0x0000000002951586: mov %rax,%rdi
0x0000000002951589: or %r15,%rdi
0x000000000295158c: lock cmpxchg %rdi,(%rdx) //lock 前缀的指令
0x0000000002951591: jne 0x0000000002951a15
0x0000000002951597: jmpq 0x00000000029515f8
0x000000000295159c: mov 0x8(%rdx),%edi
0x000000000295159f: shl $0x3,%rdi
0x00000000029515a3: mov 0xa8(%rdi),%rdi
0x00000000029515aa: or %r15,%rdi
lock前缀指令就是一个内存屏障:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
- 将当前处理器缓存行的数据写会到内存上。
- 如果是写回内存的操作,它会导致其他CPU中对应的缓存行无效。
Volatile应用场景
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量的不变式中
- 只有在状态真正独立于程序内其他内容时才能使用volatile
final关键字
写final域的重排序规则禁止对final域的写重排序到构造器最外。
- JMM禁止编译器把final域的写重排序到构造函数之外;
- 编译器会在final域写之后,构造函数return之前,插入一个storestore屏障。这个屏障可以禁止处理器把final域的写重排序到构造函数之外。
CAS
介绍
Compare-And-Swap(对比交换),是cpu的一条原子指令,作用是让cpu先进行比较两个值是否相等,然后原子地更新某个位置的值。CAS是靠硬件实现的,JVM通过封装硬件平台的汇编调用。
CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。
CAS操作是原子性的,所以多线程并发操作CAS更新数据时,可以不使用锁(synchronized)。
CAS方式为乐观锁。
实例
public class TestCas{
private AtomicInteger = new AtomicInteger(0);//java提供AtomicInteger原子类,在多线程情况下实现数据的一致性
public int add(){return i.addAndGet(1);}
}
ABA问题
CAS在操作值时,会去先检查值有没有发生变化,若无发生变化则更新值。但如果一个值从A变成B,在从B变成A,那个使用CAS进行检查时会发现它并没发生变化,但事实上已经发生了变化。
ABA问题解决可通过添加版本号。在变量前加版本号,例如1A-2B-3A。从JDK1.5开始,Atomic包提供了AtomicStampedReference解决ABA问题。
注意:Integer包装类,Integer.valueOf() 取值范围在(-128~127)之间都存入到IntegerCache.cache里面,取值相同的两个Integer可以使用==比较,若超出该取值范围,会new一个新的对象存入堆中,所以比较只能使用equals()去进行值对比。
Integer i1 = Integer.valueOf(120);
Integer i2 = Integer.valueOf(120);
System.out.println(i1==i2);//true
Integer i3 = Integer.valueOf(130);
Integer i4 = Integer.valueOf(130);
Integer i5 = new Integer(130);
System.out.println(i3==i4);//false
System.out.println(i3.equals(i4));//true
System.out.println(i3.equals(i5));//true
CAS自旋
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
//模拟CAS自旋锁
AtmoicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
//自旋锁
while(!atomicReference.compareAndSet(null),thread){}
}
//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
}
可重入锁
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
//synchronized 可重入锁
class Phone1{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms()");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call()");
}
}
class Phone2{
private ReentrantLock lock = new ReentrantLock();
public void sms(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"sms()");
call();
}catch(Exception e){
e.printException();
}finally{
lock.unlock()
}
}
public void call(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"call()");
call();
}catch(Exception e){
e.printException();
}finally{
lock.unlock()
}
}
}
public class Test{
public static void main(String[] args){
Phone1 phone1 = new Phone1();
Phone2 phone2 = new Phone2();
new Thread(()->{phone1.sms()},"1A").start();
new Thread(()->{phone1.call()},"1B").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{phone2.sms()},"2A").start();
new Thread(()->{phone2.call()},"2B").start();
}
}
//打印
//1Asms()
//1Acall()
//1Bsms()
//1Bcall()
//2Asms()
//2Acall()
//2Bsms()
//2Bcall()
死锁
//模拟产生死锁
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA,String lockB){
this.lockA=lockA;
this.lockB=lockB;
}
@Override
public void run(){
synchronized(lockA){
System.out.println(Thread.currentThread().getName()+"lock:"+lockA+
"get:"+lockB);
synchronized(lockB){ System.out.println(Thread.currentThread().getName()+"lock:"+lockB+
"get:"+lockA);
}
}
}
}
public class DemoDeadLock(){
public static void main(String[] args){
String lockA="lockA";
String lockB="lockB";
new Thread(()->{
new MyThread(lockA,lockB),"Thread1"
}).start();
new Thread(()->{
new MyThread(lockB,lockA),"Thread2"
}).start();
}
}
//运行后会出现
//Thread1lock:lockAgetlockB
//Thread2lock:lockBgetlockA
//彼此拿着对方的锁,同时也在等待对方释放锁,造成了死锁。
排查死锁
1.通过查看日志
2.通过查看堆栈信息(通过jps 获取进程号,再通过jstack获取该进程的堆栈信息)
UnSafe类(JNI)
Unsafe提供API大致可分内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。
AQS
AbstractQueuedSynchronizer(抽象的队列式同步器)
核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH:一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
JUC集合类
ConcurrentHashMap、
CopyOnWriteArrayList、
BlockingQueue、
SynchronizedQueue等
JUC工具类
CountDownLatch
CountDownLatch是一个计数器,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就 -1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
CyclicBarrier
CyclicBarrier类似CountDownLatch,不过它做的是[加法],需要里面的Thread全部运行结束才释放。
Semaphore
Semaphore底层是基于AbstractQueuedSynchronizer来实现的。Semaphore称为计数信号量,它允许n个任务同时访问某个资源,可以将信号量看做是在向外分发使用资源的许可证,只有成功获取许可证,才能使用资源。
JUC线程池
FutureTask(异步处理)
FutureTask 为 Future 提供了基础实现,如获取任务执行结果(get)和取消任务(cancel)等。如果任务尚未完成,获取任务执行结果时将会阻塞。一旦执行结束,任务就不能被重启或取消(除非使用runAndReset执行计算)。FutureTask 常用来封装 Callable 和 Runnable,也可以作为一个任务提交到线程池中执行。除了作为一个独立的类之外,此类也提供了一些功能性函数供我们创建自定义 task 类使用。FutureTask 的线程安全由CAS来保证。
ExecutorService
ExecutorService集成Execurot接口,提供一种将任务提交和每个任务将如何运行的机制,包括线程使用的细节、调度等分离开来的方法。通常使用Executor而不是显式地创建线程。
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ForkJoin(分治)
Fork/Join 技术是分治算法(Divide-and-Conquer)的并行实现,它是一项可以获得良好的并行性能的简单且高效的设计技术。目的是为了帮助我们更好地利用多处理器带来的好处,使用所有可用的运算能力来提升应用的性能。
ThreadLocal(处理共享变量)
ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。
简单的理解(一个ThreadLoacl在一个线程中是共享的,在不同线程之间又是隔离的。每个线程都只能看到自己的变量的值)
例子
package ThreadTest;
public class ThreadLocalTest {
//用ThreadLocal创建线程独享对象。ThreadLocal<T>
private static ThreadLocal<Integer> num = new ThreadLocal<>();
public static void main(String[] args) {
num.set(1);
new Thread(()->{
num.set(1);
System.out.println(num.get()+" "+Thread.currentThread().getName());
}).start();
new Thread(()->{
num.set(2);
System.out.println(num.get()+" "+Thread.currentThread().getName());
}).start();
System.out.println(num.get()+" "+Thread.currentThread().getName());
}
}
//输出结结果
2 Thread-1
1 main
1 Thread-0
ThreadLoacl实现原理:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
从源码中可以看到,向ThreadLocal存入一个值其实是向当前线程中的ThreadLocalMap存入值。也就是说其实并不是把数据存入到ThreadLocal对象中,而是以ThreadLocal这个实例存入到当前线程的Map里面,从而达到线程隔离。
单例模式
饿汉式
public class Hungry{
//饿汉式的弊端,浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){}//单例模式一定要对构造器进行私有化处理
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
为了解决饿汉式的弊端
public class LazyMan{
private LazyMan(){}
private volatile static LazyMan lazyMan;//保证原子性
//双重检测锁模式 (DCL-double checked locked)
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
lazyMan = new LazyMan();//不是原子性操作 所以要加volatile
/**
** 1.分配内存空间
** 2.初始化对象
** 3.将内存空间的地址赋值给对应的引用
** 若是编译器或者处理器对指令重排序 变成132执行顺序 多线程情况下LazyMan还没完成构建
**/
}
}
return lazyMan;
}
}
静态内部类懒汉式
public class Holder{
//对构造器私有化
private Holder(){}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private final static Holder HOLDER = new Holder();
}
}
反射机制破解单例模式(包括以上所有)
public static void main(String[] args) throw Exception{
LazyMan lazyman1 = LazyMan.getInstance();
Constructor<LazyMan> dec = LazyMan.class.getDeclaredCOnstructor(null);
//无视私有构造器
dec.setAccessible(true);
LazyMan lazyman2 = dec.newInstance();
System.out.println(lazyman1==lazyman2)//false;
//lazyman1不等于lazyman2 破坏了单例模式
}
懒汉式优化
public class LazyMan{
private LazyMan(){
//三重检测了
synchronized (LazyMan.class){//this指向实例对象 xxx.class指向该类信息
if(LazyMan!=null){
throw new RuntimeException("xxxxx");
}
}
}
private volatile static LazyMan lazyMan;//保证原子性
//双重检测锁模式 (DCL-double checked locked)
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
lazyMan = new LazyMan();//不是原子性操作 所以要加volatile
/**
** 1.分配内存空间
** 2.初始化对象
** 3.将内存空间的地址赋值给对应的引用
** 若是编译器或者处理器对指令重排序 变成132执行顺序 多线程情况下LazyMan还没完成构建
**/
}
}
return lazyMan;
}
}
枚举方法
枚举方法是没法用反射机制去
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}