一.Java多线程:锁(死锁、Lock锁、线程池)
1、死锁
多个线程各自占有一些公共资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有两个以上对象的锁的时候,就可能发生死锁的问题
(1)创建相应的方法实现死锁:
//多个线程互相抱着对方需要的资源,然后形成僵持
publicclassDemoLock{}classLipstick{}classMirror{}classMakeupextendsThread{//用static保证资源只有一份
staticLipsticklipstick=newLipstick();staticMirrormirror=newMirror();intchoice;StringgirlName;Makeup(intchoice,StringgirlName){this.choice=choice;this.girlName=girlName;}publicvoidrun(){makeup();}privatevoidmakeup(){//互相持有对方的锁,就是需要拿到对方的资源
if(choice==0){synchronized(lipstick){//获得口红的锁
System.out.println(this.girlName+"获得口红的锁");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}synchronized(mirror){//一秒钟后获得镜子的锁
System.out.println(this.getName()+"获得了镜子的锁");}}}else{synchronized(mirror){//获得镜子的锁
System.out.println(this.girlName+"获得镜子的锁");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}synchronized(lipstick){//一秒钟后获得口红的锁
System.out.println(this.getName()+"获得口红的锁");}}}}}
(2)创建测试类:
创建两个线程:
publicclassTest{publicstaticvoidmain(String[]args){Makeupmakeup1=newMakeup(0,"灰姑娘");Makeupmakeup2=newMakeup(1,"白雪公主");makeup1.start();makeup2.start();}}
(3)测试:
灰姑娘获得口红的锁
白雪公主获得镜子的锁
可以看到,以上两个线程,在获得各自的资源之后,再去获得对方资源的时候,发生了死锁
(4)避免死锁:
privatevoidmakeup(){//互相持有对方的锁,就是需要拿到对方的资源
if(choice==0){synchronized(lipstick){//获得口红的锁
System.out.println(this.girlName+"获得口红的锁");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}}synchronized(mirror){//一秒钟后获得镜子的锁
System.out.println(this.getName()+"获得了镜子的锁");}}else{synchronized(mirror){//获得镜子的锁
System.out.println(this.girlName+"获得镜子的锁");try{Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}}synchronized(lipstick){//一秒钟后获得口红的锁
System.out.println(this.getName()+"获得口红的锁");}}}
不能占有两个资源
2、死锁的预防
(1)破坏请求和保持条件
第一种协议:
破坏请求:一次申请在运行过程中的全部资源
破坏保持:只要有一种资源没有满足进程的要求,就让该进程等待,也就是说在进程等待期间未占用任何资源。
缺点:资源浪费;会发生饥饿现象。
第二种协议:
进程只获得运行初期所需要的资源,运行过程中逐步释放已分配给自己的资源。
(2)破坏不可抢占条件
当一个已经保持了某些不可被抢占资源的进程提出新的资源请求而得不到满足时,必须释放已有资源。
3、Lock锁
(1)在代码中加锁:
publicclassTestLockimplementsRunnable{staticintticketNums=10;privatefinalReentrantLocklock=newReentrantLock();@Overridepublicvoidrun(){while(true){lock.lock();if(ticketNums>0){try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(ticketNums--);}else{break;}lock.unlock();}}}
(2)测试:
publicclassTest{publicstaticvoidmain(String[]args){TestLocktestLock=newTestLock();newThread(testLock,"zhai").start();newThread(testLock,"zhang").start();newThread(testLock,"liu").start();}}10987654321
4、synchronized与Lock的对比
Lock是显式锁(手动开启和关闭锁,不能忘记解锁)synchronized是隐式锁,出了作用域自动释放
Lock是有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,jvm将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性
优先使用顺序:Lock>同步代码块(已经进入了方法体,分配了相应的资源)>同步方法(在方法体之外)
5、线程池
(1)背景
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
(2)思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完时放回池中,可以避免频繁地创建销毁、实现重复利用。类似生活中的公共交通工具。
(3)好处
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
便于线程管理(线程池的大小、最大线程数、线程没有任务时最多保持多长时间会终止)
(4)书写代码
创建一个类,实现Runnable接口:
publicclassMyThreadimplementsRunnable{@Overridepublicvoidrun(){System.out.println(Thread.currentThread().getName());}}
创建测试类:
publicclassTestPool{publicstaticvoidmain(String[]args){ExecutorServiceexecutorService=Executors.newFixedThreadPool(10);executorService.execute(newMyThread());executorService.execute(newMyThread());executorService.execute(newMyThread());executorService.execute(newMyThread());executorService.shutdown();}}
降低资源消耗(可以通过重复利用已创建的线程来降低线程创建和销毁造成的消耗)。
提高相应速度(当任务到达时,任务可以不需要等到线程创建就能立即执行)。
提高线程的可管理性(线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控)。
5、线程池的新建
public class ThreadTest {
/**
* 基于数组的有界阻塞队列
*/
private static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(10);
public static void main(String[] args) {
/**
* 创建一个线程池
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
// corePoolSize
5,
// maximumPoolSize
10,
// keepAliveTime
10L,
// unit
TimeUnit.SECONDS,
// workQueue
arrayBlockingQueue,
// threadFactory
new ThreadFactoryBuilder().setNameFormat("wx-%d").build(),
// handler
new ThreadPoolExecutor.AbortPolicy());
// 上面是创建线程池的代码,下面只是用来测试拒绝策略的
for (int i = 0; i < 30; i++){
threadPoolExecutor.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
Thread.sleep(5000L);
}
});
}
}
}
corePoolSize:线程池核心线程数大小。
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时不再创建。
maximumPoolSize:线程池最大线程数量。
线程池允许创建的最大线程数。如果阻塞队列满了,并且已创建的线程数小于最大线程数,则线程池会在创建新的线程执行任务(如果使用了无界队列,那么这个参数就没什么用了)。
keepAliveTime:线程池中非核心线程空闲的存活时间大小。
unit:线程空闲存活时间单位。
workQueue:存放任务的阻塞队列。
用于保存等待执行的任务的阻塞队列。可以选择以下几个队列:
1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue。
3、SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作。否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
4、PriorityBlockingQueue:一个具有优先级的无限阻塞队列。