线程状态迁移图
线程有状态:New,Runnable(Ready/Runing),TimedWaiting,Walting,Blocked,Terminated,线程不建议关闭,只需要正常结束。
- sleep
当前线程睡眠,睡眠结束后,继续执行。 - yield
当前线程让出执行时间,进入等待(就绪)状态,有很大可能继续执行。 - join
当前线程join其他线程,那么等待其他线程执行结束后,当前线程继续执行。 - Locksupport
https://www.jianshu.com/p/f1f2cd289205 - wait()和notify()、notifyAll(),这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象锁。 wait()和notify()、notifyAll()它们都是Object类的方法,而不是Thread类的方法。
- wait():调用该方法使持有该对象的线程把该对象的控制权交出去,释放锁,然后处于等待状态
- notify():调用该方法就会通知某个正在等待这个对象的控制权的线程可以继续运行,不释放锁
- notifyAll():调用该方法就会通知所有等待这个对象控制权的线程继续运行
public class WaitNotifyTest { List<Object> lists = new ArrayList<Object>(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) throws InterruptedException { WaitNotifyTest c = new WaitNotifyTest(); final Object lock = new Object(); new Thread(() -> { synchronized (lock) { System.out.println("t2启动"); if (c.size() != 5) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2 结束"); lock.notify();//结束 } }, "t2").start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { System.out.println("t1启动"); synchronized (lock) { for (int i = 0; i < 10; i++) { c.add(new Object()); System.out.println("add " + i); if (c.size() == 5) { lock.notify(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1").start(); } }
Java对象头详解
-
1、对象头形式
- 普通对象: Mark Word (32 bits) | class Pointer(32 bits)
- 数组对象:Mark Word(32bits) | class Pointer(32bits) | array length(32bits)
-
2、Mark Word
这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等。mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。
为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位,不同标记位下的Mark Word示意如下:
- lock:无锁(01)->偏向锁(01)–>轻量级锁(00)–>重量级锁(10)–>GC标记(11)
- biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
- age:4位的Java对象年龄。
- identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。
- thread:持有偏向锁的线程ID。
- epoch:偏向时间戳。
- ptr_to_lock_record:指向栈中锁记录的指针,30,表示指向轻量级锁。
- ptr_to_heavyweight_monitor:指向管程Monitor的指针,30,表示指向重量级锁。
Synchronized 用法
- 可以用来修饰实例方法,锁对象,最好final修饰;
- 可以用来修饰代码块,锁对象,最好final修饰;
- 可以用来修饰静态方法,锁当前类的class对象;
Synchronized 作用
-
原子性:保证单线程执行,只有一个线程获取锁对象;
-
可见性:内存强制刷新,释放锁写回主内存;
monitorenter 指令之后会有一个 Load 屏障,执行refresh处理器缓存操作,把别的处理器修改过的最新的值加载到自己的工作内存中,monitorexit 指令之后会有一个 Store 屏障,让线程把自己修改的变量都执行flush操作,刷新到主内存中。 -
有序性:happens-before,指定两个操作之间的执行顺序;
在 monitorenter 指令和 Load 屏障之后,会加一个 Acquire屏障,这个屏障的作用是禁止读操作和读写操作之间发生指令重排,在 monitorexit 指令前加一个Release屏障,也是禁止写操作和读写操作之间发生重排序。 -
可重入性:在一个线程使用synchronized方法时调用该对象另一个synchronized方法,即一个线程得到一个对象锁后再次请求该对象锁,可以获取锁,计数器实现;
锁升级
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
自旋锁/重量级锁
- 1、执行时间比较短,线程数量比较少。执行时间比较长,线程比较多用系统锁。
- 2、当自旋锁(CPU依然占用着该线程资源不放)升级为重量级锁,进入等待队列。
- 3、在需要进行等待的时候,是不会一直空转消耗CPU的,它会阻塞并且切换到别的线程执行,发生一个上下文切换,这也是一个较为耗时的操作,在重新再切换回该线程执行时,就已经发生了两次上下文切换。
Volatile
- 1、保证可见性,不保证原子性。
关键就是保证load、use的执行顺序不被打乱(保证使用变量前一定刚刚进行了load操作,从主存拿最新值来),assign、wirte的执行顺序不被打乱(保证赋值后马上就是把值写到主存) - 2、禁止指令重排序(Double Check Lock)。
内存屏障,对于volatile修饰的变量。
读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。
写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。 - 3、Double Check Lock 需要volatile,因为在实例化时对象赋默认值,没来得及初始化,如果发生指令重排序,使用还未完成初始化的对象,导致业务错误。
CAS(compare and set)
CAS是Compare and swap的简称,这个操作是硬件级别的操作,在硬件层面保证了操作的原子性。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。Java中的sun.misc.Unsafe类提供了compareAndSwapInt和compareAndSwapLong等几个方法实现CAS。
AtomicInteger/AtomicLong/AtomicBoolean;
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
ReentrantLock
ReentrantLock【互斥锁/排它锁】可以替代Synchronized,底层是CAS操作。Synchronized只有非公平锁。
-
方法使用
- lock():一直阻塞获取锁,直到获取成功;
- tryLock():尝试获取锁,成功返回true,失败返回false;
- tryLock(long time, TimeUnit unit):尝试在time时间内获取空闲的锁,在等待时间内可以被中断;InterruptedException异常;
- unlock():释放锁;
- lockInterruptibly():如果当前线程未被中断,则获得锁,如果当前线程被中断,则抛出(InterruptedException)异常;
-
可重入锁
static ReentrantLock lock=new ReentrantLock(); public static void main(String[] args) { try { lock.lock(); for(int i=0;i<10;i++){ System.out.println(i); if(i==3){ m1(); } } } finally { lock.unlock(); } new Thread(()->{m1();}).start(); } static void m1(){ try { lock.lock(); System.out.println("同一线程可重入"); } finally { lock.unlock(); } }
-
ReentrantLock可响应中断
当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly()。
thread.interrupt();//线程中断方法 -
ReentrantLock 可以实现公平锁
FairSync/NonfairSync
公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。而非公平锁则随机分配这种使用权。和synchronized一样,默认的ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。
公平锁设置,通过构造器生成,new ReentrantLock(true); -
newCondition(条件等待队列) 生产者和消费者
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock锁的newCondition()方法返回Condition对象,Condition类也可以实现等待/通知模式。
用notify()通知时,JVM会随机唤醒某个等待的线程, 使用Condition类可以进行选择性通知, Condition比较常用的两个方法:- await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
- signal()用于唤醒一个等待的线程。
public class ReentrantLockTest { List<Integer> list=new ArrayList<>(); ReentrantLock lock =new ReentrantLock(); Condition p=lock.newCondition(); Condition c=lock.newCondition(); public void put(int i) throws InterruptedException{ try { lock.lock(); while(list.size()==10){ p.await(); } list.add(i); System.out.println("生产者:"+i); c.signalAll(); } finally { lock.unlock(); } } public int get() throws InterruptedException{ try { lock.lock(); while(list.size()==0){ c.await(); } p.signalAll(); return (int) list.remove(0); } finally { lock.unlock(); } } public static void main(String[] args) { ReentrantLockTest c = new ReentrantLockTest(); for(int i=0; i<10; i++) { new Thread(()->{ for(int j=0; j<5; j++) try { System.out.println("消费者"+c.get()); } catch (InterruptedException e) { e.printStackTrace(); } }, "c" + i).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<2; i++) { new Thread(()->{ for(int j=0; j<25; j++) try { c.put( j); } catch (InterruptedException e) { e.printStackTrace(); } }, "p" + i).start(); } } }
ReadWriteLock 共享锁/排它锁
在使用读锁时(共享锁),其他线程可以进行读操作,但不可进行写操作。
在使用写锁时(排它锁),其他线程读、写操作都不可以。
-
读写锁有以下三个重要的特性:
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
-
使用
public class TestReadWriteLock { private static int value; static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); static Lock readLock = readWriteLock.readLock(); static Lock writeLock = readWriteLock.writeLock(); public static void read(Lock lock) { try { lock.lock(); Thread.sleep(1000); System.out.println("read over!"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void write(Lock lock, int v) { try { lock.lock(); Thread.sleep(1000); value = v; System.out.println("write over! value="+value); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { Runnable readR = ()-> read(readLock); Runnable writeR = ()->write(writeLock, new Random().nextInt()); for(int i=0; i<10; i++) new Thread(readR).start(); for(int i=0; i<2; i++) new Thread(writeR).start(); } }
LockSupport
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。归根结底,LockSupport调用的Unsafe中的native代码。LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就是将1变成0,同时park立即返回。再次调用park会变成block(因为permit为0了,会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累。
- 方法
- park()
当调用 park 方法后,由于默认是没有持有许可证,因此当前线程会被挂起阻塞; - unpark(Thread thread)
当 unpark 方法被调用后,如果入参的 thread 线程没有和 LockSupport 类关联许可证,那么调用后就会被关联一个许可证,因此入参的 thread 线程会被唤醒;Thread t=new Thread(()->{ for(int i=0;i<10;i++){ if(i==3){ LockSupport.park(); } System.out.println(i); } }); t.start(); Thread.sleep(4000); LockSupport.unpark(t);
- parkNanos(long nanos)
和 park 方法功能一样,只是这个是带有时间,不同的是它在被挂起 nanos 时间后会返回,就是有一个超时时间,在达到时间后即使没有获得和 LockSupport 关联的许可证也会返回Thread t=new Thread(()->{ LockSupport.parkNanos(2000000000l); for(int i=0;i<10;i++){ System.out.println(i); } }); t.start();
- park()
- 两个线程交替执行
public class LockSupportTest { volatile List<Object> lists = new ArrayList<Object>(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } static Thread t1 = null; static Thread t2 = null; public static void main(String[] args) { LockSupportTest c = new LockSupportTest(); t1 = new Thread(() -> { System.out.println("t1启动"); for (int i = 0; i < 10; i++) { c.add(new Object()); System.out.println("add " + i); if (c.size() == 5) { LockSupport.unpark(t2); LockSupport.park(); } } }, "t1"); t2 = new Thread(() -> { LockSupport.park(); System.out.println("t2 结束"); LockSupport.unpark(t1); }, "t2"); t2.start(); t1.start(); } }
CountDownLatch
-
概念
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。
CountDownLatch中只包含了Sync一个内部类,使用AQS的共享锁机制实现,它没有公平/非公平模式,所以它算是一个比较简单的同步器了。 -
使用
-
1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1,countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
Thread[] threads = new Thread[100]; CountDownLatch latch = new CountDownLatch(threads.length); for(int i=0; i<threads.length; i++) { threads[i] = new Thread(()->{ System.out.println("业务处理,name="+Thread.currentThread().getName()); latch.countDown(); },"t"+i); } for (int i = 0; i < threads.length; i++) { threads[i].start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("end CountDownLatch");
-
2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。
-
CyclicBarrier
CyclicBarrier 字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时候,屏障才会开门。所有被屏障拦截的线程才会运行。
- 方法
- CyclicBarrier(int parties, Runnable barrierAction)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。 - int await()
在所有参与者都已经在此 barrier上调用 await 方法之前,将一直等待。
- CyclicBarrier(int parties, Runnable barrierAction)
- 原理
在CyclicBarrier的内部定义了一个ReentrantLock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。 - 使用
static class TaskThread extends Thread { CyclicBarrier barrier; public TaskThread(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { try { Thread.sleep(1000); System.out.println(getName() + " 到达栅栏 A"); barrier.await(); System.out.println(getName() + " 冲破栅栏 A"); Thread.sleep(2000); System.out.println(getName() + " 到达栅栏 B"); barrier.await(); System.out.println(getName() + " 冲破栅栏 B"); } catch (Exception e) { e.printStackTrace(); } } }
int threadNum = 5; CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " 完成最后任务"); } }); for(int i = 0; i < threadNum; i++) { new TaskThread(barrier).start(); }
Phaser
Phaser表示“阶段器”,一个可重用的同步barrier。用来解决控制多个线程分阶段共同完成任务的情景问题。其作用相比CountDownLatch和CyclicBarrier更加灵活。
- 原理
在Phaser内有2个重要状态,分别是phase和party。
phase就是阶段,初值为0,当所有的线程执行完本轮任务,同时开始下一轮任务时,意味着当前阶段已结束,进入到下一阶段,phase的值自动加1。
party就是线程,party=4就意味着Phaser对象当前管理着4个线程。
Phaser还有一个重要的方法经常需要被重载,那就是boolean onAdvance(int phase, int registeredParties)方法。
此方法有2个作用:
(1)当每一个阶段执行完毕,此方法会被自动调用,因此,重载此方法写入的代码会在每个阶段执行完毕时执行,相当于CyclicBarrier的barrierAction。
(2)当此方法返回true时,意味着Phaser被终止,因此可以巧妙的设置此方法的返回值来终止所有线程。 - 使用
public class PhaserTest { public static final int PARTIES = 6; public static final int PHASES = 2; public static void main(String[] args) { Phaser phaser = new Phaser(PARTIES) { @Override protected boolean onAdvance(int phase, int registeredParties) { System.out.println("finished;phase: " + phase + ",registeredParties: "+registeredParties); return super.onAdvance(phase, registeredParties); } }; for (int i = 0; i < PARTIES; i++) { new Thread(()->{ for (int j = 0; j < PHASES; j++) { System.out.println(String.format("%s: phase: %d", Thread.currentThread().getName(), j)); phaser.arriveAndAwaitAdvance(); } }, "Thread " + i).start(); } } }
Semaphore
Semaphore,信号量,它保存了一系列的许可(permits),每次调用acquire()都将消耗一个许可,每次调用release()都将归还一个许可。
Semaphore通常用于限制同一时间对共享资源的访问次数上,也就是常说的限流。
- 使用场景
-
数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。
-
停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。
-
- 常用方法
- public Semaphore(int permits)
创建一个给定许可数量的信号量对象,且默认以非公平锁方式获取资源 - public Semaphore(int permits, boolean fair)
创建一个给定许可数量的信号量对象,且是否公平方式由传入的fair布尔参数值决定 - public void acquire()
从此信号量获取一个许可,当许可数量小于零时,则阻塞等待 - public void acquire(int permits)
从此信号量获取permits个许可,当许可数量小于零时,则阻塞等待,但是当阻塞等待的线程被唤醒后发现被中断过的话则会抛InterruptedException异常 - tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程 - tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。 - public void acquireUninterruptibly(int permits)
从此信号量获取permits个许可,当许可数量小于零时,则阻塞等待,但是当阻塞等待的线程被唤醒后发现被中断过的话不会抛InterruptedException异常 - public void release()
释放一个许可 - public void acquire(int permits)
释放permits个许可 - public int availablePermits()
返回可用的令牌数量
- public Semaphore(int permits)
- 使用
public class TestSemaPhore { public static void main(String[] args) { Semaphore s = new Semaphore(2, true); new Thread(()->{ try { s.acquire(); System.out.println("T1 start..."); Thread.sleep(200); System.out.println("T1 end..."); } catch (InterruptedException e) { e.printStackTrace(); } finally { s.release(); } }).start(); new Thread(()->{ try { s.acquire(); System.out.println("T2 start..."); Thread.sleep(200); System.out.println("T2 end..."); s.release(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }
AQS
AQS【AbstractQueuedSynchronizer】就是基于双向链表队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
- AQS 定义了两种资源共享方式:
Exclusive:独占,只有一个线程能执行,如ReentrantLock、CyclicBarrier、ReadWriteLock
Share:共享,多个线程可以同时执行,如Semaphore、CountDownLatch、ReadReadLock - NonfairSync 的lock()源码
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
- 执行tryAcquire方法如果获取到锁继续,否则进入队列
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //获取当前线程 int c = getState();//获取锁标识 if (c == 0) { if (compareAndSetState(0, acquires)) {//通过CAS的方式获取锁,c==0,期望值是0 setExclusiveOwnerThread(current);//设置当前线程为独占锁,互斥 return true; } } else if (current == getExclusiveOwnerThread()) {//如果当前线程是独占锁线程 int nextc = c + acquires; //c++,锁重入 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc);//设置锁标识 return true; } return false; }
- 当前节点加入队列
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode);//当前现场创建一个新的节点 // Try the fast path of enq; backup to full enq on failure Node pred = tail; //pred 设置为队列中的最后一个节点 if (pred != null) { //最后一个节点不等于null node.prev = pred; // 当前节点的前驱节点指向 pred节点 if (compareAndSetTail(pred, node)) {//CAS 操作添加当前节点到队列中 pred.next = node;// pred 节点的后置节点指向当前节点 return node; } } enq(node); //如果队列是空的,当前节点设置为头结点,否者执行上述操作 return node; }
- 队列中尝试获得锁
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); //获取当前节点的前置节点 if (p == head && tryAcquire(arg)) { // 前置节点是头节点并且获取到锁 setHead(node); //当前节点设置为头结点 p.next = null; // help GC 垃圾回收 failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; //没有获取到锁并且不是头结点,阻塞等待前直接点释放锁,唤醒后置节点 } } finally { if (failed) cancelAcquire(node); } }