java 线程Thread

目的:为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行

1. 多线程相关概念

1.1 程序、进程、线程

1)程序
为完成特定任务,用某种语言编写的一组指令集合。(静态代码)
2)进程
一个正在执行的程序;(程序加载到内存,动态)
3)线程
进程的一部分,控制进程的执行,执行功能的最小单位。
※ 一个 java 应用程序(java.exe),至少有三个线程,main() 主线程,gc() 垃圾回收线程,异常处理线程(会影响主线程)

1.2 内存分配

内存区域:方法区、堆、虚拟机栈、程序计数器、本地方法栈

每个线程各自有一套虚拟机栈和一套程序计数器;
一个进程一份方法区(static)和堆(new 对象及对象的实例变量),即各线程共享方法区和堆;

1.3 CPU单核和多核

单核CPU:同一时间只能执行一个线程任务,通过高频率不断切换执行各个线程任务,是一种假的多线程;

多核CPU:同一时间执行多个线程任务,真正的多线程;

1.4 并行与并发

并行:多个CPU 同时执行多个任务,好比多个人同时做不同的事;
并发:一个CPU “同时”执行多个任务,好比多个人做同一件事(秒杀)

1.5 多线程的优点

单核CPU “同时”执行多个线程要比分别执行各个线程(一个完成后再执行另一个)的速度要快,因为CPU 要不断的切换也需要耗费时间。
那么问题来了,既然多线程速度慢,为什么还要费劲整多线程呢?

优点:
1)提高应用程序的响应,对图形化界面更有意义,提高用户体验;
2)提高计算机系统CPU的利用率;
3)改善程序结构,将又长有复杂的进程分为多个线程,独立运行,利于理解和修改

1.6 多线程应用场景

1)程序需要同时执行多个任务
主线程执行时,伴随一个垃圾回收线程,随时回收垃圾,不然容易内存溢出;
2)程序需要实现一些需要等待的任务
用户输入、文件读写操作、网络操作、搜索等;
3)需要一些后台运行的程序

2. 代码实现

2.1 继承Thread 类(第一种)

2.1.1 实现步骤

  • 1.继承Thread 类
  • 2.重现 run(),具体要执行的代码
  • 3.创建子类
  • 4.子类对象调用 start(),启动当前线程,调用当前线程的 run()
        1)如果不调用start(),而直接调用run(),则线程不被启用,仍旧为单线程;
        2)对于一个线程来说,不可以让已经start()的线程再次start(),这是需要新建对象 Mythread mythread2 = new Mythread();,再去调用mythread2.start()
/***
 * 多线程创建方法 - 继承 Thread类
 */
public class ThreadTest {
    public static void main(String[] args) {
        // 3.创建子类
        Mythread mythread = new Mythread();
        // 4.子类对象调用 start()
        mythread.start();
		// 主线程执行代码
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                System.out.println("===Hello===" + i);
            }
        }
    }    
}
// 1.继承Thread 类
class Mythread extends Thread{
    // 2.重现 run(),具体要执行的代码
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                System.out.println(i);
            }
        }
    }
}

2.1.2 Thread 常用方法

1)start():启动当前线程,调用当前线程的 run();
2)run():通常需要重写Thread 类中的此方法,将创建的线程要执行的操作写在此方法中;
3)currentThread():当前线程,静态方法;
4)setName()/getName():设置/获取线程名称;
5)yield():一时释放当前CPU执行权;
6)join():在线程A中调用B线程的join(),线程A进入阻塞状态,B执行完之后,A结束阻塞状态(礼让B先执行);
7)stop():已过时,强制结束当前线程;
8)sleep(1000):让当前线程强制“睡眠”指定时间;
9)isAlive():判断当前线程是否存活

public class ThreadMethodTest {
    public static void main(String[] args) {
        MyThreadMethod myThread = new MyThreadMethod("Thread1");
        // 设置线程名
        //        myThread.setName("Thread1");
        myThread.start();
        // 设置主线程名
        Thread.currentThread().setName("MainThread");
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0) {
                // 获取当前线程名称
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

            if (i == 20) {
                try {
                    // 在线程A中调用B线程的join(),线程A进入阻塞状态,B执行完之后,A结束阻塞状态
                    // 礼让B先执行
                    myThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(myThread.isAlive());
    }
}

class MyThreadMethod extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0) {
                try {
                    // 强制阻塞线程
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取当前线程名称
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
//            if (i%20 == 0) {
//                // 一时释放CPU执行权
//                yield();
//            }
        }
    }

    public MyThreadMethod(String name){
        super(name);
    }
}

