2.4 线程的通信
- void wait() 令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
- void notify() 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- void notifyAll() 唤醒正在等待对象监视器的所有线程
注意:
- 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
- 因为这三个方法必须由锁对象(同步监视器)调用,this可以省略,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
2.4.1 生产者和消费者
- 消费者等待
- 消费者:判断桌子上是否有商品,没有就等待
- 生产者:生产产品,把产品放到桌子上,叫醒等待得消费者来取产品
- 生产者等待
- 消费者:判断桌子上是否有产品,如果没有就等待,如果有就取走,取走之后桌子就空了,叫醒等待得生产者继续生成
- 生产者:判断桌子上是否有产品,如果有就等待,如果没有才生产,把产品放到桌子上。叫醒等待的消费者来取产品
public class Demo {
public static void main(String[] args) {
Desk desk = new Desk(false);
Foodie foodie = new Foodie(desk);
Cooker cooker = new Cooker(desk);
new Thread(foodie).start();
new Thread(cooker).start();
}
}
class Foodie implements Runnable {
private Desk desk;
public Foodie(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while (true) {
synchronized (desk.getLock()) {
if (desk.isFlag()) {
System.out.println("购买食品");
desk.setFlag(false);
desk.getLock().notifyAll();
} else {
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Cooker implements Runnable {
private Desk desk;
public Cooker(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while (true) {
synchronized (desk.getLock()) {
if (!desk.isFlag()) {
System.out.println("生产食品");
desk.setFlag(true);
desk.getLock().notifyAll();
} else {
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Desk {
private boolean isFlag;
private Object lock = new Object();
public Desk(boolean isFlag) {
this.isFlag = isFlag;
}
public boolean isFlag() {
return isFlag;
}
public void setFlag(boolean flag) {
isFlag = flag;
}
public Object getLock() {
return lock;
}
}
三、线程池
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
好处:
- 提高响应速度
- 降低资源消耗
- 便于线程的管理
3.1 创建线程池-Executors
- 使用Executors静态方法,创建线程池
- submit方法 注:池子会帮忙创建对象,任务执行完毕,也会自动把线程归还给线程池
- 关闭连接池,shutdown方法
3.1.1 Executors
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
创建线程池方法
newCachedThreadPool()
:创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。newFixedThreadPool(int nThreads)
:创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。 参数可以规定最多可以容纳多少个线程Executors.newSingleThreadExecutor()
:创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n)
:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般又来执行Callablevoid shutdown()
:关闭连接池
public class ThreadPoolDemo1 {
public static void main(String[] args) throws InterruptedException {
// 创建默认线程池对象,池子默认是空的,默认最多可以容纳int类型的最大值
ExecutorService executorService = Executors.newCachedThreadPool();
// Executors --- 可以帮助创建线程池对象
// ExecutorService --- 控制线程池对象
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "在执行了");
});
Thread.sleep(2000);
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.shutdown();
}
}
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.shutdown();
}
}
public class Demo {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
// 设置线程池属性
ThreadPoolExecutor s = (ThreadPoolExecutor)service;
s.setCorePoolSize(15);
// s.setKeepAliveTime();
service.execute(new MyRunnable());
MyCallable myCallable = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(myCallable);
service.submit(ft);
Integer integer;
try {
integer = ft.get();
System.out.println(integer);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
service.shutdown();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}
class MyCallable implements Callable<Integer>{
@Override
public Integer call(){
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
return 1;
}
}
3.2 创建线程池-ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
:创建一个新 ThreadPoolExecutor给定的初始参数。
注:- 参数一:核心线程数量
- 参数二:最大线程数
- 参数三:空闲线程最大存活时间
- 参数四:时间单位(从TimeUnit里面获取单位),----TimeUnit
- 参数五:任务队列, — 让任务队列中等待,等有线程空闲了,再从这个队列中获取任务并且执行
- 参数六:创建线程工厂,— 按照默认的方式创建线程对象
- 参数七:任务的拒绝策略 — ① 什么适合拒绝 当提交的任务 > 池子中最大线程数量 + 队列容量 ② 如何拒绝 任务拒绝策略
// 参数一:核心线程数量 不能小于0
// 参数二:最大线程数 不能小于0,最大数量>=核心线程数量
// 参数三:空闲线程最大存活时间 不能小于0
// 参数四:时间单位
// 参数五:任务队列 不能为Null
// 参数六:创建线程工厂 不能为Null
// 参数七:任务的拒绝策略 不能为Null
public class Demo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}
面试题
1. sleep()和wait()的异同
-
相同点:一旦执行方法,都可有使得当前的线程进入阻塞状态
-
不同点:
- 俩个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用的范围不同:sleep()可以再任何需要得场景下调用。wait()必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果俩个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
2.synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)