线程通信
应用场景:生产者和消费者问问题
• 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
• 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
• 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
解决线程通信的三个方法
wait() 表示线程一直等待
wait(long timeout) 线程等待指定毫秒参数的时间
wait(long timeout,int nanos) 线程等待指定毫秒数、微妙的时间
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程先运行
完整的线程生命周期
阻塞状态有三种
- 普通的阻塞sleep,join,Scanner input.next()
- 同步阻塞(锁池队列) 没有获取同步监视器的线程的队列
- 等待阻塞(阻塞队列) 被调用了wait()后释放锁,然后进行该队列
- 区别1:sleep():线程会让出CPU进入阻塞状态,但不会释放对象锁;wait():线程会让出CPU进入阻塞状态,也会放弃对象锁,进入等待此对象的等待锁定池
- 区别2:wait只能在同步控制方法或同步控制块里面使用,而sleep可以在任何地方使用
生产者与消费者案例实现(Synchronized)
同步方法下实现线程通信
/**
* 线程通信B
* 线程通信的细节 基于synchronized下的线程通信
* 【A】线程通信必须在锁情况下进行
* 【B】进行线程通信的多个线程,要使用同一个线程监视器(product),
* 还必须要调用该同步监视器的wait()、notify()、notifyAll()
*
* 1.wait() 会一直等待下去 直到唤醒操作执行
* Thread.sleep(); 执行的时候没有释放锁
* product.wait(); 等待的时候是释放了锁资源
*
* 2.notify()、notifyAll() 唤醒
* notify():随机唤醒一个线程
* notifyAll():唤醒所有等待线程
*
* 基于synchronized实现线程通信问题:
* 基于多个生产者和消费者的情况下,进行线程唤醒的时候没有办法确定唤醒哪一个生产者和消费者
* 因为这个唤醒的过程是随机的,所以非常可能造成想要唤醒生产者的时候却唤醒了消费者
*/
public class TestB {
public static void main(String[] args) {
Product product = new Product();
//开启生产者线程
new Thread(new ProduceRunnable2(product)).start();
//开启消费者线程
new Thread(new ConsumeRunnable2(product)).start();
}
}
class ProduceRunnable2 implements Runnable{
//这样操作的目的就是保证生产者和消费者使用的同一件商品
private Product product;
public ProduceRunnable2(Product product) {
this.product = product;
}
@Override
public void run() {
int num=0;
while (true){
synchronized (product){
if (product.isFlag()){//如果库存为true,证明有库存
try {
product.wait(); //使当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (num%2==0){
product.setColor("白色");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("馒头");
}else {
product.setColor("黄色");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("玉米饼");
}
System.out.println("生产者生产了:"+product.getColor()+" "+product.getName());
num++;
//证明商品已经生产出来了
product.setFlag(true);
//通知消费者消费
product.notify();
}
}
}
}
class ConsumeRunnable2 implements Runnable{
private Product product;
public ConsumeRunnable2(Product product) {
this.product = product;
}
@Override
public void run() {
while (true){
synchronized (product) {
if (!product.isFlag()){//如果当前库存中没有商品
try {
product.wait();//没有商品就等待生产者生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了:" + product.getColor() + " " + product.getName());
//消费完成了
product.setFlag(false);
//通知生产者生产
product.notify();
}
}
}
}
Lock锁下实现线程通信
/**
*
* 基于Lock锁下实现线程通信
* 【1】基于lock锁的线程通信也必须在锁情况下实现
* 【2】使用同一个lock创建出等待队列
* Condition produceContion=lock.newCondition();
* 【3】await(); signal(); signalAll();
*/
public class TestA {
public static void main(String[] args) {
Product product = new Product();
//创建锁对象
Lock lock = new ReentrantLock();
//创建等待的队列
Condition produceCondition = lock.newCondition();
Condition consumeCondition = lock.newCondition();
new Thread(new ProduceRunnable(product,lock,produceCondition,consumeCondition)).start();
new Thread(new ConsumeRunnable(product,lock,produceCondition,consumeCondition)).start();
}
}
class ProduceRunnable implements Runnable {
private Product product;
private Lock lock;
private Condition produceCondition;
private Condition consumeCondition;
public ProduceRunnable(Product product, Lock lock, Condition produceCondition, Condition consumeCondition) {
this.product = product;
this.lock = lock;
this.produceCondition = produceCondition;
this.consumeCondition = consumeCondition;
}
@Override
public void run() {
int num = 0;
while (true) {
lock.lock();
if (product.isFlag()) {
//如果当前if可以进入 证明当前有库存,使当前线程等待 生产者等待
try {
produceCondition.await(); //把当前线程放到生产者队列中等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (num % 2 == 0) {
product.setColor("白色");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("馒头");
} else {
product.setColor("黄色");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("玉米饼");
}
System.out.println("生产者生产了:" + product.getColor() + " " + product.getName());
num++;
//生产商品结束
product.setFlag(true);
//通知消费者队列进行消费
consumeCondition.signal();//随机唤醒一个消费者等待线程
consumeCondition.signalAll();//唤醒消费者队列所有等待线程
lock.unlock();
}
}
}
class ConsumeRunnable implements Runnable {
//这样操作的目的就是保证生产者和消费者使用的是同一件商品
private Product product;
private Lock lock;
private Condition produceCondition;
private Condition consumeCondition;
public ConsumeRunnable(Product product, Lock lock, Condition produceCondition, Condition consumeCondition) {
this.product = product;
this.lock = lock;
this.produceCondition = produceCondition;
this.consumeCondition = consumeCondition;
}
@Override
public void run() {
while (true) {
lock.lock();
if (!product.isFlag()) {
try {
consumeCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了:" + product.getColor() + " " + product.getName());
//消费结束
product.setFlag(false);
//去生产者队列中通知生产者生产
produceCondition.signal();
lock.unlock();
}
}
}
Condition
Condition是在Java1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify()、使用Conditionde await()、signal()这种方式实现线程间协作更加安全和高效。
它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。一个Condition包含一个等待队列,一个Lock可以产生多个Condition,所以可以有多个等待队列。
在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。
调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock()之间才可以使用
- Condition中的await()对应Object的wait();
- Condition中的signal()对应Object的notify();
- Condition中的signalAll()对应Object的notifyAll()。
signal()
唤醒一个等待线程
如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从await返回之前,该线程必须重新获取锁。
signalAll()
唤醒所有等待线程
如果所有的线程都在等待此条件,则唤醒所有线程。在从await()返回之前,每个线程都必须重新获取锁。
LockSupport锁下线程通信
public class Product {
private String color;// 白色 黄色
private String name;// 馒头 玉米饼
private boolean flag=false;//库存信号 默认的是没有库存
private ProduceRunnable produceRunnable;
private ConsumeRunnable consumeRunnable;
public ProduceRunnable getProduceRunnable() {
return produceRunnable;
}
public void setProduceRunnable(ProduceRunnable produceRunnable) {
this.produceRunnable = produceRunnable;
}
public ConsumeRunnable getConsumeRunnable() {
return consumeRunnable;
}
public void setConsumeRunnable(ConsumeRunnable consumeRunnable) {
this.consumeRunnable = consumeRunnable;
}
public Product(){}
public Product(String color, String name) {
this.color = color;
this.name = name;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product{" +
"color='" + color + '\'' +
", name='" + name + '\'' +
'}';
}
}
/**
*
* 基于LockSupport锁下实现线程通信:
*
* 不需要加锁
* park() 等待
* unpark(Thread th) 唤醒
*
*
*/
public class Test01 {
public static void main(String[] args) {
Product product=new Product();
ProduceRunnable produceRunnable = new ProduceRunnable();
ConsumeRunnable consumeRunnable = new ConsumeRunnable();
product.setProduceRunnable(produceRunnable);
product.setConsumeRunnable(consumeRunnable);
produceRunnable.setProduct(product);
consumeRunnable.setProduct(product);
//开启生产者线程
produceRunnable.start();
//开启消费者线程
consumeRunnable.start();
}
}
//生产者类
class ProduceRunnable extends Thread {
private Product product;
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
int num=0;
while (true){
if(product.isFlag()){
//证明当前有库存,使当前线程处于等待状态
LockSupport.park();
}
if(num%2==0){
product.setColor("白色");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("馒头");
}else{
product.setColor("黄色");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("玉米饼");
}
System.out.println("生产者生产了:"+product.getColor()+" "+product.getName());
num++;
//生产结束
product.setFlag(true);
//唤醒消费者线程
LockSupport.unpark(product.getConsumeRunnable());
}
}
}
//消费者线程
class ConsumeRunnable extends Thread{
//这样操作的目的就是保证生产者和消费者使用的同一件商品
private Product product;
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
while (true){
if(!product.isFlag()){
LockSupport.park();
}
System.out.println("消费者消费了:"+product.getColor()+" "+product.getName());
product.setFlag(false);
//唤醒生产者生产
LockSupport.unpark(product.getProduceRunnable());
}
}
}
三种线程通信方式对比
- 对于Object中wait和notify:必须在锁中使用,这里指synchronized,并且wait方法要先于notify,否则wait方法会一致阻塞该线程
- Lock中lock.newCondition:condition.await()和condition.signal():必须在锁中使用,这里指lock.lock()之后,并且await方法要先于signal,否则await方法会一直阻塞该线程。
- LockSupport优势,优势1:不需要再锁中进行;优势二:park方法可以不先于unpark方法
线程池
什么是线程池?
- 创建好销毁对象是非常耗费时间的
- 创建对象:需要分配内存等资源
- 小慧对象:虽然不需要程序员操心,但是垃圾回收器会在后台一直跟踪并销毁
- 对于经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:创建好多个线程,放入线程池中,使用时直接获取引用,不使用时放回池中。可以避免频繁创建销毁、实现重复利用
- 技术案例:线程池、数据库连接池
- JDK1.5起,提供了内置线程池
线程池的好处
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 提高线程的可管理性:避免线程无限制创建、从而消耗系统资源,降低系统稳定性,甚至内存溢出或者CPU耗尽
线程池的应用场合
- 需要大量线程,并且完成任务的时间短
- 对性能要求苛刻
- 接收突发性的大量请求
使用线程池执行大量的Runnable命令
public class TestThreadPool1 {
public static void main(String[] args) {
//创建一个线程池
//创建一个线程池,池中只有1个线程,保证线程一直存在
//ExecutorService pool = Executors.newSingleThreadExecutor();
//创建一个线程池,池中有固定数量的线程
//ExecutorService pool = Executors.newFixedThreadPool(10);
//创建一个线程池,池中线程的数量可以动态变化
ExecutorService pool = Executors.newCachedThreadPool();
//使用线程池执行大量的Runnable命令
for(int i=0;i<20;i++){
final int n = i;
//指定Runnable命令
Runnable command = new Runnable(){
public void run() {
System.out.println("开始执行"+n);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行结束"+n);
}
};
//不需要new Thread(command);
//使用线程池执行命令
pool.execute(command);
}
//关闭线程池
pool.shutdown();
}
}
//class MyRunnable implements Runnable{
// public void run() {
// System.out.println("开始执行");
// System.out.println("执行结束");
// }
//}
使用线程池执行大量的Callable任务
public class TestThreadPool2 {
public static void main(String[] args)
throws InterruptedException, ExecutionException {
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
//使用线程池
List<Future> list = new ArrayList<Future>();
for(int i=0;i<20000;i++){
//指定一个Callable任务
Callable<Integer> task = new MyCallable();
//将任务交给线程池
Future<Integer> future = pool.submit(task);
//将Future加入到List
list.add(future);
}
System.out.println("ok?");
//遍历并输出多个结果
for(Future f:list){
System.out.println(f.get());
}
System.out.println("OK!");
//关闭线程池
pool.shutdown();
}
}
class MyCallable implements Callable<Integer>{
public Integer call() throws Exception {
Thread.sleep(2000);
return new Random().nextInt(10);
}
}
线程池API总结
-
Executor:线程池顶级接口,只有一个方法
-
ExecutorService:真正的线程池接口
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Futuresubmit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭线程池
-
AbstractExecutorService:基本实现了ExecutorService的所有方法
- l ThreadPoolExecutor:默认的线程池实现类
- ScheduledThreadPoolExecutor:实现周期性任务调度的线程池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
ForkJoin框架
Fork/Join框架是JAVA7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果得到大任务结果的框架
工作窃取算法
一个大任务拆分成多个小任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列中,并且每个队列都有单独的线程来执行队列里的任务,线程和队列一一对应。
但是会出现这样一种情况:A线程处理完了自己队列的任务,B线程的队列里还有很多任务要处理。
A是一个很热情的线程,想过去帮忙,但是如果两个线程访问同一个队列,会产生竞争,所以A想了一个办法,从双端队列的尾部拿任务执行。而B线程永远是从双端队列的头部拿任务执行。
注意:线程池中的每个线程都有自己的工作队列(PS,这一点和ThreadPoolExecutor不同,ThreadPoolExecutor是所有线程公用一个工作队列,所有线程都从这个工作队列中取任务),当自己队列中的任务都完成以后,会从其它线程的工作队列中偷一个任务执行,这样可以充分利用资源。
优点
利用了线程进行并行计算,减少了线程间的竞争
缺点
如果双端队列中只有一个任务时,线程间会存在竞争。
窃取算法消耗了更多的系统资源,如会创建多个线程和多个双端队列。
ForkJoinTask:
使用该框架,需要创建一个ForkJoin任务,它提供在任务中执行fork和join操作的机制。一般情况下,我们并不需要直接继承ForkJoinTask类,只需要继承它的子类,它的子类有两个:
-
RecursiveAction:用于没有返回结果的任务。
-
RecursiveTask:用于有返回结果的任务。
-
fork()方法:将任务放入队列并安排异步执行
-
²join()方法:等待计算完成并返回计算结果。
ForkJoinPool:
- 任务ForkJoinTask需要通过ForkJoinPool来执行。
ForkJoinWorkerThread:
-
ForkJoinPool线程池中的一个执行任务的线程。
-
//使用ForkJoin框架计算 1+2+3......+n = ? public class SumTask extends RecursiveTask<Long> { private int start; private int end; private final int step = 2000000;//最小拆分成几个数相加 public SumTask(int start, int end) { this.start = start; this.end = end; } @Override protected Long compute() { long sum = 0; if(end - start <= step ){ //小于5个数,直接求和 for (int i = start; i <=end; i++) { sum+=i; } }else{ //大于5个数,分解任务 int mid = (end + start)/2; SumTask leftTask = new SumTask(start,mid); SumTask rightTask = new SumTask(mid+1,end); //执行子任务 leftTask.fork(); rightTask.fork(); //子任务,执行完,得到执行结果 long leftSum = leftTask.join(); long rightSum = rightTask.join(); sum = leftSum+rightSum; } return sum; } public static void main(String[] args) throws ExecutionException, InterruptedException { //如果多核CPU,其实是一个一直使用,其他闲置;怎么办,多线程解决; //但是涉及到任务的拆分与合并等众多细节,不要紧, //现在使用ForkJoin框架,可以较轻松解决; long start = System.currentTimeMillis(); long sum = 0; for(int i=0;i<=1000000000;i++){ sum +=i; } System.out.println(sum); long end = System.currentTimeMillis(); System.out.println("for:"+(end - start)); //使用ForkJoin框架解决 //创建一个线程池 ForkJoinPool pool = new ForkJoinPool(); //定义一个任务 SumTask sumTask = new SumTask(1,1000000000); //将任务交给线程池 start = System.currentTimeMillis(); Future<Long> future = pool.submit(sumTask); //得到结果并输出 Long result = future.get(); System.out.println(result); end = System.currentTimeMillis(); System.out.println("pool:"+(end - start)); } }
可以看出,使用了 ForkJoinPool 的实现逻辑全部集中在了 compute() 这个函数里,仅用了很少行就实现了完整的计算过程。特别是,在这段代码里没有显式地“把任务分配给线程”,只是分解了任务,而把具体的任务到线程的映射交给了 ForkJoinPool 来完成。