多线程

第一章 多线程

1.1并发于并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

在这里插入图片描述

在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

1.2线程与进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

    进程概念:
    在这里插入图片描述

线程概念:

在这里插入图片描述

线程调度:

  • 分时调度

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

    • 设置线程的优先级
      在这里插入图片描述

    • 抢占式调度详解

      大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

      实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
      其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

在这里插入图片描述

主线程

在这里插入图片描述

/*
     主线程:执行主(main)方法的线程

     单线程程序:java程序中只有一个线程
     执行从main方法开始,从上到下一次执行

     JVM执行main方法,main方法会进入栈内存中
     JVM会找到操作系统开辟一条main方法通向cup的执行路径
     cpu就可以通过这个路径来执行main方法
     而这个路径称为main(主)路径
 */
public class Demo1MainThread {
    public static void main(String[] args) {

        //单线程程序会从主线程中从上到下一次执行
        Person person = new Person("张三");
        person.run();
            //定义循环,执行20次
            for(int i=0; i<20000; i++){
                System.out.println("main"+"-->"+i);
            }


    }
}

1.3创建线程类

在这里插入图片描述

/*
    创建多线程程序的第一种方式:创建Thread类的子类
    java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须要继承Thread类

    实现步骤:
        1.创建一个Thread类的子类
        2.在Thread子类中重写Thread类中的run方法,设置线程任务(开启线程做什么)
        3.创建Thread线程类子类对象
        4.用对象调用Thread类中的start方法,开启新的线程,执行run方法
            void start()使该线程开始执行:java虚拟机调用该线程的run方法
            结果是两个线程并发的运行:当前线程(main线程)和新创建的线程,并且执行其run方法

     注意:
        多次启动一个线程是非法的,特别是当一个线程已经结束执行后,不能再重新启动
        java程序属于抢占式调度,哪个线程的优先级高,哪个线程优先执行,同级线程,随机选择一个执行

 */


    /*
        多线程随机打印的原理:
            1.JVM执行main方法时,让OS开辟一条main方法通向cpu的路径
                1.1当main中创建一个new MyThread()对象,则OS开辟了一条通向cpu的新路径
                    1.1.1然后调用start方法,接着调用run方法

           对于cpu而言,就有两条路径去执行程序,因为是同优先级,所以会随机执行
     */
public class Demo1Thread {
    //3。创建一个Thread子类的对象
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("main-->"+i);
        }
    }
}

自定义线程子类:

//1.创建一个Thread类的子类
public class MyThread extends Thread{
    //重写run方法
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("run-->"+i);
        }
    }
}

获取线程名字

/*
    获取线程的名称:
        1.使用Thread类中的方法getName()
            String getName()返回该线程的名称
        2.可以调用Thread中方法,获取当前正在执行的线程,使用线程中的getName方法获取线程的名称
            static Thread currentThread() 返回当前正在运行的线程对象的引用
 */
public class MyThread2 extends Thread{
    @Override
    public void run() {
//        String name = getName();
//        System.out.println(name);

        System.out.println(currentThread().getName());

    }
}

1.4创建线程的方式二Runnable

/*
  创建多线程程序的第二种方式:实现Runnable接口
    java.lang.Runnable
        Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
    java.lang.Thread类的构造方法
        Thread(Runnable target) 分配新的 Thread 对象。
        Thread(Runnable target, String name) 分配新的 Thread 对象。

 实现步骤:
        1.创建一个Runnable接口的实现类
        2.在实现类中重写Runnable接口的run方法,设置线程任务
        3.创建一个Runnable接口的实现类对象
        4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        5.调用Thread类中的start方法,开启新的线程执行run方法

实现Runnable接口创建多线程程序的好处:
        1.避免了单继承的局限性
            一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
            实现了Runnable接口,还可以继承其他的类,实现其他的接口
        2.增强了程序的扩展性,降低了程序的耦合性(解耦)
            实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
            实现类中,重写了run方法:用来设置线程任务
            创建Thread类对象,调用start方法:用来开启新线程
 */

实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法

public class Demo01Runnable {
    public static void main(String[] args) {
        RunnableImp run = new RunnableImp();
        Thread thread = new Thread(run);
        thread.start();


        //传入一个Runable实现类的对象到Thread构造参数中,使其真正成为一个线程
        Thread t=new Thread(new RunnableImp2());
        t.start();


        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"--》"+i);
        }
    }
}

