山不过来,我就过去

生产者-消费者模型 

         在前面我们讲了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

总的来说,就是如下图的这样,不断循环。

以学生类为资源举例

首先,要对线程等待,唤醒有基本的思想
不同种类线程之间的通信(等待唤醒机制)

出现线程安全问题了:
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();//关闭线程池
    }
}

记得最后关闭线程池。