等待与唤醒机制:线程之间的通信(续)
public class BaoZi {
String pi;
String xian;
// 包子的状态:设置初始值为false没有包子
boolean flag = false;
}
注意:
包子铺线程和包子线程关系-->通信(互斥)
必须使用同步技术保证两个线程只能有一个在执行
锁对象必须保证唯一,可以使用包子对象作为锁对象
包子铺类和吃货的类就需要把包子对象作为参数传递进来
1. 需要在成员位置创建一个包子变量
2. 使用带参数构造方法,为这个包子变量赋值
public class BaoZiPu extends Thread{
private BaoZi bz;
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
int count = 0;
// 让包子铺一直生成包子
while(true){
synchronized (bz){
if(bz.flag == true){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 增加一些趣味性:交替生成两种包子
if(count % 2 == 0){
bz.pi = "薄皮";
bz.xian = "三鲜馅";
}else{
bz.pi = "冰皮";
bz.xian = "牛肉大葱馅";
}
count++;
System.out.println("包子铺正在生成:" + bz.pi + bz.xian + "包子");
//生成包子需要3秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改包子的状态为true有
bz.flag = true;
bz.notify();
System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,吃货可以开始吃了");
}
}
}
}
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
//使用死循环,让吃货一直吃包子
while(true){
synchronized (bz){
if(bz.flag == false){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
//修改包子的状态为false没有
bz.flag = false;
bz.notify();
System.out.println("吃货已经把" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
System.out.println("====================================================");
}
}
}
}
public class Demo {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
new BaoZiPu(bz).start();
new ChiHuo(bz).start();
}
}
线程池
线程池:容器-->集合(ArrayList,HashSet,【LinkedList<Thread>】,HashMap)
集合:
add(new Thread(xxx));
add(new Thread(xxx)); 类似队列
add(new Thread(xxx));
...
当程序第一次启动的时候,创建多个线程,保存到一个集合中,当我们想要使用线程的时候,就可以从集合中取出来线程使用
Thread t = list.remove(0); 返回的是被移除的元素(线程只能被一个任务使用)
Thread t = linked.removeFist();
当我们使用完毕线程,需要把线程归还给线程池
list.add(t);
linked.addLast(t);
在JDK1.5之后,JDK内置了线程池,我们可以直接使用
线程池: 线程3 线程2 线程2
【任务1、任务2、任务3获得线程对象执行任务】
入口--> 任务5 任务4 任务3 任务2 任务1 出口-->
【线程池中已无空闲线程,任务4、任务5等待执行,等待其他某个任务执行完毕后,归还线程到线程池,再从线程池中获取线程,执行任务】
合理利用线程池能够带来三个好处:
1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生产线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个Runnable任务用于执行
关闭/销毁线程池的方法
void shutdown()
线程池的使用步骤:
1. 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
例如: ExecutorService es = Executors.newFixedThreadPool(2);
2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
例如: public class RunnableImpl implements Runnable{
@Override
public void run() {
...
}
}
3. 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
例如: es.submit(new RunnableImpl());
注意:线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableImpl()); // pool-1-thread-1创建了一个新的线程执行
es.submit(new RunnableImpl()); // pool-1-thread-1创建了一个新的线程执行
es.submit(new RunnableImpl()); // pool-1-thread-2创建了一个新的线程执行
4. 调用ExecutorService中的方法shutdown()销毁线程池(不建议执行)
例如: es.shutdown();
注意:线程池没有了继续获取线程会抛异常
es.submit(new RunnableImpl()); // 抛异常