2.1.3 线程调度

优先级从低到高:1 ~ 10
// 设定优先级
setPriority(Thread.MAX_PRIORITY);
// 获取优先级
getPriority()

注意:
    设置高优先级,只是提高执行概率,并不是一定优先执行

public class ThreadMethodTest {
    public static void main(String[] args) {
        MyThreadMethod myThread = new MyThreadMethod();
        myThread.setName("Thread1");
        // 设置分线程的优先级(只是提高执行概率,并不是一定优先执行)
        myThread.setPriority(Thread.MAX_PRIORITY);
        myThread.start();
        // 设置主线程名
        Thread.currentThread().setName("MainThread");
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0) {
                // 获取当前线程名称
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":"+i);
            }
        }
    }
}

class MyThreadMethod extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0) {
                // 获取当前线程名称
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":"+i);
            }
        }
    }
}

2.1.4 案例

三个卖票窗口,总票数100张

/***
 * 三个卖票窗口,总票数100张
 */
public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        w1.start();
        w2.start();
        w3.start();
    }
}

class Window extends Thread{
    private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                System.out.println(getName() + "卖票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

存在线程安全问题,保留

若不想使用static ,引出下一节Rumable 接口
private static int ticket = 100;

2.2 实现Runnable 接口(第二种)

2.2.1 实现步骤

  • 1.定义类实现Runable 接口;
  • 2 覆盖Runable中的run();
  • 3 通过Thread 类创建线程对象;// Thread t1 = new Thread(Runable r);
  • 4 将Runable 接口子类对象作为实际参数传递给Thread 类的构造方法;
  • 5 调用Thread 类的Start 方法开启线程,并调用Runable 接口子类的run 方法
/***
 * 多线程创建方法 - 实现Runnable 接口
 */
public class RunnableTest {
    public static void main(String[] args) {
        // 3.创建实现接口类的对象
        MyRunnable runnable = new MyRunnable();
        // 4.将此对象作为参数传递到Thread 类的构造器中,创建Thread 对象
        Thread t1 = new Thread(runnable);
        t1.setName("线程1");
        // 5.通过Thread 类的对象调用start()
        t1.start();

        // 再启动一个线程
        Thread t2 = new Thread(runnable);
        t2.setName("线程2");
        t2.start();
    }
}

// 1.创建实现Runnable接口的类
class MyRunnable implements Runnable{
    // 2.实现抽象方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

2.2.2 案例

三个卖票窗口,总票数100张,Runnable方式
因为只创建了一个 Window1 对象,所以ticket 不用static 定义

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.start();
        t2.start();
        t3.start();
    }
}
class Window1 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

2.3 实现Callable 接口(第三种)(JDK5.0新增)

public class ThreadCallableTest {
    public static void main(String[] args) {
        // 3.创建Callable 接口实现例的对象
        CallableThread callableThread = new CallableThread();
        // 4.接口实现例的对象作为参数传递给FutureTask构造器中,创造FutureTask对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callableThread);
        // 5.将FutureTask对象作为参数,传递给Thread类中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        try {
            // 返回值即为FutureTask 参数CallableThread 重写的call() 的返回值
            // 6.获取Callable中call() 的返回值,可以用于线程间传值(参考)
            Integer sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
// 1.创建Callable 接口的实现类
class CallableThread implements Callable<Integer> {
    // 2.实现call(),子线程需要执行的操作声明在此处
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i%2 == 0){
                sum += i;
            }
        }
        return sum;
    }
}

2.4 线程池-(第四种)(JDK5.0新增)

1)背景
线程经常创建和销毁、使用量特别打的资源,比如并发情况下的线程,对性能影响打
2)思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具,而不用自己造车。
3)好处
① 提高相应速度(减少了创建新线程的时间);
② 降低资源消耗(重复利用线程池中线程,不需要每次都创建);
③ 便于线程管理
corePoolSize:核心池大小
maximunPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 1.提供指定线程数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        // 设置线程池的属性
        ThreadPoolExecutor executorService1 = (ThreadPoolExecutor) executorService;
        executorService1.setCorePoolSize(15);

        // 2.执行指定的线程操作,需要提供实现Runnable 或Callable接口
        executorService1.execute(new RunnableThread1());// 适合适用于Runnable
        executorService1.execute(new RunnableThread2());// 适合适用于Runnable

        // 3.关闭连接池
        executorService1.shutdown();

        // Callable接口
        // executorService.submit(Callable callable);// 适合适用于Callable
    }
}
class RunnableThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
class RunnableThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

2.5 Thread 和Runnable 比较

