Java多线程个人学习笔记

Java Concurrency Java多线程2021.2.2

1 并发与并行

并发:指两个或多个时间在同一个时间内发生的

并行:指两个或多个时间在同一个时刻发生(同时发生)

并行的速度要快一些

2 线程与进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

在这里插入图片描述

3 线程调度

  • 分时调度

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

  • 抢占式调度

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

4 主线程

package com.javaconcurrency;
/*
* 主线程:执行主(main)方法的线程
*
* 单线程程序:java程序中只有一个线程
* 执行从main方法开始,从上到下开始
*
* JVM执行main方法,main方法会进入到栈内存
* JVM会找操作系统开辟一条main方法通向cpu的执行路径
* cpu就可以通过这个路径来执行main方法
* 而这个路径有一个名字,叫main(主)线程
* */
public class Demo01 {
    public static void main(String[] args) {
        Person p1 = new Person("小强");
        p1.run();
        System.out.println(0/0);
        Person p2 = new Person("旺财");
        p2.run();
    }
}

在这里插入图片描述

5 创建线程类

5.1创建多线程程序的第一种方式-创建Thread的子类

package com.javaconcurrency;
/*
* 创建多线程程序的第一种方式:创建Thread类的子类
* java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
*
* 实现步骤:
*     1.创建一个Thread类的子类
*     2.在Thread类的子类中重写Thread类的run方法,设置线程任务(开启线程要做什么?)
*     3.创建Thread类的子类对象
*     4.调用Thread类中的start方法,开启新的线程,执行run方法
*         void start()使该线程开始执行,Java虚拟机调用该线程的run方法
*         结果是两个线程并行地运行,当前线程(main线程)和另一个线程(创建的新线程,执行其run方法)
*         多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动
*     java程序是属于抢占式调度,哪个线程的优先级高,哪个线程有限执行,同一个优先级,随机选择一个执行
* */
public class Demo02 {

    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类中的start方法,开启新的线程,执行run方法
        mt.start();

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

}

/*
	MyTread类继承Thread用作实例化的对象
*/
//1.创建一个Thread类的子类
public class MyThread extends Thread {
    //2.在Thread类的子类中重写Thread类的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run"+i);
        }
    }
}

在这里插入图片描述

5.2 多线程原理-随机性打印的原理

在这里插入图片描述

5.3 多线程与原理-多线程内存图解

在这里插入图片描述

5.4 创建多线程程序的第二种方式-实现Runnable接口(尽量使用这种,不要盲目使用第一种)

package com.javaconcurrency;
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
    //2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

package com.javaconcurrency;
/*
* 创建多线程程序的第二种方式:实现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方法
* */
public class Demo06 {

    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();

        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(run);

        //5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();

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

运行结果:

在这里插入图片描述

5.5 Thread和Runnable的区别

  • 实现Runnable接口创建多线程程序的好处

    1. 避免了单继承的局限性

      一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类不能继承其他的二类

      实现了Runnable接口,还可以继承其他的类,实现其他的接口

    2. 增强了程序的扩展性,降低了程序的耦合性(解耦)

      实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)

      实现类中,重写了run方法,用来设置线程任务

      创建Thread类对象,调用start方法,用来开启新线程

5.6 匿名内部类方式实现线程的创建(需要加深理解,反复看)

package com.javaconcurrency;

/*
* 匿名内部类方式实现线程的创建
*
* 匿名:没有名字
* 内部类:写在其他类内部的类
*
* 匿名内部类作用:简化代码
*     把子类继承父类,重写父类的方法,创建子类对象和一步完成
*     把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
* 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
*
* 格式:
*     new 父类/接口(){
*         重复父类/接口中的方法
*     };
*
* */

public class Demo07 {

    public static void main(String[] args) {
        //线程的父类是Thread
        // new MyThread().start();
        new Thread(){
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"黑马");
                }
            }
        }.start();

        //线程的接口Runnable
        //Runnable r = new RunnableImpl();//多态
        Runnable r = new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"程序员");
                }
            }
        };
        new Thread(r).start();

        //简化接口的方式
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"哈哈");
                }
            }
        }).start();
    }

}

6 Thread类的常用方法

package com.javaconcurrency;


public class MyThread extends Thread {
    //在Thread类的子类中重写Thread类的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
//        String name = getName();
//        System.out.println(name);
//        Thread thread = Thread.currentThread();
//        System.out.println(thread);//Thread[Thread-0,5,main]


//        String name = thread.getName();
//        System.out.println(name);//Thread-0

