synchronized扩展:重入锁
使用java.util.concurrent.locks.ReentrantLock类来实现
重入锁必须手动加锁,释放锁,加几次锁,就要释放几次锁,多放,报异常,少放,还有锁
public class ReenterLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run(){
for(int j=0;j<10000000;j++){
lock.lock();
try {
i++;
} finally{
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock t = new ReenterLock();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
中断响应
对于synchronized,如果一个线程在等待锁,只能获得锁继续执行,或者保持等待。重入锁可以线程被中断,就是等待过程中,程序可以根据需要取消锁的请求
lockInterruptibly()可以对中断进行响应的锁申请动作,在等待锁的过程中,可以响应中断
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public IntLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
lock1.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock1.lockInterruptibly();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(lock1.isHeldByCurrentThread())
lock1.unlock();
if(lock2.isHeldByCurrentThread()){
lock2.unlock();
}
System.out.println(Thread.currentThread().getId()+":线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
IntLock r1 = new IntLock(1);//先占用lock1,再占用lock2
IntLock r2 = new IntLock(2);//先占用lock2,再占用lock1
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();t2.start();
//主程序休眠,俩线程死锁,
Thread.sleep(1000);
//中断其中一个线程,t2被中断,会放弃对lock1的申请,同时释放lock2
t2.interrupt();
}
}
上述代码,中断后,俩线程都退出,t1完成工作,t2放弃任务,释放资源
锁申请等待限时
用tryLock()进行一次限时的等待
public class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if(lock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
}else{
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock.isHeldByCurrentThread())
lock.unlock();
}
}
public static void main(String[] args) {
TimeLock t = new TimeLock();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
tryLock()可以接收俩个参数,一个为等待时长,一个为计时单位
也可以不带参数执行,不会等待
public class TryLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public TryLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
if(lock ==1){
while(true){
if(lock1.tryLock()){
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(lock2.tryLock()){
try {
System.out.println(Thread.currentThread().getId()+":my job done");
return;
} catch (Exception e) {
lock2.unlock();
}
}
} finally{
lock1.unlock();
}
}
}
}else{
while(true){
if(lock2.tryLock()){
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(lock1.tryLock()){
try {
System.out.println(Thread.currentThread().getId()+":my job done");
return;
} catch (Exception e) {
lock1.unlock();
}
}
} finally{
lock2.unlock();
}
}
}
}
}
public static void main(String[] args) {
TryLock r1 = new TryLock(1);
TryLock r2 = new TryLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
只看到一个输出
公平锁
synchronized不公平锁,重入锁可以公平
有一个构造函数,fair为true,锁是公平的,实现公平锁要求系统维护一个有序队列,因此成本较高,性能低下
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public class FairLock implements Runnable {
public static ReentrantLock fairLock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + " 获得锁");
} finally {
fairLock.unlock();
}
}
}
public static void main(String[] args) {
FairLock r1 = new FairLock();
new Thread(r1, "Thread_t1").start();
new Thread(r1, "Thread_t2").start();
}
}
公平锁t1,t2交替输出
对ReentrantLock重要方法整理:
lock()获得锁,锁被占用,则等待
lockInterruptibly()获得锁,但优先响应中断
tryLock()尝试获得锁,成功,返回true,失败返回false,不等待,立刻返回
tryLock(long time,TimeUnit unit)在给定时间尝试获得锁
unLock释放锁
重入锁的实现主要集中在java层面,主要包含三个元素,原子状态,等待队列,阻塞原语用来挂起和恢复线程
Condition条件
与wait和notify作用大致一样Condition与重入锁关联
通过lock接口的Condition newCondition()生成与当前重入锁绑定的Condition实例,利用Condition对象,可以让线程在合适的时间等待,或在某一时刻得到通知执行
Condition接口实例
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
await()方法使当前线程等待,并释放锁,当其他线程使用signal()或者signalAll(),线程重新获得锁并继续执行,或者当线程中断时,也能跳出等待。
awaitUninterruptibly()与await()差不多,但不会在等待过程中响应中断
singal()唤醒一个等待中的线程,singalAdd()唤醒所有在等待的线程
public class ReenterLockCondition implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
//通过lock生成一个与之绑定的Condition对象
public static Condition condition = lock.newCondition();
@Override
public void run(){
try {
lock.lock();
//线程在Condition对象上等待
condition.await();
System.out.println("Thread is going on");
} catch (Exception e) {
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockCondition t1 = new ReenterLockCondition();
new Thread(t1).start();
Thread.sleep(2000);
lock.lock();
//主线程发出通知,等待在Condition上的线程可以继续执行
condition.signal();
lock.unlock();
}
}
线程使用Condition.await(),要求线程持有相关的重入锁,在await调用后释放锁,signal也一样要先获得锁,在调用,随机唤醒Condition对象中的一个等待线程,然后释放锁
允许多个线程同时访问:信号量(Semaphore)
无论内部锁还是重入锁,一次只允许一个线程访问一个资源。信号量可以指定多个线程,同时访问某一个资源
主要有以下构造方法
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
第二个参数指定是否公平
主要逻辑方法
public void acquire()
public void acquireUninterruptibly()
public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit)
public void release()
acquire()方法尝试获取一个准入的许可,无法获得,线程就会等待,acquireUninterruptibly()和acquire()类似,但不影响中断,tryAcquire()尝试获得一个许可,成功返回true,不等待,release()用于线程访问资源结束后,释放一个许可,使其他等待许可线程可以进行资源访问
public class SemapDemo implements Runnable {
//同时可以五个线程进入临界区管理代码
final Semaphore semp = new Semaphore(5);
@Override
public void run() {
try {
semp.acquire();
//模拟耗时操作
System.out.println(Thread.currentThread().getId()+":done");
semp.release();
} catch (Exception e) {
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemapDemo demo = new SemapDemo();
for(int i =0;i<20;i++){
exec.submit(demo);
}
}
}
ReadWriteLock读写锁
系统中,读操作的次数远远大于写,读写锁可以提高性能
public class ReadWriteLockDemo {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
public Object handleRead(Lock lock) throws InterruptedException{
try {
lock.lock();//模拟读操作
Thread.sleep(1000);//读操作耗时越多,读写锁的优势越明显
return value;
} finally {
lock.unlock();
}
}
public void HandleWrite(Lock lock,int index) throws InterruptedException{
try {
lock.lock();//模拟写操作
Thread.sleep(1000);
value = index;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo demo = new ReadWriteLockDemo();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
try {
demo.handleRead(readLock);
// demo.handleRead(lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnable = new Runnable() {
@Override
public void run() {
try {
demo.HandleWrite(writeLock, new Random().nextInt());
// demo.HandleWrite(lock, new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for(int i=0;i<18;i++){
new Thread(readRunnable).start();
}
for(int i=0;i<2;i++){
new Thread(writeRunnable).start();
}
}
}
使用读写分离,因此,读线程完全并行,写会阻塞读,写线程实际是串行的,使用重入锁,所有的读和写线程都必须相互等待,耗时长
倒计时器:CountDownLatch
控制线程等待,让某个线程等待直到倒计时结束,再开始执行
构造器接收一个整数作为参数,即当前这个计数器的计数个数
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public class CountDownLatchDemo implements Runnable{
//计数数量为10表示需要有10个线程完成任务,等待在CountDownLatch上的线程才能继续执行
static final CountDownLatch end = new CountDownLatch(10);
static final CountDownLatchDemo demo = new CountDownLatchDemo();
@Override
public void run(){
//模拟检查任务
try {
Thread.sleep(new Random().nextInt(10)*1000);
System.out.println("check complete");
//一个线程执行完,到计数器减少1
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(10);
for(int i=0;i<11;i++){
exec.submit(demo);
}
//等待检查,主线程等待所有的检查任务全部完成,主线程继续执行
end.await();
System.out.println("Fire!");
exec.shutdown();
}
}
循环 栅栏:CyclicBarrier
也可以实现线程间的计数等待CyclicBarrier可以接收一个参数作为barrierAction,即当计数器一次计数完成后,系统会执行的动作
计数器设置为10,凑齐10个后,计数器归0,接着凑齐下一批10个线程
public class CyclicBarrierDemo {
public static class Soldier implements Runnable{
private String soldier;
private final CyclicBarrier cyclic;
Soldier(CyclicBarrier cyclic,String soldierName) {
this.cyclic = cyclic;
this.soldier = soldierName;
}
@Override
public void run(){
try {
//等待所有士兵到齐
cyclic.await();
doWork();
//等待所有士兵完成工作
cyclic.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt()%10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier+":任务完成");
}
}
public static class BarrierRun implements Runnable{
boolean flag;
int N;
public BarrierRun(boolean flag, int n) {
this.flag = flag;
N = n;
}
@Override
public void run(){
if(flag){
System.out.println("司令:+[士兵"+N+"个,任务完成]");
}else{
System.out.println("司令:+[士兵"+N+"个,集合完毕]");
flag =true;
}
}
}
public static void main(String[] args) {
final int N = 10;
Thread[] allSoldier = new Thread[N];
boolean flag = false;
//创建实例,计数器设置为10,计数器达到指标,执行BarrierRun的run方法,没管过士兵线程会执行Soldier的run方法
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
//设置屏障点,主要是为了执行这个方法
System.out.println("集合队伍!");
for(int i=0;i<N;++i){
System.out.println("士兵"+i+"报道!");
allSoldier[i] = new Thread(new Soldier(cyclic, "士兵"+i));
allSoldier[i].start();
}
}
}
集合队伍!
士兵0报道!
士兵1报道!
士兵2报道!
士兵3报道!
士兵4报道!
士兵5报道!
士兵6报道!
士兵7报道!
士兵8报道!
士兵9报道!
司令:+[士兵10个,集合完毕]
士兵9:任务完成
士兵7:任务完成
士兵2:任务完成
士兵8:任务完成
士兵5:任务完成
士兵0:任务完成
士兵1:任务完成
士兵4:任务完成
士兵6:任务完成
士兵3:任务完成
司令:+[士兵10个,任务完成]
线程阻塞工具类:LockSupport
可以在任何位置让线程阻塞,和Thread.suspend()比较,弥补由于resume()在前发生,导致线程无法继续执行的异常,不需要先获取某个对象的锁
静态方法park()可以阻塞当前线程
public class LockSupportDemo {
public static Object u= new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread{
public ChangeObjectThread(String name){
super.setName(name);
}
@Override
public void run(){
//对临界区的访问
synchronized (u) {
System.out.println("in "+getName());
LockSupport.park();
}
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}
上述代码正常执行
LockSupport类使用类似信号量的机制,为每个线程准备一个许可,许可可用,park()函数立即返回,将许可变为不可用,许可不可用,就会阻塞,unpark()使许可变为可用,许可只能有一个,即使unpark()发生在park()之前,可以使下一个park操作立即返回
支持中断影响
public class LockSupportIntDemo {
public static Object u= new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread{
public ChangeObjectThread(String name){
super.setName(name);
}
@Override
public void run(){
//对临界区的访问
synchronized (u) {
System.out.println("in "+getName());
LockSupport.park();
if(Thread.interrupted()){
System.out.println(getName()+"被中断了");
}
}
System.out.println(getName()+"执行结束");
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.interrupt();
LockSupport.unpark(t2);
}
}
in t1
t1被中断了
t1执行结束
in t2
t2执行结束
线程复用:线程池
Executor框架提供了各种类型的线程池
主要以下几种工厂方法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
第一个返回固定数量的线程池,提交新任务,有空闲线程,立即执行,没有则暂存到一个队列中,有线程空闲,就处理队列中的
第二个返回只有一个线程的线程池,多余任务会保存在一个任务队列中,线程空闲,按先入先出的顺序执行
第三返回可根据实际情况调整线程数量的线程池,有空闲线程可以复用,优先使用可复用的线程,所有线程都工作,有新任务,就会创建新的线程处理任务,所有线程在当前任务执行完毕后,返回线程池复用
第四个线程池大小为1,有在指定时间执行某功能的能力
第五个可以指定线程数量
固定大小的线程池
public class ThreadPoolDemo {
public static class MyTask implements Runnable{
@Override
public void run(){
System.out.println(System.currentTimeMillis()+":Thread ID:"+Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyTask task = new MyTask();
ExecutorService es = Executors.newFixedThreadPool(5);
for(int i =0;i<10;i++){
es.submit(task);
}
}
}
计划任务
newScheduledThreadPool(),返回ScheduledExecutorService,可以根据时间对线程进行调度
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
FixedRate,任务调度频率一定,以上个任务开始执行为起点,之后的周期时间,调度下一次任务;FixDelay在上个任务结束后,经过延期后进行任务调度
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(8000);
System.out.println(System.currentTimeMillis()/1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 2, TimeUnit.SECONDS);
}
}
休息时间太长,任务会在上个任务结束后立即执行,如果是Delay,会在休息时间基础上加上调度周期时间
任务出现异常,后续所有执行都会中断
核心线程池的内部实现
内部实际上都是ThreadPoolExecutor实现
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ThreadPoolExecutor最重要的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize,指定核心线程数量;
maximumPoolSize,最大线程数量;
KeepAliveTime,线程池线程数量超过核心线程数量时,多余的空闲线程存活的时间;
unit,KeepAliveTime的单位;
workQueue,任务队列,被提交尚未执行的任务;
threadFactory,线程工厂,用于创建线程,默认即可;
handler,拒绝策略,任务太多来不及处理,如何拒绝任务
workQueue指被提交但未执行的任务队列,是BlockingQueue接口的对象,仅用来存放Runnable对象,有以下几种BlockingQueue
直接提交队列:由SynchronousQueue提供,没有容量,每一个插入操作都要等待一个相应的删除操作,每一个删除操作都要等待相应的插入操作,提交的任务不会真实的保存,总是将新任务提交给线程执行,没有空闲线程,尝试创建新线程,进程数量大于最大值,执行拒绝策略,通常设置最大的maximumPoolSize
有界的任务队列:使用ArrayBlockingQueue,构造函数必须带一个容量参数,表示该队列最大容量 public ArrayBlockingQueue(int capacity),有新任务执行,实际线程小于核心线程数,优先创建新线程,大于则将新任务加入等待队列,等待队列满了,在总线程数不大于最大线程数的情况下,创建新线程,大于,执行拒绝策略
无界任务队列:通过LinkedBlockingQueue实现,除非系统资源耗尽,不存在任务入队失败情况,此时最大线程数失效
优先任务队列:带有执行优先级的队列,通过PriorityBlockingQueue,控制任务的执行先后顺序,特殊的无界队列
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
第五行workerCountOf()获取线程总数,小于核心线程数将任务通过addWorker()直接调度执行,否则第10行workQueue。offer()进入等待队列,失败执行第17行,将任务直接交给线程池,当前线程数达到最大线程数,执行18行拒绝策略
拒绝策略
四种拒绝策略
AbortPolicy策略,直接抛出异常,阻止系统正常工作
CallerRunsPolicy策略,线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务,任务提交线程的性能会急剧下降
DiscardOledestPolicy策略,丢弃最老的一个请求,也就是即将执行的任务,并尝试再次提交当前的任务
DiscardPolicy策略,丢弃无法处理的任务,不予处理
这些策略实现了RejectedExecutionHandler接口,也可以自己扩充此接口
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
public class RejectThreadPoolDemo {
public static class MyTask implements Runnable{
@Override
public void run(){
System.out.println(System.currentTimeMillis()+":Thread ID:"+Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
ExecutorService es = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+" is discard");
}
});
for(int i =0;i<Integer.MAX_VALUE;i++){
es.submit(task);
Thread.sleep(10);
}
}
}
1500819406087:Thread ID:11
1500819406103:Thread ID:12
1500819406119:Thread ID:13
1500819406134:Thread ID:14
1500819406150:Thread ID:15
java.util.concurrent.FutureTask@3d4eac69 is discard
java.util.concurrent.FutureTask@42a57993 is discard
有大量任务被丢弃
自定义线程创建,ThreadFactory
最开始的线程来自ThreadFactory,它是一个接口,只有一个创建线程的方法
Thread newThread(Runnable r);
自定义线程可以跟踪在何时创建多少线程,自定义线程名称,组及优先级等,可以设置线程为守护线程
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
ExecutorService es = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
System.out.println("create "+t);
return t;
}
});
for(int i =0;i<5;i++){
es.submit(task);
}
Thread.sleep(2000);
}
扩展线程池
ThreadPoolExecutor是一个可以扩展的线程池,提供了beforeExecute(),afterExecute(),terminated()三个接口对线程池进行控制
public class ExtThreadPool {
public static class MyTask implements Runnable{
public String name;
public MyTask(String name) {
this.name = name;
}
@Override
public void run(){
System.out.println("正在执行"+":Thread Id:"+Thread.currentThread().getId()+",Task Name="+name);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<Runnable>()){
@Override
protected void beforeExecute(Thread t, Runnable r){
System.out.println("准备执行:"+((MyTask)r).name);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("执行完成:"+((MyTask)r).name);
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
};
for(int i=0;i<5;i++){
MyTask task = new MyTask("hh"+i);
es.execute(task);
Thread.sleep(10);
}
//关闭线程池,会等待所有线程执行完后再关闭线程池,但不会等待所有线程执行完成后再返回,只是发送一个关闭信号,执行后,线程池不能在接收新任务
es.shutdown();
}
}
shutdown:关闭线程池,会等待所有线程执行完后再关闭线程池,但不会等待所有线程执行完成后再返回,只是发送一个关闭信号,执行后,线程池不能在接收新任务
在线程池中寻找堆栈
使用submit()不会打印异常堆栈,可以用execute()或者
Future re = pools.submit();
re.get();,他们都可以得到部分堆栈信息
public class DivTask implements Runnable{
int a,b;
public DivTask(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run(){
double re =a/b;
System.out.println(re);
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadPoolExecutor pools = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
for(int i=0;i<5;i++){
//pools.execute(new DivTask(100, i));
Future re = pools.submit(new DivTask(100, i));
re.get();
}
}
}
对线程池扩展,在调度任务之前,先保存一下提交任务线程的堆栈信息
public class TraceThreadPoolExecutor extends ThreadPoolExecutor{
public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public void execute(Runnable task){
super.execute(wrap(task,clientTrace(),Thread.currentThread().getName()));
}
@Override
public Future<?> submit(Runnable task){
return super.submit(wrap(task,clientTrace(),Thread.currentThread().getName()));
}
private Runnable wrap(final Runnable task,final Exception clientStatus, String name) {
return new Runnable() {
@Override
public void run() {
try {
task.run();//运行任务
} catch (Exception e) {
clientStatus.printStackTrace();
throw e;
}
}
};
}
private Exception clientTrace() {
return new Exception("client stack trace");
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
for(int i=0;i<5;i++){
pools.execute(new DivTask(100, i));
}
}
}
Fork/Join框架
Fork增加分支,join等待分支执行完毕
ForkJoinPool线程池,对于fork方法并不急着开启线程,而是提交给ForkJoinPool线程处理,节省资源;每个线程拥有一个队列,执行完任务的线程可以帮助繁忙的线程,但从任务队列底部开始,原线程从顶部开始
ForkJoinPool的重要接口
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task)
可以向ForkJoinPool线程池提交ForkJoinTask任务,此任务就是支持fork()分解和join()等待的任务,ForkJoinTask有俩个重要的子类,分别是RecursiveAction和RecursiveTask,分别表示没有返回值的任务和可以携带返回值的任务
//计算列的和需要返回值
public class CountTask extends RecursiveTask<Long>{
private static final long serialVersionUID = 1L;
private static final int THRESHOLD = 1000;
private long start;
private long end;
public CountTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
//设置任务分解的模型
boolean canCompute = (end-start)<THRESHOLD;
if(canCompute){
for(long i=start;i<=end;i++){
sum+=i;
}
}else {
//分成100个小任务
long step = (start+end)/100;
List<CountTask> subTasks = new ArrayList<>();
long pos = start;
for(int i = 0;i<100;i++){
long lastOne = pos+step;
if(lastOne>end){
lastOne = end;
}
CountTask subTask = new CountTask(pos, lastOne);
pos+=step+1;
subTasks.add(subTask);
//提交子任务
subTask.fork();
}
for(CountTask t: subTasks){
//等待子任务结束
sum+=t.join();
}
}
return sum;
}
public static void main(String[] args) {
//建立线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
//构建任务
CountTask task = new CountTask(0, 20000L);
//把任务交给线程池
ForkJoinTask<Long> result = forkJoinPool.submit(task);
try {
//获取结果
long res = result.get();
System.out.println("sum="+res);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
任务层次划分很深,会导致线程数量越积越多,性能下降,或者函数调用层次变得很深,最终导致溢出
ForkJoin线程池采用无锁的栈管理空闲线程,工作线程无任务,就会被挂起,压入由线程池维护的栈,有任务,则唤醒这些线程
JDK并发容器
并发集合
ConcurrentHashMap:高效的并发hashMap,线程安全的hashMap
CopyOnWriteArrayList:是List,读多写少的场合,性能很好,远远好于Vector
ConcurrentLinkedQueue:高效并发队列,使用链表实现,线程安全的LinkedList
BlockingQueue:接口,JDK内部通过链表,数组等方式实现这个接口,阻塞队列,适合数据共享的通道
ConcurrentSkipListMap:跳表的实现,是Map,使用跳表的数据结构进行快速查找
Collections工具类可以将任意集合包装成线程安全的
线程安全的HashMap
实现线程安全的HashMap
法一:
Map m = Collections.synchronizedMap(new HashMap<>());
使用委托,将Map相关的功能交给传入的HashMap实现,自己负责保证线程安全,性能不是很好
法二:ConcurrentHashMap
有关List的线程安全
ArrayList和Vector都使用数组作为其内部实现,Vector是线程安全的,ArrayList不是线程安全的,LinkedList使用链表的数据结构,但不是线程安全的
Collections.synchronizedList(new LinkedList<>());
高效读写的队列:深度剖析CollectionLinkedQueue
队列Queue也是数据结构,使用链表作为它的数据结构,
节点node核心
private static class Node<E> {
volatile E item;
volatile Node<E> next;
Item用来表示目标元素,next表示当前Node的下一个元素
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
第一第三个方法表示设置当前的Item,next值,第一个参数为期望值,第二个参数为设置目标值,当前值等于cmp时,将目标设为val
链表的头部和尾部
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
head永远不为空,通过head和succ()后继方法一定能遍历整个链表
final Node<E> succ(Node<E> p) {
Node<E> next = p.next;
return (p == next) ? head : next;
}
tail更新会延迟,每次更新跳跃俩个元素
public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p是最后一个节点
if (p.casNext(null, newNode)) {
// 每俩次更新下
if (p != t)
casTail(t, newNode);
return true;
}
//CAS竞争失败,再次尝试
}
//遇到哨兵节点,从head开始遍历,如果tail被修改,使用tail
else if (p == q) p = (t != (t = tail)) ? t : head;
else
//取下一个节点或最后一个节点
p = (p != t && t != (t = tail)) ? t : q;
}
}
这个方法没有任何锁操作,for循环没有出口,第一次加入元素,队列为空,p.next为空,p的next节点赋值为newNode,,此时p==t成立,不会更新tail末尾,增加一个元素后,tail不会被更新,增加第二个元素时,t还在head位置,p.next指向第一个元素,q!=null,表示q不是最后的节点,往队列中增加元素需要最后一个节点的位置,循环开始查找最后一个节点位置,此时,p实际指向链表中的第一个元素,next为null,在第二个循环中q=null,p更新自己的next,指向新加入的节点,p!=t成功,更新t位置,t移到链表最后,p==q是由于哨兵节点,即next指向自己的节点,主要表示要删除的节点或空节点,可能直接返回head,执行过程中,tail被其他线程修改,新tail作为链表末尾
p = (t != (t = tail)) ? t : head;
执行!=时,先取得左边的t,在执行t=tail,取得新t值,并发时,右边的t被其他线程修改,用新的tail作为链表末尾,即等式右边的t
哨兵节点产生
ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<>();
q.add("1");
q.poll();
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
高效读取CopyOnWriteArrayList
写和写同步等待,写入操作时,进行自我复制,不修改原有内容,对原有数据进行复制,修改的内容写入副本,写完后将副本替换原有数据
读取操作
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
写入操作
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
array是volatile,读取线程会立即知道这个修改
数据共享通道:BlockingQueue
它是一个接口
ArrayBlockingQueue基于数组实现,适合做有界队列,可容纳的最大元素需要在队列创建时指定,LinkedBlockingQueue适合做无界队列或边界值非常大的,因为内部元素可以动态增加,不会因为初始容量很大,耗掉大半内存
BlockingQueue适合作为数据共享的通道因为它会让服务任务在队列为空时等待,有新消息进入队列后,自动唤醒线程
ArrayBlockingQueue的内部元素都放置在一个对象数组中
final Object[] items;
可以用offer()和put()向队列中压入元素,offer在当前队列满时立即返回false,put会等待,直到有空闲位置
弹出元素可以用poll和take,都从队列头部获得一个元素,队列为null,poll返回空,take等待
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
执行take(),队列为空,当前线程等待在notEmpty上,新元素入队时,进行一次notEmpty的通知
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
public void clear() {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
items[i] = null;
if (++i == items.length)
i = 0;
} while (i != putIndex);
takeIndex = putIndex;
count = 0;
if (itrs != null)
itrs.queueIsEmpty();
for (; k > 0 && lock.hasWaiters(notFull); k--)
notFull.signal();
}
} finally {
lock.unlock();
}
}
void removeAt(final int removeIndex) {
// assert lock.getHoldCount() == 1;
// assert items[removeIndex] != null;
// assert removeIndex >= 0 && removeIndex < items.length;
final Object[] items = this.items;
if (removeIndex == takeIndex) {
// removing front item; just advance
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// an "interior" remove
// slide over all others up through putIndex.
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
随机数据结构:跳表SkipList
跳表是一种可以用来快速查找的数据结构,类似平衡树,都可以对元素快速查找,但平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整,对跳表的插入和删除只需要对整个数据结构的局部进行操作,跳表只需要部分锁,平衡树需要全局锁
跳表是随机算法,本质是同时维护多个链表,链表是分层的
最低层的链表维护跳表内所有的元素,每上面一层都是下面一层的子集,一个元素插入哪些层是随机的,所有链表的元素都是排序的,可以从顶级链表开始查找,发现被查找元素大于当前链表的取值,会转入下一层链表继续查找,搜索是跳跃的,
空间换时间的算法
使用跳表实现Map和使用hash实现Map,hash并不会保存元素的顺序,跳表有序
Map<Integer,Integer> map = new ConcurrentSkipListMap<>();
for(int i=0;i<30;i++){
map.put(i, i);
}
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
System.out.println(entry.getKey());
}
一个Node表示一个节点
static final class Node<K,V> {
final K key;
volatile Object value;
volatile Node<K,V> next;
对node的操作使用CAS方法
boolean casValue(Object cmp, Object val) {
return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val);
}
boolean casNext(Node<K,V> cmp, Node<K,V> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
另一个数据结构是Index,内部包装了Node,增加向下和向右的引用
static class Index<K,V> {
final Node<K,V> node;
final Index<K,V> down;
volatile Index<K,V> right;
每一层的表头,需要记录当前处于哪一层,表示链表头部的第一个Index
static final class HeadIndex<K,V> extends Index<K,V> {
final int level;
HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
super(node, down, right);
this.level = level;
}
}