开发中优先选择实现Runnable 接口的方式
1)实现的方法没有类的单继承的局限性;
2)更适合处理多个线程有共享数据的情况;
两种方式都要重写run()

2.6 Callable 和 Runnable比较

Callable 要比Runnable 更强大些

Callable 的Call() 可以有返回值
可以抛异常;
支持泛型;
需要借助FutureTask 类,比如获取返回结果

3. 线程的生命周期

3.1 线程的状态

新建、运行、冻结(sleep()、wait())、阻塞、消灭

cpu的执行资格:可以被cpu处理,在处理队列中排队
cpu的执行权:正在被cpu处理

3.2 生命周期

在这里插入图片描述

4. 线程安全

线程的同步(只有一个线程可以访问)
解决线程安全问题,多线程访问共同变量时,为保证当前变量的完整性,Synchronized 关键字修饰操作共享变量的方法时,其他线程对共享变量不可操作;

4.1 同步代码块(synchronized)

1)确认共享数据
共享数据:多个线程共同操作的变量(数据)
2)确认操作共享数据的代码
操作共享数据的代码,即为需要被同步的代码,要放入同步代码块中;
3)确认同步监视器
同步监视器,俗称“锁”,要求多个线程必须要公用一把锁。

4.1.1 解决Runnable 安全问题

1)多窗口卖票
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window1 implements Runnable{
    // 共享数据
    private int ticket = 100;
    // 锁:多个线程必须要公用一把锁,因为Window1对象只被创建一次
    Object obj = new Object();
    @Override
    public void run() {
        while (true){
            // 同步代码块,obj也可以换成this,因为该对象只被创建一次
            synchronized (obj){
                if (ticket > 0){
                    // 提升出现错票概率
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}
2)银行同账户存钱
/***
 * 两个储户分别向同一账户存3000元,每次存1000
 * 分析:
 *   属于多线程(两个储户),存在共享数据(账户余额)
 */
public class AccountTest {
    public static void main(String[] args) {
        // 两客户公用账户对象
        Account acc = new Account(0);
        Customer c1 = new Customer(acc);
        Customer c2 = new Customer(acc);
        c1.setName("甲");
        c2.setName("乙");
        c1.start();
        c2.start();
    }
}

// 共享账户类
/***
 * 两个储户分别向同一账户存3000元,每次存1000
 * 分析:
 *   属于多线程(两个储户),存在共享数据(账户余额)
 */
public class AccountTest {
    public static void main(String[] args) {
        // 两客户公用账户对象
        Account acc = new Account(0);
        Customer c1 = new Customer(acc);
        Customer c2 = new Customer(acc);
        c1.setName("甲");
        c2.setName("乙");
        c1.start();
        c2.start();
    }
}
// 共享账户类
class Account{
    private double balance;
    public Account(double balance) {
        this.balance = balance;
    }
    // 存钱(synchronized 解决线程安全问题)
    // 继承Thread 默认锁为this,而Account 对象唯一,没有死锁问题
    public synchronized void deposit(double amt){
        if (amt > 0) {
            // 添加阻塞(测试用)
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balance += amt;
            System.out.println(Thread.currentThread().getName() + "客户存钱成功!余额为:"+balance);
        }
    }
}
// 用户类
class Customer extends Thread{
    private Account acc;
    public Customer(Account acc) {
        this.acc = acc;
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            acc.deposit(1000);
        }
    }
}

输出:
甲客户存钱成功!余额为:1000.0
甲客户存钱成功!余额为:2000.0
乙客户存钱成功!余额为:3000.0
甲客户存钱成功!余额为:4000.0
乙客户存钱成功!余额为:5000.0
乙客户存钱成功!余额为:6000.0

4.1.2 解决Thread 安全问题

public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        w1.start();
        w2.start();
        w3.start();
    }
}
class Window extends Thread{
    private static int ticket = 100;
    private static Object obj = new Object();
    @Override
    public void run() {
        while (true){
        	// obj 不可以用this 替代,因为Window 对象被创建多次,不唯一,而可以用Window.class,类只加载一次
            synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

4.2 同步方法(synchronized)

如果操作共享数据的代码完整的声明在一个方法中,可以将该方法声明为同步的方法;
仍然需要同步监视器,只是不需要显示声明,非静态的同步方法是this,静态的同步方法是当前类本身

4.2.1 解决Runnable 安全问题

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.start();
        t2.start();
        t3.start();
    }
}

class Window1 implements Runnable{
    // 共享数据
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            show();
        }
    }
	// 提取操作共享数据的代码
    private synchronized void show(){
        if (ticket > 0){
            // 提升出现错票概率
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
            ticket--;
        }
    }
}