        //链式编程
        System.out.println(Thread.currentThread().getName());//Thread-0
    }
}


package com.javaconcurrency;
/*
* 获取线程的名称:
*     1.使用Thread类中的方法getName()
*         String getName() 返回线程的名称
*     2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
*         static Thread currentThread() 返回当前正在执行的线程对象的引用。
*
* */
public class Demo03 {
/*
* 线程的名称:
*     主线程:main
*     新线程:Thread-0, Thread-1, Thread-3
* */
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt = new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();//Thread-0

        new MyThread().start();//Thread-1
        new MyThread().start();//Thread-1

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }

}


6.1 Thread类的常用方法-设置线程名称

本节了解即可

package com.javaconcurrency;
/*
    设置线程名称(了解):
        1.使用Thread类中的方法setName(名字)
            void setName(String name)改变线程名称,使之与参数name相同。
        2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
            Thread(String name) 分配新的Thread对象
 */
public class MyThread extends Thread {

    public MyThread() {
    }

    //把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        //获取线程名称
        System.out.println(Thread.currentThread().getName());


   }
}


package com.javaconcurrency;


public class Demo04 {

    public static void main(String[] args) {
        //开启多线程
        MyThread mt = new MyThread();
        mt.setName("哈哈");
        mt.start();

        new MyThread("小张").start();
    }

}

6.2 Thread类的常用方法-sleep

使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)

package com.javaconcurrency;
/*
    public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
    毫秒数结束之后,线程继续执行
 */
public class Demo05 {
    //模拟秒表
    public static void main(String[] args) {
        for (int i = 0; i < 60; i++) {
            System.out.println(i);
            try {
                Thread.currentThread().sleep(1000);//老师用Thread.sleep(1000)个人感觉不是很严谨
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

7 线程安全问题

在这里插入图片描述

多个线程共享数据才会出现线程安全问题

7.1 线程安全问题的代码实现

package com.javaconcurrency;
/*
* 实现卖票案例
* */
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 100;
    //设置线程任务:卖票
    @Override
    public void run() {
        //先判断票是否存在
        //使用死循环,让卖票操作重复执行
        while (true){
            if(ticket>0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

/*
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
* */
public class Demo08 {

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

}

7.2 线程安全问题产生的原理

在这里插入图片描述

7.3 解决线程安全问题-同步代码块

  • 同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

格式:

synchronized(同步锁){
    需要同步操作的代码
}

案例:

package com.javaconcurrency;

/*
 * 卖票案例出现了线程安全问题
 * 卖出了不存在的票和重复的票
 *
 * 解决线程安全问题的第一种方案:使用同步代码块
 * 格式:
 *     synchronized(锁对象){
 *         可能会出现线程安全问题的代码(访问了共享数据的代码)
 *     }
 *     注意:
 *         1.通过代码块中的锁对象,可以使用任意的对象
 *         2.但是必须保证多个线程使用的锁对象是同一个
 *         3.锁对象作用:
 *             把同步代码块锁住,只让一个线程再同步代码块中执行
 * */
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 100;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true) {
            //同步代码块
            synchronized (obj) {
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                }
            }
        }

    }
}

package com.javaconcurrency;
/*
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
* */
public class Demo08 {

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

}

7.4 同步技术的原理

在这里插入图片描述

7.5 解决线程安全问题-同步方法

定义一个同步方法
同步方法也会把方法内部的代码所著
只让一个线程执行
同步方法的锁对象是谁?
就是实现类对象 new RunnableImpl()
也是就是this(重要)

package com.javaconcurrency;

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

    解决线程安全问题的第二种方案:使用同步方法
    使用步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中
        2.再方法上添加synchronized修饰符

    格式:定义方法的格式
    修饰符 synchronized 返回值类型 方法名(参数列表){
        可能会出现线程安全的代码
    }
* */
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket = 100;

    //设置线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:"+this);//this:com.javaconcurrency.RunnableImpl@1b6d3586
        //使用死循环,让卖票操作重复执行
        while (true) {
            this.payTicket();
        }
    }

    /*
     * 定义一个同步方法
     * 同步方法也会把方法内部的代码所著
     * 只让一个线程执行
     * 同步方法的锁对象是谁?
     * 就是实现类对象 new RunnableImpl()
     * 也是就是this(重要)
     * */
    public synchronized void payTicket() {
        //使用死循环,让卖票操作重复执行
        while (true) {
            //先判断票是否存在
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                ticket--;

            }
        }
    }
}