Runnable实现类:

实现Runnable接口

public class RunnableImp implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"--》"+i);
        }
    }
}

1.5Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类。

扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用

java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程

1.6匿名内部类的方式实现线程创建

匿名内部类方式实现线程的创建:
    匿名:没有名字
    内部类:写在其他类内部的类

匿名内部类的作用:简化代码
    1.把子类继承父类,重写父类方法,创建子类对象一步完成
    2.把子类实现接口,重写接口方法,创建子类对象合成一步
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
public class Demo01InnerClassThread {
    public static void main(String[] args) {
        //1.把子类继承父类,重写父类方法,创建子类对象一步完成
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"黑马");
                }
            }
        }.start();

        // 2.把子类实现接口,重写接口方法,创建子类对象合成一步
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"程序员");
                }
            }
        }){}.start();

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "传智播客");
                }
            }
        };

      new Thread(runnable).start();

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+"main");
        }
    }
}

第二章线程安全

2.1线程安全

/*
    实现买票案例
 */
public class RunnableImp implements Runnable {
    private int ticket=100;
    @Override
    public void run() {
        //窗口永远开着
        while (true){
            //为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
            //有票 可以买
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }

}

开启多个线程同时卖票(模拟多个窗口同时买票)

public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImp r = new RunnableImp();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();

    }
}

出现线程安全问题:会出现卖出同样的票,甚至不存在的票

在这里插入图片描述

2.2线程同步

2.2.1解决线程安全的第一种方式:synchronized

 卖票案例出现了线程安全问题
卖出了不存在的票和重复的票

解决线程安全问题的一种方案:使用同步代码块
格式:
    synchronized(锁对象){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

注意:
    1.通过代码块中的锁对象,可以使用任意的对象
    2.但是必须保证多个线程使用的锁对象是同一个
    3.锁对象作用:
        把同步代码块锁住,只让一个线程在同步代码块中执行
public class RunnableImp implements Runnable {
    private int ticket=100;
    Object obj=new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){
                if(ticket>0){
                    //为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }

        }
    }

}

测试代码

public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImp r = new RunnableImp();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();

    }
}

2.2.3解决线程安全的第二种方式:同步方法

 卖票案例出现了线程安全问题
卖出了不存在的票和重复的票

解决线程安全问题的第二种方案:使用同步方法
使用步骤:
    1。把访问公共资源的代码抽取出来,放到一个方法中
    2.在这个方法中加入关键字synchronized修饰符

格式:
    修饰符 synchronized 返回值类型 方法名(){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

1.同步方法中也有锁对象,即实现类对象 new RunnableImp

也就是this

2.静态方法中无法用this对象,因为Static方法要先于对象创建

需要用类的class对象

public class RunnableImp implements Runnable {
    private static int ticket=100;
    Object obj=new Object();
    @Override
    public void run() {

        //判断同步方法的锁对象是谁
        System.out.println("this:"+this);
        while (true){

            payTicketStatic();
        }
    }
    /*
        同步方法中也有锁对象
        这个对象就是是实现类对象 new RunnableImp();
        也就是this
     */
    public /*synchronized */void payTicket(){
        synchronized (this){
            if(ticket>0){
                //为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }

    }
    /*
        静态方法
     */

    public static synchronized void payTicketStatic(){
        /*
            因为此方法为静态方法,所以不能用this对象,因为Static要先于创建对象
            需要用类的class类对象
        */
        synchronized (RunnableImp.class){
            if(ticket>0){
                //为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }

    }

}

2.5解决线程安全的第三种方式:Lock锁

卖票案例出现了线程安全问题
卖出了不存在的票和重复的票

解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock实现提供了比使用 synchronized方法和语句可获得的更广泛的锁定操作
Lock接口中的方法:
    void lock():获取锁
    void unlock():释放锁

实现类:java.uitl.concurrent.locks.ReentrantLock implements Lock 接口

使用步骤:
    1.在成员位置创建一个ReentrantLock对象
    2.在可能出现线程安全问题的代码前调用lock()方法获取锁
    3.在可能出现线程安全问题的代码后调用unlock()方法释放锁
public class RunnableImp implements Runnable {
    private int ticket=100;
    ReentrantLock l=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //为了提高线程安全问题出现的概率,让抢夺大cpu资源的线程睡眠,丧失掉cpu的资源

            l.lock();
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
            l.unlock();
        }
    }

}

测试代码

public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImp r = new RunnableImp();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();

    }
}

第三章 线程状态概述

3.1线程状态概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,

有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TimedWaiting(计时同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckW97vC0-1604820457038)(D:\学习资料\java自学\2020Java学习路线\2020Java学习路线\02_Java进阶\day06_线程、同步\resource\线程的状态图.bmp)]

