1 生产者消费者模式
-
应用场景:
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
-
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件:
- 对于生产者,没有生产产品之前,要通知消费者等待;生产了产品之后,需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
- 在生产者消费者问题,仅有
synchronized
是不够的。synchronized
可阻止并发更新同一个共享资源,实现了同步;但synchronized
不能用来实现不同线程之间的消息传递(通信)
-
Java提供了几个方法解决线程之间的通信问题
方法名 作用 wait()
表示线程一直等待,直到其他线程通知,它与 sleep()
不同,会释放锁wait(long timeout)
指定等待的毫秒数 notify()
唤醒一个处于等待状态的线程 notifyAll()
唤醒同一个对象上所有调用 wait()
方法的线程,优先级高的线程优先调度这些方法均是
Object
类的方法,都只能在同步方法或同步代码块中使用,否则会抛出IllegalMonitorStateException
2 管程法
public class TestPC {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Producer(buffer).start();
new Consumer(buffer).start();
}
}
// 生产者
class Producer extends Thread {
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
buffer.push(new Product(i));
System.out.println("生产了" + i + "件产品");
}
}
}
// 消费者
class Consumer extends Thread {
Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第" + buffer.pop().id + "件产品");
}
}
}
// 产品
class Product {
int id; // 产品编号
public Product(int id) {
this.id = id;
}
}
// 缓冲区
class Buffer {
// 缓冲区大小
Product[] products = new Product[10];
// 缓冲器计数器
int count = 0;
// 生产者放入产品
public synchronized void push(Product product) {
// 如果缓冲区满,需要等待消费者消费
if (count == products.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,需要往缓冲区内加入产品
products[count++] = product;
// 通知消费者消费
this.notifyAll();
}
// 消费者消费产品
public synchronized Product pop() {
// 判断能否消费
if (count == 0) {
// 等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Product result = products[--count];
// 通知生产者生产
this.notifyAll();
return result;
}
}
3 信号灯法
public class TestPC {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
// 生产者——>演员
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
tv.play("电视剧" + i);
} else {
tv.play("广告" + i);
}
}
}
}
// 消费者——>观众
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
// 产品——>节目
class TV {
// 演员表演,观众等待 true
// 观众观看,演员等待 false
boolean flag = true;
String name; // 表演的节目名
// 表演
public synchronized void play(String name) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + name);
// 通知观众观看
this.notifyAll(); // 通知唤醒
this.name = name;
this.flag = !this.flag;
}
// 观看
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:" + name);
// 通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
4 线程池
-
为了减少创建和销毁线程的次数,让每个线程可以多次使用,可根据系统情况调整执行的线程数量;为了防止消耗过多内存,可以使用线程池
-
Java中线程池的顶级接口是
Executor
,ExecutorService
是Executor
的子类,也是真正的线程池接口,它提供了提交任务和关闭线程池等方法 -
ExecutorService
常见子类为**ThreadPoolExecutor
**void execute(Runnable command)
:执行任务/命令,无返回值,一般用于执行Runnable
<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般用于执行Callable
void shutdown()
:关闭连接池
-
ThreadPoolExecutor
拥有一些核心参数:public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockongQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) { throw new IllegalArgumentException(); } if (workQueue == null || threadFactory == null || handler == null) { throw new NullPointerException(); } this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
corePoolSize
(线程池核心线程大小):线程池中会维护一个最小的线程数量,即使这些线程处于空闲状态也不会被销毁,除非设置了allowCoreThreadTimeOut
。这里的最小线程数量即是corePoolSize
maximumPoolSize
(线程池最大线程数量):线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize
来指定keepAliveTime
(空闲线程存活时间):一个线程如果处于空闲状态,且当前线程数大于corePoolSize
,那么在指定时间后该空闲线程就会被销毁unit
(空闲线程存活时间单位):keepAliveTime
的计量单位workQueue
(工作队列):新任务被提交后,会先进入工作队列中,在任务调度时再从队列中取出任务threadFactory
(线程工厂):创建一个新线程时使用的工厂,可以用于设定线程名和是否为守护线程等handler
(拒绝策略):当工作队列中的任务已达到最大限制,且线程池中的线程数量也达到最大限制,此时如果有新任务提交,使用拒绝策略解决
-
JDK中提供了4种工作队列
ArrayBlockingQueue
:基于数组的有界阻塞队列,按FIFO排序。新任务进入时先放置到队列的队尾,**有界数组可以防止资源耗尽的问题。**当线程池中线程数量达到corePoolSize
后,新任务放在该队列的队尾,等待被调度。如果队列已满则创建一个新线程。如果线程数量达到maximumPoolSize
,则执行拒绝策略LinkedBlockingQueue
:基于链表的无界阻塞队列(实际上最大容量为Integer.MAX
),按FIFO排序。线程池中的线程数量达到corePoolSize
后,新任务会一直存入该队列。因此使用该队列时,参数maxPoolSize
其实是不起作用的SynchronousQueue
:不缓存任务的阻塞队列,生产者放入一个任务必须要等到消费者取出这个任务。即新任务进入时不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程。如果线程数量达到maximumPoolSize
,则执行拒绝策略PriorityBlockingQueue
:具有优先级的无界阻塞队列,优先级通过参数Comparator
实现
-
JDK提供了4种拒绝策略:
CallerRunsPolicy
:该策略在调用者线程中直接执行被拒绝任务的run()
方法,如果线程池已经shutdown()
,则直接抛弃该任务AbortPolicy
:该策略直接抛弃任务,并抛出RejectedExecutionException
异常DiscardPolicy
:该策略直接抛弃任务,什么都不做DiscardOldestPolicy
:该策略抛弃进入队列最早的任务,然后尝试将这次拒绝的任务放入队列
-
Java中定义了
Executors
工具类用来方便创建各种常用线程池newSingleThreadExecutor()
:单线程线程池,只有一个线程在工作,所有任务按照顺序执行newFixedThreadPool()
:定长线程池,最大线程数等于核心线程数newCachedThreadPool()
:可缓存线程池,核心线程为0,最大线程为Integer.MAX_VALUE
,线程处于空闲状态超过60s会被销毁,采用SynchronousQueue
作为阻塞队列newScheduledThreadPool()
:支持定时的定长线程池,核心线程数固定,最大线程为Integer.MAX_VALUE
,使用DelayedQueue
作为阻塞队列,实现延迟和定时的功能
-
public class TestPool { public static void main(String[] args) { // 创建服务,创建线程池 ExecutorService service = Executors.newFixedThreadPool(10); // 执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); // 关闭连接 service.shutdown(); } } class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + i); } } }