package com.javaconcurrency;
/*
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
* */
public class Demo08 {

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        System.out.println("run"+run);//runcom.javaconcurrency.RunnableImpl@1b6d3586
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

}

7.6 解决线程安全问题-静态同步方法

涉及到反射-了解即可

package com.javaconcurrency;

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

    解决线程安全问题的第二种方案:使用同步方法
    使用步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中
        2.再方法上添加synchronized修饰符

    格式:定义方法的格式
    修饰符 synchronized 返回值类型 方法名(参数列表){
        可能会出现线程安全的代码
    }
* */
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private static int ticket = 100;

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true) {
            payTicketStatic();
        }
    }

    /*
     * 静态的同步方法
     * 锁对象是谁呢
     * 不能是this
     * this是创建对象之后产生的,静态方法优先于对象
     * 静态方法的锁对象是本类的class属性-->class文件对象(反射)
     * */
    public static synchronized void payTicketStatic() {
        //synchronized (RunnableImpl.class){}//xxx.class 是对象
        //使用死循环,让卖票操作重复执行
        while (true) {
            //先判断票是否存在
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                ticket--;

            }
        }
    }
}

package com.javaconcurrency;
/*
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
* */
public class Demo08 {

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

}

7.7 解决线程安全问题-Lock锁

jdk1.5 之后才有

package com.javaconcurrency;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

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

    使用步骤:
        1.在成员位置创建一个ReentrantLock对象
        2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
        3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock获取锁

    }
* */
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private static int ticket = 100;

    //1.在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();
            try {
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;

                }
            } finally {
                //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock获取锁
                l.unlock();//无论程序是否异常,都会把锁释放
            }

        }

    }
}

package com.javaconcurrency;
/*
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
* */
public class Demo08 {

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }

}

注意:将unlock()放在finally中,保证无论程序是否异常,都会把锁释放,可以提高程序的效率

8 等待唤醒机制

8.1 线程状态概述

wait() 使线程进入等待

notify() 唤醒线程

在这里插入图片描述

8.2 等待唤醒案例

在这里插入图片描述

8.3 等待唤醒案例代码实现

package com.javaconcurrency.demo10.WaitAndNotify;
        /*
         * 等待唤醒案例:线程之间的通信
         *     创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
         *     创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
         *
         * 注意:
         *     顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
         *     同步使用的锁对象必须保证唯一
         *     只有锁对象才能调用wait和notify方法
         *
         * Object类中的方法
         * void wait()
         *     在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待
         * void notify()
         *     唤醒在此对象监视器上等大的单个线程
         *     会继续执行wait方法之后的代码
         * */