3.2Timed Waiting(计时等待)

进入到TimeWaiting(计时等待)有两种方式
 1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:
     void notify() 唤醒在此对象监视器上等待的单个线程。
     void notifyAll() 唤醒在此对象监视器上等待的所有线程。
public class Demo02Wait {
    public static void main(String[] args) {
        Object obj=new Object();
        new Thread(){
            @Override
            public void run() {
                //消费则一直购买
                while (true){
                    //使用同步代码块,保证只能有一个线程在执行
                    synchronized (obj){
                        System.out.println("告知老板购买的包子数和种类,然开排队等待");
                        try {
                            //调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
                            obj.wait(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //wait()方法后的代码
                        System.out.println("领取包子,开始吃");
                        System.out.println("------------------");
                    }
                }

            }
        }.start();
    }

}

在这里插入图片描述

等待唤醒案例:线程之间的通信

     创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
     创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

注意:
    1.顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
    2.同步使用的对象必须唯一
    3.只有锁对象才能调用wait()和Notify方法

Object类中的方法:
void wait()
      在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
      唤醒在此对象监视器上等待的单个线程。
      会继续执行wait方法之后的代码
public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        //1.创建锁对象,保证唯一
        Object obj=new Object();

        //2.创建一个客户线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //消费则一直购买
                while (true){
                    //使用同步代码块,保证只能有一个线程在执行
                    synchronized (obj){
                        System.out.println("告知老板购买的包子数和种类,然开排队等待");
                        try {
                            //调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //wait()方法后的代码
                        System.out.println("领取包子,开始吃");
                        System.out.println("------------------");
                    }
                }

            }
        }.start();

        //创建一个生产者线程做包子
        new Thread(){

            //重写父类中的run方法
            @Override
            public void run() {
                //老板要一直做包子
                while (true){
                    //花了5秒钟做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //使用同步代码保证只有一个线程在执行
                    synchronized(obj){
                        System.out.println("5秒钟后,老板将包子做好了,开始叫号");
                        //唤醒同一对象中进入无线等待的线程(消费者线程),让其领包子
                        obj.notify();
                    }
                }

            }
        }.start();
    }
}
唤醒的方法:
    void notify() 唤醒在此对象监视器上等待的单个线程。
    void notifyAll() 唤醒在此对象监视器上等待的所有线程。
public class Demo02WaitAndNotifyAll {
    public static void main(String[] args) {
        //1.创建锁对象,保证唯一
        Object obj=new Object();

        //2.创建一个客户线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //消费则一直购买
                while (true){
                    //使用同步代码块,保证只能有一个线程在执行
                    synchronized (obj){
                        System.out.println("客户1告知老板购买的包子数和种类,然开排队等待");
                        try {
                            //调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //wait()方法后的代码
                        System.out.println("领取包子,开始吃");
                        System.out.println("------------------");
                    }
                }

            }
        }.start();

        //2.创建一个客户线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //消费则一直购买
                while (true){
                    //使用同步代码块,保证只能有一个线程在执行
                    synchronized (obj){
                        System.out.println("客户2告知老板购买的包子数和种类,然开排队等待");
                        try {
                            //调用同一对象的wait方法,使此线程进入到WAITING状态(无限等待)
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //wait()方法后的代码
                        System.out.println("领取包子,开始吃");
                        System.out.println("------------------");
                    }
                }

            }
        }.start();


        //创建一个生产者线程做包子
        new Thread(){

            //重写父类中的run方法
            @Override
            public void run() {
                //老板要一直做包子
                while (true){
                    //花了5秒钟做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //使用同步代码保证只有一个线程在执行
                    synchronized(obj){
                        System.out.println("5秒钟后,老板将包子做好了,开始叫号");
                        //唤醒同一对象中进入无线等待的线程(消费者线程),让其领包子
                        //obj.notify();
                        obj.notifyAll();
                    }
                }

            }
        }.start();
    }
}