4.2.2 解决Thread 安全问题

public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        w1.start();
        w2.start();
        w3.start();
    }
}
class Window extends Thread{
    private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            show();
        }
    }
    // 方法要定义为 static
    private static synchronized void show(){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
            ticket--;
        }
    }
}

4.2.3 解决懒汉模式线程安全问题

// 懒汉式
class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static synchronized Bank getInstance(){
        if (instance == null){
            instance = new Bank();
        }
        return instance;
    }
}

4.3 解决线程安全问题(Lock锁)

JDK5.0 新增
步骤:
1.实例话lock
2.加锁
3.解锁

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window1 implements Runnable{
    // 共享数据
    private int ticket = 100;
    // 1.实例话lock,如果用继承Thread 方式的话,需要将lock 声明为静态(锁唯一)
    // true:先来后到
    private ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true){
            try {
                // 2.加锁
                lock.lock();
                if (ticket > 0){
                    // 提升出现错票概率
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
                    ticket--;
                }
            }finally{
                // 3.解锁
                lock.unlock();
            }
        }
    }
}

4.4 synchronized 和 Lock锁 异同

4.4.1 相同点

二者都可以解决线程安全问题

4.4.2 不同点

synchronized 在执行完相应的同步代码后,自动的释放同步监视器
Lock锁 需要手动的启动同步和结束同步

4.4.3 优先使用顺序

开发中推荐使用优先顺序:
Lock锁 ⇒ 同步代码块(进入方法体,再分配资源) ⇒ 同步方法(在方法体之外)

4.5 线程死锁

解决线程安全时,添加锁的情况下,容易造成死锁问题,要注意

不同的线程分别占用对方需要的同步资源不放弃,都在等对方放弃自己需要的同步资源,就形成了线程死锁;
出现死锁后,不会出现异常,也不会提示,只是所有的线程都处于阻塞状态,无法继续

死锁代码例

public class ThreadLockTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        // 继承Thread 线程
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    // 阻塞代码(测试用),增加死锁发生概率
                    // 拥有s1锁,等待s2锁
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
		// 实现Runnable 线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    // 阻塞代码(测试用),增加死锁发生概率
                    // 拥有s2锁,等待s1锁
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

5. 同步线程间通信

多线程在处理同一资源,但是任务却不同

5.1 用到的方法

wait():一旦执行,当前线程进入阻塞状态,并释放监视器(锁);
notify():一旦执行,将会唤醒被 wait的一个线程,多个线程被wait 时,唤醒优先级高的线程;
notifyAll():一旦执行,将会唤醒所有被 wait的线程。

注意:
1)只能用于同步代码块或同步方法中;
2)三个方法的调用者必须是同步代码块或同步方法中的同步监视器(锁);
3)三个方法是Object 类的方法;

5.2 案例

5.2.1 交替打印

/***
 * 使用两个线程交替打印 1~100
 */
public class ThreadCommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}
class Number implements Runnable{
    private int number = 1;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                // 唤醒当前阻塞线程
                notify();
                if(number <= 100){
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    // 当前线程阻塞,执行wait() 会释放锁
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
}

5.2.2 生产者与消费者

生产者将产品交给店员,消费者从店员处取走产品,店员最多持有20个产品,超出则要求生产者暂停生产,低于20再通知生产;没有产品,告诉消费者等一等,有产品后通知消费者取走产品。

/***
 * 生产者与消费者
 * 分析:
 *   线程:生产者、消费者
 *   共享数据:产品
 */
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer producer = new Producer(clerk);
        Consumer customer1 = new Consumer(clerk);

        Consumer customer2 = new Consumer(clerk);

        producer.setName("生产者");
        customer1.setName("消费者1");
        customer2.setName("消费者2");

        producer.start();
        customer1.start();
        customer2.start();
    }
}

// 店员
class Clerk{
    private int productCount = 0;

    // 生产产品
    public synchronized void produceProduct() {
        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第"+productCount +"个产品");
            // 生产完产品,唤醒阻塞的消费者
            notify();
        }else {
            // 等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    // 消费产品
    public synchronized void consumeProduct() {
        if (productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第"+productCount +"个产品");
            productCount--;
            // 消费者买走一个产品,就可以唤醒生产者继续生产
            notify();
        }else {
            // 等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 生产者
class Producer extends Thread{

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true){
            System.out.println(getName() + ": 开始生产产品...");

            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.produceProduct();
        }
    }
}

// 消费者
class Consumer extends Thread{

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {

        while (true){
            System.out.println(getName() + ": 开始消费...");

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值