public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj) {
                    while (true) {
                        System.out.println("告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到Waiting状态(无限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,开吃");
                        System.out.println("======================");
                    }
                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    //花了5秒钟做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

/*
运行结果:
告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,开吃
======================
*/

8.4 Object类中wait带参方法和notifyAll方法

package com.javaconcurrency.demo10.WaitAndNotify;
/*
* 进入到TimeWaiting(计时等待)有两种方式
* 1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
* 2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
*
* 唤醒的方法:
*     void notify()  唤醒在此对象监视器上的一个线程,随机唤醒一个
*     void notifyAll()  唤醒在此对象监视器上等待的所有线程
* */
public class Demo02WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)
        new Thread() {
            @Override
            public void run() {
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj) {
                    while (true) {
                        System.out.println("顾客1告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到Waiting状态(无限等待)
                        try {
                            obj.wait();
                            //obj.wait(5000);//5秒后自动唤醒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客1开吃");
                        System.out.println("======================");
                    }
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj) {
                    while (true) {
                        System.out.println("顾客2告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到Waiting状态(无限等待)
                        try {
                            obj.wait();
                            //obj.wait(5000);//5秒后自动唤醒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客2开吃");
                        System.out.println("======================");
                    }
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                while (true) {
                    //花了5秒钟做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客
                        obj.notifyAll();
                    }
                }
            }
        }.start();
    }

}

/*
notify()
顾客1告知老板要的包子的种类和数量
顾客2告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客1开吃
======================
顾客1告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客2开吃
======================
顾客2告知老板要的包子的种类和数量

notifyAll()
顾客1告知老板要的包子的种类和数量
顾客2告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客2开吃
======================
顾客2告知老板要的包子的种类和数量
包子已经做好了,顾客1开吃
======================
顾客1告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客1开吃
======================
顾客1告知老板要的包子的种类和数量
包子已经做好了,顾客2开吃
======================
顾客2告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客2开吃
======================
顾客2告知老板要的包子的种类和数量
包子已经做好了,顾客1开吃
======================
顾客1告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客1开吃
======================
顾客1告知老板要的包子的种类和数量
包子已经做好了,顾客2开吃
======================
顾客2告知老板要的包子的种类和数量
*/

8.5 线程间通信

**概念:**多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

为什么要处理线程间通信:

多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源:

多个线程在处理同一个资源,并且任务不同时,需线程通信来帮助解决线程之间对同一个变量的使用或操作就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—一等待唤酲机制。

8.6 等待唤醒机制概述

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于object的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了object类
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法

在这里插入图片描述

8.7 等待唤醒机制需求分析

在这里插入图片描述

8.8 等待唤醒机制代码实现-包子类和包子

package com.javaconcurrency.demo10.WaitAndNotify.A;
/*
* 资源类:包子类
* 设置包子的属性
*     皮
*     陷
*     包子的状态:有 true,没有 false
* */
public class BaoZi {
    //皮
    String pi;
    //馅
    String xian;
    //包子的状态 有 true,没有 false,设置初始值为false
    boolean flag = false;
}

package com.javaconcurrency.demo10.WaitAndNotify.A;
/*
* 生产者(包子铺)类:是一个线程类,可以继承 Thread
* 设置线程任务(run):生产包子
* 对包子的状态进行判断
* true:有包子
*     包子铺调用wait方法进入等待状态
* false:没有包子
*     包子铺生产包子增加一些趣味性:交替生产两种包子
*         有两种状态(i%2==0)
*     包子铺生产好了包子
*     修改包子的状态为true有
*     唤醒吃货线程,让吃货线程吃包子
*
* 注意:
    包子铺线程和包子线程-->通信(互斥)
    必须同时同步技术保证两个线程只能有一个在执行
    锁对象必须保证唯一,可以使用包子对象作为锁对象
    包子铺类和吃货的类就需要把包子对象作为参数传递进来
        1.需要在成员位置创建一个包子变量
        2.使用带参数构造方法,为这个包子变量赋值
* */
public class BaoZiPu extends Thread{
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;

    //2.使用带参数构造方法,为这个包子变量赋值
    public BaoZiPu(BaoZi bz){
        this.bz = bz;
    }

    //3.设置线程任务(run):生产包子
    @Override
    public void run() {
        int count = 0;
        //让包子铺一直生产包子
        while(true){
//必须同时同步技术保证两个线程只能有一个在执行
            synchronized (bz){
                //对包子的状态进行判断
                if(bz.flag == true){
                    //包子铺调用wait方法进入等待状态
                    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+"包子");
                //生产包子需要三秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子铺生产好了包子
                //设置为有true
                bz.flag = true;
                //唤醒吃货线程,让吃货线程吃包子
                bz.notify();
                System.out.println("包子铺已经生产好了:"+bz.pi+bz.xian+"包子");
            }
        }
    }
}

8.9 等待唤醒机制代码实现-吃货类和测试类

package com.javaconcurrency.demo10.WaitAndNotify.A;
/*
* 消费者(吃货)类:是一个线程类,可以继承 Thread
* 设置线程任务(run):吃包子对包子的状态进行判断
* false:没有包子
*     吃货调用wit方法进入等待状态
* true:有包子
*     吃货吃包子
*     吃货吃完包子
*     修改包子的状态为 false没有
*     吃货唤醒包子铺线程,生产包子
* */
public class ChiHuo extends Thread{
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;

    //2.使用带参数构造方法,为这个包子变量赋值
    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("==============================");
            }
        }
    }
}

package com.javaconcurrency.demo10.WaitAndNotify.A;
/*
* 测试类:
* 包含main方法,程序执行的入口,启动程序
* 创建包子对象;
* 创建包子铺线程,开启,生产包子;
* 创建吃货线程,开启,吃包子;
* */
public class Demo {
    public static void main(String[] args) {
        //创建包子对象;
        BaoZi bz = new BaoZi();
        //创建包子铺线程,开启,生产包子;
        new BaoZiPu(bz).start();
        //创建吃货线程,开启,吃包子;
        new ChiHuo(bz).start();
    }
}

运行结果

在这里插入图片描述

9 线程池

9.1 线程池概述

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

如果并发的线程数量很,并且毎个线程都是执行一个时间很短的任芻就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

在这里插入图片描述


版权声明:本文为Tylers_zhang原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。