public class DeadLockTest { private static Object work = new Object(); private static Object money = new Object(); public static void main(String[]args){ Thread worker = new Thread(new Runnable() { //匿名内部类 @Override public void run() { synchronized (work) { System.out.println("先给我钱,我再工作"); synchronized (money) { System.out.println("给我钱"); } } } }); Thread boss = new Thread(new Runnable() { @Override public void run() { synchronized (money) { System.out.println("先给我干活,在给钱"); synchronized (work) { System.out.println("给我干活"); } } } }); worker.start(); boss.start(); } }
死锁:
产生条件:以下四种条件同时满足才会导致死锁
1、互斥条件:
共享资源只能同时被一个线程占用
2、占用且等待
eg:拿到woker锁,不释放的同时去申请money锁
3、不可抢占:
线程Thread1拿到对象锁后,其他线程无法进行抢占锁
4、循环等待:
线程T1拿到了资源X的锁,去申请Y的锁
线程T2拿到了资源Y的锁,去申请X的锁
银行家算法解决死锁问题
ThreadLocal-线程本地变量
属于线程私有资源不与其他线程共享
set() 设置线程私有属性值
get()取得线程私有属性值
public class ThreadLocalTest { private static String commStr; private static ThreadLocal
threadLocalStr = new ThreadLocal<>(); public static void main(String[]args) throws InterruptedException { //主线程 commStr = "main-commStr"; threadLocalStr.set("main-ThreadLocalStr"); Thread thread = new Thread(new Runnable() { @Override public void run() { //子线程 commStr = "thread0-commStr"; threadLocalStr.set("thread0-ThreadLocalStr"); } }); thread.start(); thread.join(); System.out.println(commStr); System.out.println(threadLocalStr.get()); } } 每个Thread对象内部都有一个ThreadLocal.ThreadLocalMap对象(存放元素)
该对象属于每个线程自己,因此其保存的内容也是线程私有,在多线程场景下并不共享。
生产消费者模型
多线程三大核心问题:
分工
互斥
同步(线程间通信)
Object:wait() ,notify()必须搭配synchronized使用
使用wait() ,notify()有一个前提,必须在同步方法或同步代码块中使用
必须拿到相应的锁才能调用,否则抛出IllegalMonitorStateException
wait():痴汉方法
持有锁的线程调用wait()方法后会一直阻塞,直到有别的线程调用notify()将其唤醒
public final native void wait (long time):、等待一段时间,若还未被唤醒,继续执行,默认单位为毫秒
notify():
唤醒任意一个处于wait的线程
任意一个Object及其子类对象都有两个队列
同步队列:所有尝试获取该对象Monitor失败的线程,都加入同步队列,排队获取锁
等待队列:已经拿到锁的线程在等待其他资源时,主动释放锁,置入该对象的等待队列中,等待被唤醒。
当调用notify()会在等待队列中任意唤醒一个线程,将其置入同步队列尾部,排队获取锁。
notifyAll() :将等待队列中的所有线程唤醒,并且加入同步队列。
生产—消费者模型
设计模式:先找到第三方
生产者
消费者
例子1:一个生产者,一个消费者消费一件商品
class Goods { private String goodsName; private int count; @Override public String toString() { return "Goods{" + "goodsName='" + goodsName + '\'' + ", count=" + count + '}'; } protected synchronized void set(String goodsName) { if (count == 1) { System.out.println("此时还有商品,需要消费者消费了再继续生产"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.goodsName = goodsName; count++; System.out.println(Thread.currentThread().getName()+"生产" +this); //唤醒消费者线程 this.notify(); } public synchronized void get() { if (count == 0) { System.out.println("商品已售完,需要生产者上架商品"); //需要让消费者阻塞 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count--; System.out.println(Thread.currentThread().getName()+"消费" +this); //唤醒生产者线程 this.notify(); } } class Producer implements Runnable{ private Goods goods; public Producer(Goods goods) { this.goods = goods; } @Override public void run() { this.goods.set("一个小黑瓶"); } } class Consumer implements Runnable{ private Goods goods; public Consumer(Goods goods) { this.goods = goods; } @Override public void run() { this.goods.get(); } } public class CPTest { public static void main(String[]args) throws InterruptedException { Goods goods = new Goods(); Producer producer = new Producer(goods); Consumer consumer = new Consumer(goods); Thread produceThread = new Thread(producer,"生产者"); Thread consumeThread = new Thread(consumer,"消费者"); //produceThread.start(); consumeThread.start(); Thread.sleep(1000); //consumeThread.start(); produceThread.start(); } }
//改进,让其一直生产消费,只需改下面两段代码 @Override public void run() { while (true) { this.goods.set("一个小黑瓶"); } } @Override public void run() { while (true) { this.goods.get(); } }
例子2:多个生产、消费者消费同一件商品
class Goods { private String goodsName; private int count; @Override public String toString() { return "Goods{" + "goodsName='" + goodsName + '\'' + ", count=" + count + '}'; } protected synchronized void set(String goodsName) { while (count == 1) { System.out.println("此时还有商品,需要消费者消费了再继续生产"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.goodsName = goodsName; count++; System.out.println(Thread.currentThread().getName()+"生产" +this); //唤醒消费者线程 this.notifyAll(); } public synchronized void get() { while (count == 0) { System.out.println("商品已售完,需要生产者上架商品"); //需要让消费者阻塞 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count--; System.out.println(Thread.currentThread().getName()+"消费" +this); //唤醒生产者线程 this.notifyAll(); } } class Producer implements Runnable{ private Goods goods; public Producer(Goods goods) { this.goods = goods; } @Override public void run() { while (true) { this.goods.set("一个小黑瓶"); } } } class Consumer implements Runnable{ private Goods goods; public Consumer(Goods goods) { this.goods = goods; } @Override public void run() { while (true) { this.goods.get(); } } } public class CPTest { public static void main(String[]args) throws InterruptedException { Goods goods = new Goods(); Producer producer = new Producer(goods); Consumer consumer = new Consumer(goods); Thread produceThread = new Thread(producer,"生产者1"); Thread produceThread1 = new Thread(producer,"生产者2"); Thread produceThread2 = new Thread(producer,"生产者3"); Thread consumeThread = new Thread(consumer,"消费者1"); Thread consumeThread1 = new Thread(consumer,"消费者2"); Thread consumeThread2= new Thread(consumer,"消费者3"); Thread consumeThread3 = new Thread(consumer,"消费者4"); consumeThread.start(); produceThread.start(); produceThread1.start(); produceThread2.start(); consumeThread1.start(); consumeThread2.start(); consumeThread3.start(); } }
例子3:多个生产、消费者消费多件商品
class Goods { private String goodsName; private int count; private int MaxCount; public Goods(int maxCount) { MaxCount = maxCount; } @Override public String toString() { return "Goods{" + "goodsName='" + goodsName + '\'' + ", count=" + count + '}'; } protected synchronized void set(String goodsName) { while (count == MaxCount) { System.out.println("此时还有商品,需要消费者消费了再继续生产"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.goodsName = goodsName; count++; System.out.println(Thread.currentThread().getName()+"生产" +this); //唤醒消费者线程 this.notifyAll(); } public synchronized void get() { while (count == 0) { System.out.println("商品已售完,需要生产者上架商品"); //需要让消费者阻塞 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count--; System.out.println(Thread.currentThread().getName()+"消费" +this); //唤醒生产者线程 this.notifyAll(); } } class Producer implements Runnable{ private Goods goods; public Producer(Goods goods) { this.goods = goods; } @Override public void run() { while (true) { this.goods.set("一个小黑瓶"); } } } class Consumer implements Runnable{ private Goods goods; public Consumer(Goods goods) { this.goods = goods; } @Override public void run() { while (true) { this.goods.get(); } } } public class CPTest { public static void main(String[]args) throws InterruptedException { Goods goods = new Goods(10); Producer producer = new Producer(goods); Consumer consumer = new Consumer(goods); //产生20个生产者 // List
list = new ArrayList<>(goods); List list = new ArrayList<>(); for (int i = 0; i < 20; i++) { list.add(new Thread(producer,"生产者"+(i+1))); } //产生30个消费者 for (int i = 0; i < 30; i++) { list.add(new Thread(consumer,"消费者"+(i+1))); } //启动所有生产者,消费者 for (Thread thread :list) { thread.start(); } } }
公平锁 lock
解决死锁问题
等待时间最长的线程先获取到锁
可重入锁:
public synchronized void testA() { //线程1 testB(); } public synchronized void testB() { //线程2 wait }
sychronized有可重入锁
Lock-JDLock-JDK1.5基于java语言层面实现的线程“锁”
产生背景:synchronized 死锁
synchronized 阻塞式获取锁,获取不到就一直阻塞下去。
破坏不可抢占(死锁条件之一):
1、不响应中断 interrupt() ——void lockInerruptibly();
2、支持超时操作——boolean tryLock(long time,TimeUnit unit)
3、非阻塞式获取锁 线程若获取不到锁,线程直接退出——boolean tryLock();
Lock体系使用的格式
try {
//加锁
lock.lock();
}finally {
//解锁
lock.unlock();
}
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Task implements Runnable { private int ticket = 20; private Lock ticketLock = new ReentrantLock(); @Override public void run() { for (int i = 0; i < 20; i++) { //需要对程序上锁 try { //等同于synchronized(this) ticketLock.lock(); if (ticket > 0) { System.out.println(Thread.currentThread().getName() +"还剩下"+ticket--+"票"); Thread.sleep(20); } } catch (InterruptedException e) { e.printStackTrace(); } finally { ticketLock.unlock(); } if (ticket >0) System.out.println(Thread.currentThread().getName()+ "还剩下"+ticket--+"票"); } } } public class LockTest { public static void main(String[]args){ Task task = new Task(); Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); Thread thread3 = new Thread(task); thread1.start(); thread2.start(); thread3.start(); } }
破坏方法一:响应中断 interrupt() ——void lockInerruptibly(); (当线程持有一个锁的时候,外部通过调用interrupt方法打断它,抛出一个中断异常,在catch块中执行相应的逻辑)
class TaskInterrupt implements Runnable { private Lock lock = new ReentrantLock(); @Override public void run() { try { while (true) { lock.lockInterruptibly(); } } catch (Exception e) { System.out.println("线程响应中断"); return; } finally { lock.unlock(); } } } public class LockInterrupt { public static void main(String[]args){ TaskInterrupt taskInterrupt = new TaskInterrupt(); Thread thread = new Thread(taskInterrupt); thread.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); } }
破坏方法二:支持超时
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class TaskTime implements Runnable { private Lock lock = new ReentrantLock(); @Override public void run() { testA(); } public void testA() { try { if (lock.tryLock(1,TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName()+ "获取锁成功"); Thread.sleep(2000); } else { System.out.println(Thread.currentThread().getName()+ "获取锁失败"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class LockTime { public static void main(String[]args){ TaskTime taskTime = new TaskTime(); Thread thread1 = new Thread(taskTime,"线程A"); Thread thread2 = new Thread(taskTime,"线程B"); thread2.start(); thread1.start(); } }
独占锁:synchronized,在任意一个时刻,只有一个线程可以拥有此锁
共享锁:在同一时刻,可以有多个线程拥有锁
读写锁——共享锁的一种实现
读锁共享 :多个线程可以同时拿到读锁进行访问,当写线程开始工作时,所有的读线程全部阻塞
写锁独占:任意一个时刻,只有一个线程可以拿到写锁
Condition:Lock体系线程通信方式,类比Object wait与notify
await() 释放lock锁,将线程置于等待队列阻塞
signal() 随机唤醒一个处于等待的线程
signalAll() :唤醒所有等待的线程
小结:
java中实现线程“锁”的方式:synchronized 与 lock
1、synchronized 与 lock都是对象锁,都支持可重入锁,
区别:
2、lock可以实现synchronized不具备的特性,如响应中断,支持超时,非阻塞的获取锁,公平锁,共享锁(读写锁,ReentrantReaderWriteLock实现)
3、Lock的体系的Condition等待队列可以有多个(区分于synchronized只有一个等待队列),可以进一步提高效率,减少线程阻塞与唤醒带来的开销(唤醒不该唤醒额线程)
获取一个lock锁的condition队列:
lock.newCondition():产生一个新的Condition队列
到底用synchronized还是lock?
1、若没有特殊的应用场景,推荐使用synchronized,其使用方便(隐式加减锁),并且由于synchronized是JVM层面的实现,在之后的JDK还有对其优化的空间
2、若要使用公平锁,读写锁,超时获取锁等特殊场景,才会考虑使用Lock
FutureTask:可以保证多线程场景下,任务只会被执行一次,其他线程不在执行此任务。
Future接口中的get方法会阻塞当前线程直到取得Callable的返回值。
线程池:
class CallableTest implements Callable
{ private int ticket = 20; @Override public String call() throws Exception { for (int i = 0; i < 20; i++) { if (ticket > 0) { System.out.println(Thread.currentThread().getName()+ "还剩下"+ticket--+"票"); } } return Thread.currentThread().getName()+"票卖完了"; } } public class ExecutorTest { public static void main(String[]args) throws ExecutionException, InterruptedException { //创建一个线程池对象 ExecutorService executorService = new ThreadPoolExecutor(2, 3,60,TimeUnit.SECONDS, new LinkedBlockingDeque<>()); CallableTest callableTest = new CallableTest(); for (int i = 0; i < 5; i++) { //executorService.execute();不能用execute启动,execute只能接收Runnable接口 executorService.submit(callableTest); //结果用Future接口接收 //Future submit = executorService.submit(callableTest); //Future接口中的get方法会阻塞当前线程直到取得Callable的返回值。 //--------main一直卡在此处,此时只创建了一个线程----- // System.out.println(submit.get()); } executorService.shutdown(); } } 线程池的核心两大接口
ExecutorService:普通线程池
-void execute(Runnable command);
-
Future submit(Callable task); ScheduledExecutorService:定时线程池
public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
线程池的一个核心类:
ThreadPoolExecutor:是普通线程池ExecutorService的一个子类
创建线程的方法,推荐使用线程池来创建线程
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, RejectedExecutionHandler handler) 1)corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其
他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调
用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
2)runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。
·ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
·LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于
ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
·SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操
作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool
使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
3)maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小
于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么
效果。
4) keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且
每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
5) TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫
秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微
秒)。
6) RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种
策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线
程池框架提供了以下4种策略。 ·AbortPolicy:直接抛出异常。(默认采用此策略) ·CallerRunsPolicy:只用调用者所在
线程来运行任务。 ·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 ·DiscardPolicy:不处
理,丢弃掉。
线程池工作流程:
1、当一个任务提交给线程池时,首先判断核心线程池的数量是否达到corePoolSize,若未达到,线程池创建新的线程执行任务并将其置入核心池中,否则,判断线程池是否有空闲线程,若有,则分配任务执行,否则进入步骤2
2、判断当前线程池中数量有没有达到线程池的最大数量(maximumPoolSize),若没有,创建新的线程执行任务并将其置入步骤3
3、判断阻塞队列是否已满,若未满,将任务置入阻塞队列中等待调度,否则进入步骤4
4、调用相应的拒绝策略打回任务(四种拒绝策略,默认抛出异常给用户AbortPolicy)
核心线程池:
最大线程池:
阻塞队列:
打回策略:
关闭线程池:shutdown方法
使用线程池创建线程的好处:
第一:线程池中的线程被包装为Worker工作线程,具备可重复执行任务的能力,降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。
第三:提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控
合理配置线程池
配置核心池以及最大线程池数量:
CPU密集型任务(大数运算):NCPU(电脑CPU数量)+1//当前操作系统中能同时运行的最大线程数量 System.out.println(Runtime.getRuntime().availableProcessors());
IO密集型任务:2*NCPU
内置的四大线程池:
Exectors - 线程池的工具类
1、固定大小线程池:Executors.newFixedThreadPool(int nThread)
适用于负载较重的服务器(配置较低),来满足分配资源的要求
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
()); } class RunnnableTest implements Runnable { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName()+ "、"+i); } } } public class ExecutorTest1 { public static void main(String[]args){ ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executorService.submit(new RunnnableTest()); } } }
2、单线程池-只有一个线程
当多线程场景下需要让任务串行执行的情况下,采用此方法
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
())); 3、缓存线程池:
一般用于较轻的服务器,或执行很多短期的异步任务。
Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue
()); } 当任务提交速度 > 线程执行速度,会不断创建新线程,有可能无限创建线程将内存写满。
当任务提交速度 < 线程执行速度,只会创建若干个有限线程。
class RunableTest implements Runnable{ @Override public void run() { for (int i = 0; i < 3; i++) { /* try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }*/ System.out.println(Thread.currentThread().getName()+ "、"+i); } } } public class ExecutorTest2 { public static void main(String[]args){ ExecutorService executorService = Executors.newCachedThreadPool(); //缓存线程池 for (int i = 0; i < 5; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } executorService.submit(new RunableTest()); } executorService.shutdown(); } } //比较在任务提交阶段和执行阶段时间长短的结果
4、定时调度池
(1)延迟delay个单元后创建nThreads个线程,执行command任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
class RunableTest implements Runnable{ @Override public void run() { for (int i = 0; i < 3; i++) { /* try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }*/ System.out.println(Thread.currentThread().getName()+ "、"+i); } } } public class ExecutorTest2 { public static void main(String[]args){ ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3); for (int i = 0; i < 5; i++) { //延迟两秒后执行任务 executorService.schedule(new RunableTest(),2,TimeUnit.SECONDS); } executorService.shutdown(); } }
(2)延迟delay个单元后每隔period时间单元就执行一次command任务
public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);