第四章等待唤醒机制

1.1线程间的通信

生产包子的例子

包子铺线程生产包子,吃货线程消费包子。
当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),
并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。
如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),
并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。

吃货:

public class ChiHuo extends Thread{
    private BaoZi bz;
    public ChiHuo(String name,BaoZi bz){
        super(name);
        this.bz=bz;
    }

    @Override
    public void run() {
        while (true){
            synchronized (bz){
                //判断是否有包子
                if(bz.flag==false){
                    //没有包子,吃货线程则陷入等待
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    //有包子
                    System.out.println("吃货正在吃"+bz.pi+bz.xian+"包子");
                    bz.flag=false;
                    //唤醒包子对象监听器下的包子铺线程
                    bz.notify();
                }


            }
        }
    }
}
public class BaoZiPu extends Thread{
    private BaoZi bz;
    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz=bz;
    }

    @Override
    public void run() {
        int count = 0;
        while (true){
            synchronized (bz){
                //判断是否有包子
                if(bz.flag==true){
                    //如果有包子,则包子铺陷入waiting状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    //没有包子
                    System.out.println("包子铺开始做包子");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(count%2 == 0){
                        // 冰皮  五仁
                        bz.pi = "冰皮";
                        bz.xian = "五仁";
                    }else{
                        // 薄皮  牛肉大葱
                        bz.pi = "薄皮";
                        bz.xian = "牛肉大葱";
                    }
                    count++;
                    bz.flag=true;
                    //唤醒包子对象监听器下的吃货线程
                    bz.notify();
                }


            }
        }
    }
}

包子对象

    包子铺线程生产包子,吃货线程消费包子。
    当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),
    并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
    接下来,吃货线程能否进一步执行则取决于锁的获取情况。
    如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),
    并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
public class BaoZi {
    String pi;
    String xian;
    boolean flag = false;//包子资源的状态

    public BaoZi() {
    }

    public BaoZi(String pi, String xian, boolean flag) {
        this.pi = pi;
        this.xian = xian;
        this.flag = flag;
    }
}

测试类

public class Demo {
    public static void main(String[] args) {
        BaoZi baoZi = new BaoZi("冰皮", "五仁", true);
        new ChiHuo("张三",baoZi).start();
        new BaoZiPu("李四包子铺",baoZi).start();

    }
}

第五章线程池

概述

**线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:

在这里插入图片描述

在这里插入图片描述

例子

线程池: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里面提供的静态方法newFixedThreadPoo生产一个指定线程数量的线程池
      放回一个线程接口的实现类(面向接口编程)
      2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
      3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
      4.调用ExecutoeService中的shutdown方法销毁线程池(不建议使用)
public class Demo01ThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //调用一个线程池中的某个线程执行实现类中的run方法,线程可以重复利用
        executorService.submit(new RunnableImp());
        executorService.submit(new RunnableImp());
        executorService.submit(new RunnableImp());

       /*
       //销毁线程池则会无法使用线程
        executorService.shutdown();
        executorService.submit(new RunnableImp()); //此时无法在调用线程 java.util.concurrent.RejectedExecutionException
*/


    }

}
package 线程.ThreadPool;
/*
    2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
 */
public class RunnableImp implements Runnable{
    @Override
    public void run() {
        {
            System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
        }
    }
}

第六章ThreadLocal

概述:threadLocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定的线程可以得到存储数据

作用:用于解决线程安全

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

public class Demo01ThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //调用一个线程池中的某个线程执行实现类中的run方法,线程可以重复利用
        executorService.submit(new RunnableImp());
        executorService.submit(new RunnableImp());
        executorService.submit(new RunnableImp());

       /*
       //销毁线程池则会无法使用线程
        executorService.shutdown();
        executorService.submit(new RunnableImp()); //此时无法在调用线程 java.util.concurrent.RejectedExecutionException
*/


    }

}
package 线程.ThreadPool;
/*
    2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
 */
public class RunnableImp implements Runnable{
    @Override
    public void run() {
        {
            System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
        }
    }
}

第六章ThreadLocal

概述:threadLocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定的线程可以得到存储数据

作用:用于解决线程安全

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值