文章目录
线程生命周期
主要分为5个部分,新建、就绪、运行、阻塞和结束
- 新建后通过start方法进入就绪阶段
- 就绪阶段通过CPU抢到执行权进入运行阶段
- 运行阶段遇到IO阻塞,线程等待方法,wait方法或者互斥锁进入阻塞阶段
- 阻塞阶段通过相应的解除阻塞方法,如等待时间结束,被唤醒方法和抢到锁进入就绪阶段
- 运行阶段进入结束阶段,通过运行结束或stop方法
线程的常用实现方式
- 通过继承Thread实现
- 通过实现Runable接口实现
- 通过实现Callable接口实现,可以返回返回值
建议:使用接口方式实现线程,原因是1.继承整个Thread开销过大;2.java不可多重继承,而可以实现多个接口
线程的阻塞方式和区别及应用
- 无限期等待(Waiting):没有CPU执行时间,需要其他线程显示地唤醒。
- 没有设置Timeout参数的Object.wait()
- 没有设置Timeout参数的Thread.join()
- LockSupport.park()
- ReentrantLock
- 期限等待(Timed Waiting):没有CPU执行时间,在到达时间后自动地唤醒。
- 设置Timeout参数的Object.wait()
- 设置Timeout参数的Thread.join()
- Thread.sleep()
- LockSupport.parkNanaol()
- LockSupport.parckUntil
- 阻塞(Blocked):线程被阻塞住了,需要获取锁才能执行,相对于等待状态不是等待一定时间
- synchronized
sleep、yied、wait、join的区别,以及为何sleep和yied是线程静态方法,wait是Object的方法
- sleep:释放CPU资源,与锁无关,阻塞一定时长
- yied:释放CPU资源,与锁无关,不阻塞,调用后根据线程优先级争抢cpu
- wait:释放CPU资源,释放锁资源,和synchronizer锁相关,且必须在synchronizer代码块中执行,调用后会对该对象的锁进行释放(notify和notifyAll,不会释放锁,需要等到synchronizer代码块执行完,才释放锁)
- join:释放CPU资源,与锁无关,阻塞当前线程,直至调用线程执行完成或设置的等待时间完成
AQS的实现方式
AbstractQueuedSynchronizer是一个模板的抽象类,开发人员只需要实现其中的什么条件下获取同步状态,什么条件下释放同步状态的方法即可,状态值的获取设置和比较也是由抽象类实现,且通过自旋保证值的线程安全。实现后,具体的同步状态如何获取以及如何释放,则由抽象类实现,开发人员只需要调用即可。
AQS的原理
AQS获取同步:首先会通过tryAcquire方法判断是否可以获取同步,可以则直接执行,若不可以,将构造节点加入到一个双休的CLH队列中,加入后,若处于第一个节点,则会进行自旋获取同步状态,若处于后续节点,则会被阻塞在队列中(通过LockSupport.park实现,其实是等待态)
AQS释放同步:当获取了同步状态的节点释放时,会唤醒后继节点,此节点将加入到同步状态的争夺中
AQS相关实现
ConcurrentHashMap
- ConcurrentHashMap 和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比 HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个 Map 锁住。
- 此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map 中正在被写入的部分进行锁定。
ConcurrentLinkedQueue
- ConcurrentLinkedQueue是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。
- LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。
public class BlockingQueueTest {
public static void main(String[] args) {
BlockingQueue blockingQueue =new ArrayBlockingQueue(1000);
new Thread(new BlockingQueueTest().new Consumer(blockingQueue)).start();
new Thread(new BlockingQueueTest().new Producer(blockingQueue)).start();
}
class Consumer implements Runnable {
private BlockingQueue<String> blockingQueue;
public Consumer(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
try {
String take = blockingQueue.take();
System.out.println(take);
String take2 = blockingQueue.take();
System.out.println(take2);
String take4 = blockingQueue.take();
System.out.println(take4);
String take3 = blockingQueue.poll(10, TimeUnit.SECONDS);
System.out.println(take3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Producer implements Runnable{
private BlockingQueue<String> blockingQueue;
public Producer(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
try {
this.blockingQueue.put("1111");
Thread.sleep(1000);
this.blockingQueue.put("2222");
Thread.sleep(1000);
this.blockingQueue.put("3333");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果
1111
2222
3333
null
CountDownLatch
- CountDownLatch 以一个给定的数量初始化。countDown() 每被调用一次,这一数量就减一。通过调用 await() 方法之一,线程可以阻塞等待这一数量到达零。以下是一个简单示例。
- Decrementer 三次调用 countDown() 之后,等待中的 Waiter 才会从 await() 调用中释放出来。
public class CountDownLatchTest {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(new CountDownLatchTest().new General(countDownLatch)).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new CountDownLatchTest().new Soldier(countDownLatch),"3号").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new CountDownLatchTest().new Soldier(countDownLatch),"1号").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new CountDownLatchTest().new Soldier(countDownLatch),"2号").start();
}
class General implements Runnable {
private CountDownLatch countDownLatch;
public General(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
countDownLatch.await();
System.out.println("将领发布命令!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Soldier implements Runnable{
private CountDownLatch countDownLatch;
public Soldier(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"士兵准备完毕。 count:"+countDownLatch.getCount());
countDownLatch.countDown();
}
}
}
输出结果
3号士兵准备完毕。 count:3
1号士兵准备完毕。 count:2
2号士兵准备完毕。 count:1
将领发布命令!!!
CyclicBarrier
- java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步。换句话讲,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情。
public class TestCyclicBarrier {
public static void main(String[] args) {
Runnable surfInternet = new Runnable() {
@Override
public void run() {
System.out.println("满足条件,可以上网了!!!");
}
};
Runnable codding = new Runnable() {
@Override
public void run() {
System.out.println("满足条件,可以编程了!!!");
}
};
CyclicBarrier surfInternetCyclicBarrier = new CyclicBarrier(2, surfInternet);
CyclicBarrier coddingCyclicBarrier = new CyclicBarrier(2, codding);
// CyclicBarrier surfInternetCyclicBarrier = new CyclicBarrier(2);
// CyclicBarrier coddingCyclicBarrier = new CyclicBarrier(2);
new Thread(new TestCyclicBarrier.SuerfInternetCondition(surfInternetCyclicBarrier, coddingCyclicBarrier)).start();
new Thread(new TestCyclicBarrier.coddingCyclicCondition(surfInternetCyclicBarrier, coddingCyclicBarrier)).start();
}
static class SuerfInternetCondition implements Runnable {
private CyclicBarrier surfInternetCyclicBarrier;
private CyclicBarrier coddingCyclicBarrier;
public SuerfInternetCondition(CyclicBarrier surfInternetCyclicBarrier, CyclicBarrier coddingCyclicBarrier) {
this.surfInternetCyclicBarrier = surfInternetCyclicBarrier;
this.coddingCyclicBarrier = coddingCyclicBarrier;
}
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("买了电脑");
surfInternetCyclicBarrier.await();
Thread.sleep(2000);
coddingCyclicBarrier.await();
System.out.println("学了编程");
} catch (Exception e) {
e.printStackTrace();
}
}
}
static class coddingCyclicCondition implements Runnable {
private CyclicBarrier surfInternetCyclicBarrier;
private CyclicBarrier coddingCyclicBarrier;
public coddingCyclicCondition(CyclicBarrier surfInternetCyclicBarrier, CyclicBarrier coddingCyclicBarrier) {
this.surfInternetCyclicBarrier = surfInternetCyclicBarrier;
this.coddingCyclicBarrier = coddingCyclicBarrier;
}
@Override
public void run() {
try {
System.out.println("连了网线");
surfInternetCyclicBarrier.await();
System.out.println("装了IDE");
coddingCyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
输出结果
连了网线
买了电脑
满足条件,可以上网了!!!
装了IDE
学了编程
满足条件,可以编程了!!!
Semaphore
- java.util.concurrent.Semaphore 类是一个计数信号量。这就意味着它具备两个主要方法:
- acquire()
- release()
- 计数信号量由一个指定数量的 “许可” 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。因此,在没有任何 release() 调用时,最多有 N 个线程能够通过 acquire() 方法,N 是该信号量初始化时的许可的指定数量。这些许可只是一个简单的计数器。这里没啥奇特的地方。
public class TestSemaphore {
public static void main(String[] args) {
// 公平
Semaphore semaphore = new Semaphore(3,true);
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"1").start();
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"2").start();
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"3").start();
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"4").start();
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"5").start();
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"6").start();
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"7").start();
new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"8").start();
}
class SemaphoreRunable implements Runnable{
private Semaphore semaphore;
public SemaphoreRunable(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" 通过了");
semaphore.release();
semaphore.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" 返回了");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果
2 通过了
1 通过了
3 通过了
6 通过了
5 通过了
4 通过了
7 通过了
2 返回了
8 通过了
1 返回了
5 返回了
3 返回了
6 返回了
4 返回了
7 返回了
8 返回了
Exchange
- java.util.concurrent.Exchanger 类表示一种两个线程可以进行互相交换对象的会和点。这种机制图示如下:
public class TestExchanger {
public static void main(String[] args) throws InterruptedException {
Exchanger exchanger = new Exchanger();
new Thread(new TestExchanger().new ExchangerRunable("A",exchanger),"A").start();
Thread.sleep(5000);
new Thread(new TestExchanger().new ExchangerRunable("B",exchanger),"B").start();
}
class ExchangerRunable implements Runnable {
private String exchangerObj;
private Exchanger exchanger;
public ExchangerRunable(String exchangerObj, Exchanger exchanger) {
this.exchangerObj = exchangerObj;
this.exchanger = exchanger;
}
@Override
public void run() {
String previous = exchangerObj;
try {
exchangerObj = (String) exchanger.exchange(exchangerObj);
System.out.println(Thread.currentThread().getName() + " previous:" + previous + " exchange end: " + exchangerObj);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果
B previous:B exchange end: A
A previous:A exchange end: B
CountDownLatch 控制多线程执行完前,父线程阻塞
CyclicBarrier 控制多线程在某点同步
Semaphore 控制同时执行线程数
Exchanger 两个线程在某点交换数据
相关锁
-
synchronized和Reentrantlock
- 区别:
- 使用方式不同,synchronized可在方法上和代码块中标示,而Reentrantlock只能在代码块中使用
- synchronized是jvm实现的而Reentrantlock是在jdk中的juc包下实现的
- Reentrantlock可实现公平锁和非公平锁,而synchronized只能是非公平的
- Reentrantlock可中断,而synchronized不行
- Reentrantlock可以同时绑定多个Condition对象(Condition对象通过Reentrantlock的newCondition方法创建,可以指定线程唤醒,而Objecet只能唤醒所有线程)
- 选择:建议在不需要使用Reentrantlock的特殊功能时,使用synchronized,原因是1.synchronized是jvm自带的,可兼容各个版本,而Reentrantlock不行;2.效率上synchronized在1.6之后,进行了优化,加入了CAS自选,效率与Reentrantlock相似;3.synchronized会自动释放锁,不用担心死锁
-
独占锁VS共享锁
- 独占锁:只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁
- 共享锁:多个线程可同时执行,如Semaphore、CountdownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
-
公平锁VS非公平锁
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁。
- 非公平锁: 当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的。
-
乐观锁VS悲观锁(概念)=自旋锁VS互斥锁(实现)
- 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
- 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
- 两种锁的使用场景:从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
- CAS的缺点:1.ABA问题;2.循环时间长开销大;3.只能保证一个共享变量的原子操作
线程池
线程池优势和参数
使用线程池的好处
- 频繁创建线程会消耗资源,而从池中获取线程则节省开销,就好比吃大锅饭,每个人重新造一把勺子很耗费时间,用完了放进锅中,别人再来拿就能够节约时间
- 将线程的创建和使用分开,方便维护
线程池执行任务顺序
- 首先核心线程数量未满,则使用核心线程来执行任务,核心线程一般不会超时,除非设置
- 当核心任务满后,判断是否超出队列大小,队列大小未满,则加入队列,等待核心线程执行完后,去除队列任务执行
- 当队列满时,若未超出最大线程数量,则创建线程执行
- 若超出最大线程数量,则根据用户或默认的策略抛出异常拒绝任务
线程池参数设置
- 核心线程数设置:根据每秒需要和可执行的任务数量设置,总共100~1000个任务,其中80%的时间为200个任务,一个任务消耗0.1秒,则每秒 20个任务需要执行,则核心线程数设置为20(此外,其上限也受制于硬件环境,若服务器为CPU密集型(CPU经常100%,IO很短时间就能完成),则核心线程数最大值 = CPU核数 + 1,若服务器是IO密集型,则核心线程数最大值 = CPU核数 * 2)
- 队列大小设置:是核心线程执行1秒的任务数量,由于一个任务为0.1s,核心线程为20,所以20/0.1为200
- 最大核心线程数设置:(最大的任务 - 队列大小)* 每个任务的时间,所以以上为(1000 - 200)* 0.1 = 80
线程池参数可动态变化:
- 核心线程数和最大线程数可进行动态变化,但需注意核心线程数不能小于最大线程数量
- 队列大小无法动态变化,但可重写队列实现完成
线程池异常问题
- 问题:线程池对抛出的异常不会处理,导致用户无法感知和查看日志
- 解决:
- 对代码进行try catch
- 自定义线程池,并重写afterExecute方法
- 使用submit,对返回的Future对象进行获取
线程池类型
1.newCachedThreadPool:
- 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
- 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
- 适用:执行很多短期异步的小程序或者负载较轻的服务器
2.newFixedThreadPool:
- 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无界阻塞队列
- 通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程- 了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列),但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
- 适用:执行长期的任务,性能好很多
3.newSingleThreadExecutor:
- 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无界阻塞队列
- 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
- 适用:一个任务一个任务执行的场景
4.NewScheduledThreadPool:
- 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
- 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
- 适用:周期性执行任务的场景
ThreadLocal变量
- 作用:用作线程间的数据隔离,每个线程可填充自己的数据
- 使用场景:1.横跨多个方法的上下文信息,如Session、用户信息;2.Spring的事务隔离,每个线程拥有自己的数据库连接
- 原理:每个线程都会存储自己的ThreadLocal变量,从而保证数据隔离,存储类似于Map的方式,key为ThreadLocal的对象(从而可以设置不同的ThreadLocal变量),value为该对象设置的值。虽然类似Map,但并没有继承Map,数据结构为数组,若发生hash冲突,则表中的位置往后移动
// Thread的变量
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- 存在的问题:会出现内存泄漏的问题,原因是key为弱引用,垃圾回收会处理,处理后会设置为null,但是value无法被回收,由于使用线程池的方式线程实例一直存在,但时value一直累积增多,无法回收
- 问题解决:在使用完ThreadLocal后,调用remove方法
- 线程能否共享:可以,子线程可以通过上述提到的inheritableThreadLocals进行共享父线程的变量,原因是线程初始化时,会将父线程的该变量引用到子线程中,实现如下
private void test() {
final ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("13");
Thread t = new Thread() {
@Override
public void run() {
super.run();
Log.i( "年龄" + threadLocal.get());
}
};
t.start();
}
public class Thread implements Runnable {
……
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
……
}