山不过来,我就过去
生产者-消费者模型
在前面我们讲了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。
总的来说,就是如下图的这样,不断循环。
以学生类为资源举例
首先,要对线程等待,唤醒有基本的思想
不同种类线程之间的通信(等待唤醒机制)
出现线程安全问题了:
1.是不是多线程环境
2.是不是由共享资源
3.是否有多条语句在操作共享资源
线程的等待唤醒 Object类
void wait ()
在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
void notify ()
唤醒在此对象监视器上等待的单个线程。
void notifyAll ()
唤醒在此对象监视器上等待的所有线程。
生产线程:如果没有资源资源我就生产,有了资源我就等待,通知消费线程来消费
消费线程:有了资源我就消费,没有资源我就等着,你通知生产线程生产
1)定义一个学生类,定义一个标记,代表有无资源。
public class Student {
public String name;
public int age;
//定义一个标记
boolean flag;//表示资源,false就没有,ture就有
}
2定义一个生产资源SetThread类
public class SetThread extends Thread {
Student student;
int i=0;
public SetThread(Student student) {
this.student=student;
}
@Override
public void run() {
while (true){
synchronized (student){
if(student.flag){
try {
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i%2==0){
student.name="老大";
student.age=15;
}else {
student.name="老二";
student.age=12;
}
student.flag=true;
//通知
student.notifyAll();//唤醒以后,还要再次争抢时间片
i++;
}
}
}
}
3)定义一个消费GetThread类
public class GetThread extends Thread {//获得资源
Student student;
public GetThread(Student student) {
this.student=student;
}
@Override
public void run() {
while (true){
synchronized (student){
if(!student.flag){//没有资源
try {
student.wait();//等着:wait()方法 线程一旦等待 就要立马释放锁,等会被唤醒了,也就从这里醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(student.name + "===" + student.age);
//修改标记
student.flag = false;
student.notify();//通知
}
}
}
}
4)接下来,开启线程
public class Test {
public static void main(String[] args) {
Student student = new Student();
SetThread th1 = new SetThread(student);
GetThread th2 = new GetThread(student);
th1.start();
th2.start();
}
}
结果:
总的就是,生产-消费的关系一直循环(等待唤醒机制)
线程池的概述与使用
1)线程池概述
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
2)内置线程池的概述
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool(): 根据任务的数量来创建线程对应的线程个数
public static ExecutorService newFixedThreadPool(int nThreads): 固定初始化几个线程
public static ExecutorService newSingleThreadExecutor(): 初始化一个线程的线程池
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
使用步骤:
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程
3)匿名内部类的方式实现多线程
a.继承Tread类
b.继承Runnable类
public class Thread {
public static void main(String[] args) {
//匿名实现多线程
//继承thread类
new Thread(){
public void run(){
for(int x=0;x<111;x++){
System.out.println(getName()+":"+x);
}
}
}.start();
//实现runable
new Thread(new Runnable() {
@Override
public void run() {
for(int x=0;x<100;x++){
System.out.println("wwww");
}
}
}).start();
}
}
4)Executors工厂类来产生线程池
首先,定义一个MyCallable类来继承Callable,需要重新其中的call()方法。
public class MyCallable implements Callable<Integer> {
int len;
public MyCallable(int i) {
len=i;
}
@Override
public Integer call() throws Exception{//此方法需要返回值,
int sum=0;
for (int i = 0; i <= len; i++) {
sum=sum+i;
}
return sum;
}
}
接下来开启线程池。
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Callable<Integer> 这个任务接口,执行完之后会有返回值 跟线程池配合使用
// Runnable 这个任务接口 执行完之后没有返回值
//获取一个有指定数量线程对象的线程池
ExecutorService service = Executors.newFixedThreadPool(3);//定义线程个数
//submit 提交要执行的值返回任务,并返回表示挂起的任务结果的 Future。
Future<Integer> sb1 = service.submit(new MyCallable(10));//第一个线程
Future<Integer> sb2 = service.submit(new MyCallable(100));//
Future<Integer> sb3 = service.submit(new MyCallable(1000));
System.out.println(sb1.get());
System.out.println(sb2.get());
System.out.println(sb3.get());
service.shutdown();//关闭线程池
}
}
记得最后关闭线程池。