侧边栏壁纸
博主头像
coydone博主等级

记录学习,分享生活的个人站点

  • 累计撰写 306 篇文章
  • 累计创建 51 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

多线程

coydone
2021-03-18 / 0 评论 / 0 点赞 / 345 阅读 / 15,617 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-01,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

线程概述

计算机的操作系统大多采用多任务和分时设计,多任务是指在一个操作系统中可以同时运行多个程序,例如,可以在使用QQ聊天的同时听音乐,即有多个独立运行的任务,每个任务对应一个进程,每个进程又可以产生多个线程。

串行与并发

串行:多任务按照顺序依次执行 ,就好比一条单行道,里面有很多车,只要前车不前进,后面的车始终会被阻塞在那。

并行**:**就是多个任务同时运行(多个CPU)。 好比一条路有多条道,就可以同时通行多辆车。

并发:是指多个任务同时请求运行,而处理器一次只能接受一个任务,就会把多个任务安排轮流执行,由于CPU时间片运行时间较短,就会感觉多个任务在同时执行。

进程与线程

进程

认识进程先从程序开始。程序(Program)是对数据描述与操作的代码的集合,如Office中的Word、QQ等应用程序。

进程(Process)是程序一次动态执行的过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。操作系统同时管理一个计算机系统的多个进程,让计算机系统中的多个进程轮流使用CPU资源,或者共享操作系统的其他资源。

进程的特点是:

  • 进程是系统运行程序的基本单位。

  • 每一个进程都有自己独立的一块内存空间、一组系统资源。

  • 每一个进程的内部数据和状态都是完全独立的。

当一个应用程序运行的时候都会产生一个进程。Ctrl+alt+delete可以查看任务管理器。

线程

线程是进程中执行运算的最小单位,一个进程在其执行过程中可以产生多个线程,而线程必须在某个内部进程内执行。

线程是进程内部的一个执行单元,是可完成一个独立任务的顺序控制流程,如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。

线程按处理级别分类

核心级线程:核心级线程是和系统任务有关的线程,它负责处理不同进程之间的多个线程。允许不同进程中的线程按照同一相对优先调度方法对线程进行调度,使他们有条不紊地工作,可以发挥多处理器的并发优势,以充分利用计算机的硬/软件资源。

用户级线程:在开发程序时,由于程序需要而编写的线程即用户级线程,这些线程的创建、执行和消亡都是在编写应用程序时进行控制的。对于用户级线程的切换,通常发生在一个应用程序的诸多线程之间。

线程与进程的区别和联系

一个进程中至少要有一个线程。

资源分配给进程,同一进程的所有线程共享该进程的所有资源。

处理机分配给线程,即真正在处理机上运行的是线程。

总结

进程:内存中正在运行的一个应用程序。

线程:进程中某一条执行路线。

多线程:一个应用程序中有多条执行路线。

优点:提升用户体验度,提高计算机系统的利用效率。

多线程程序执行的特点:

1、线程随机的。通过线程抢占CPU执行权决定

2、每条线程的功能都会执行完毕。

线程的生命周期

生命周期

在程序开发中,将一个对象从被实例化完成,到这个对象使用结束,并销毁,将这样的过程称为对象的生命周期。类似于人的一生。

线程的生命周期

一个线程被实例化完成,到这个线程销毁,中间的过程。

线程的状态:

1、新生态:New,一个线程对象被实例化完成,但是还没有做任何的操作。

2、就绪态:Ready,一个线程已经被开启,已经开始争抢CPU时间片。

3、运行态:Run,一个线程抢到了CPU时间片,开始执行这个线程中的逻辑。

4、阻塞态:Interrupt,一个线程在运行的过程中,受到某些操作的影响,放弃了已经获取到的CPU时间片,并且不再参与CPU时间片的争抢,此时线程处于挂起状态。

5、死亡态:Dead,一个线程对象需要被销毁。

Java中的多线程

主线程

main()方法其实就是一个线程:所以main()方法顺序执行,main()方法所在的线程叫主线程。

Java程序的执行过程是:当我们在dos命令行中输入java空格类名回车后,启动JVM,并且加载对应的class文件。虚拟机并会从main方法开始执行我们的程序代码,一直把main()方法的代码执行结束。如下代码演示:

/*
 *进程:进程就是系统中运行的一个应用程序,我们可以打开任务管理器查看进程那一栏
 *线程:进程中包含一个或多个线程,线程控制进程的执行
 *也可以说应用程序中有一个或多个线程
 *main方法其实就是一个线程:所以main方法顺序执行
 *main方法所在的线程叫 主线程
 */
public class Demo {
    public static void main(String[] args) {
        System.out.println(1/0);
    }
}

可以看到 System.out.println(1/0);抛出异常,Exception in thread "main"说明该异常是从main线程中跑抛出的,说明main方法在一个线程中,这个线程就是主线程。

Thread类

Thread类是Java中的线程类。Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

构造方法:

Thread()//分配新的Thread对象
Thread(String name)//分配新的Thread对象,将指定的name作为其线程名称    

常用方法:

void start()//使该线程开始执行,java虚拟机调用该线程的run方法
void run()//该线程要执行的操作,如循环100次打印变量的值。
static void sleep(long millis)//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)    

线程创建方式

常见方式两种:

一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run() 方法。创建对象,开启线程。run()方法相当于其他线程的main()方法。

另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run() 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

继承Thread类

创建线程的步骤:

1、定义一个类继承Thread。

2、重写run()方法。

3、创建子类对象,就是创建线程对象。

4、调用start()方法,开启线程并让线程执行,同时还会告诉JVM去调用run()方法。

/*
1.继承Thread类
1-1 新建类,继承Thread
1-2 重写run()
*/
public class MyThread extends Thread {
    @Override
    public void run() {
        //自己功能
        for(int i=1;i<=10;i++){
            System.out.println("MyThread输出:"+i);
        }
    }
}
public class Demo{
    public static void main(String[] args) {
        //启动自己的线程,循环输出1-10之间所有的整数
        MyThread t1=new MyThread();
        t1.start();//启动t1线程 自动调用run()

        //main主线程:循环输出1-100之间所有的整数
        for(int i=1;i<=100;i++){
            //Thread.currentThread().getName():获取正在执行的线程的名称
            System.out.println("main输出:"+i);
        }
    }
}

线程对象调用 run() 方法和调用 start() 方法区别?

线程对象调用run()方法不开启线程。仅是对象调用方法。线程对象调用start()开启线程,并让JVM调用run()方法在开启的线程中执行。

继承Thread类原理

我们为什么要继承Thread类,并调用其的start()方法才能开启线程呢?继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢?如下代码:

Thread t1 = new Thread();
t1.start();
//这样做没有错,但是该start()调用的是Thread类中的run()方法,
//而这个run()方法没有做什么事情,
//更重要的是这个run()方法中并没有定义我们需要让线程执行的代码。

创建线程的目的是什么?是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。

对于主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run()方法中。

Thread类run()方法中的任务并不是我们所需要的,只有重写这个run()方法。既然Thread类已经定义了线程任务的编写位置(run方法),那么只要在编写位置(run方法)中定义任务代码即可。所以进行了重写run()方法动作。

每个线程执行的代码都会在一个栈中,CPU在多个线程的run()方法的代码中做着随机切换动作。

Thread类的方法

static Thread currentThread()//返回对当前正在执行的线程对象的引用
String getName()//返回该线程的名称    

//Thread.currentThread():获取当前线程对象
//Thread.currentThread().getName():获取当前线程对象的名称  

实现Runnable接口(推荐)

Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法,需要由接口实现类重写该方法。

接口中的方法

void run()//使用实现接口Runnable的对象创建一个线程时,
//启动该线程将导致在独立执行的线程中调用对象的run方法

Thread类构造方法

Thread(Runnable target)//分配新的Thread对象,以便将target作为其运行对象
//分配新的Thread对象,以便将target作为其运行对象,将指定的name作为其名称    
Thread(Runnable target,String name)

创建线程的步骤:

1、定义类实现Runnable接口。

2、覆盖接口中的run方法。

3、创建Thread类的对象

4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

5、调用Thread类的start方法开启线程。

public class Demo02 {
    public static void main(String[] args) {
        //创建线程执行目标类对象
        Runnable runn = new MyRunnable();
        //将Runnable接口的子类对象作为参数传递给Thread类的构造函数
        Thread thread = new Thread(runn);
        Thread thread2 = new Thread(runn);
        //开启线程
        thread.start();
        thread2.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程:正在执行!"+i);
        }
    }
}
public class MyRunnable implements Runnable{
    //定义线程要执行的run方法逻辑
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("我的线程:正在执行!"+i);
        }
    }
}

实现Runnable的原理

为什么需要定一个类去实现Runnable接口呢?继承Thread类和实现Runnable接口有啥区别呢?

实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖Runnable接口中的run方法,将线程任务代码定义到run方法中。

创建Thread类的对象,只有创建Thread类的对象才可以创建线程。线程任务已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。

实现Runnable的好处

第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。

线程的匿名内部类使用

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

方式1:创建线程对象时,直接重写Thread类中的run方法

new Thread() {
	public void run() {
        for (int x = 0; x < 40; x++) {
            System.out.println(Thread.currentThread().getName()
            + "...X...." + x);
        }
    }
}.start();

方式2:使用匿名内部类的方式实现Runnable接口,重写Runnable接口中的run方法

Runnable r = new Runnable() {
      public void run() {
          for (int x = 0; x < 40; x++) {
              System.out.println(Thread.currentThread().getName()
                      + "...Y...." + x);
          }
      }
};
new Thread(r).start();

线程的方法

线程的命名

public class Thread3 {
    public static void main(String[] args) {
        //线程的命名
        // 1.实例化一个线程对象
        // Thread t = new Thread() ;
        // t. setName ( "custom") ;
        // 2.实例化一个线程对象的同时,通过构造方法对线程进行命令
        //Thread( Runnable r, String name )
        // Thread t = new Thread(() -> {}, "custom") ;
        // 3.使用自定义的线程类,在实例化线程对象的同时,进行名称的赋值
        //需要给线程类添加对应的构造方法
        MyThread2 t = new MyThread2("custom");
        System.out.println(t.getName());
    }
}
class MyThread2 extends Thread {
    //无参构造
    public MyThread2() {
    }
    //有参构造
    public MyThread2(String name) {
        //super(name);
        this.setName(name);
    }
}

线程的休眠

public class Thread2 extends Thread {
    @Override
    public void run() {
        //自己功能
        for(int i=1;i<=10;i++){
            System.out.println(i);
            //线程休眠
            //参数:毫秒为单位的时间差
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadMethod {
    public static void main(String[] args) {
        threadSleep();
    }
    private static void threadSleep(){
        //实例化一个线程对象
        Thread2 t1=new Thread2();
        t1.start();//启动t1线程 自动调用run()
    }
}

线程的优先级

public class Thread3 {
    public static void main(String[] args) {
        threadPriority();
    }
    /**
     * 设置线程的优先级...
     */
    private static void threadPriority() {
        //设置线程的优先级,只是修改这个线程可以去抢到CPU时间片的概率。
        //并不是优先级高的线程一定能抢到CPU时间片
        //优先级的设置,是一个整数[0, 10]的整数,默认是5
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        };
        Thread t1 = new Thread(r, "Thread-1");
        Thread t2 = new Thread(r, "Thread-2");
        //设置优先级
        //设置优先级必须要放到这个线程开始执行(start)之前
        t1.setPriority(10);
        t2.setPriority(1);
        t1.start();
        t2.start();
    }
}

守护线程

public final void setDaemon(boolean on):将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。

public final boolean isDaemon():测试这个线程是否是守护线程。

线程的礼让

public class Thread3 {
    public static void main(String[] args) {
        threadYield();
    }
    /**
     * 线程的礼让
     */
    private static void threadYield() {
        //线程礼让,指的是让当前的运行状态的线程释放自己的CPU资源,由运行状态,回到就绪状态。
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    if (i == 3){
                        Thread.yield();
                    }
                }
            }
        };
        Thread t1 = new Thread(r, "Thread-1");
        Thread t2 = new Thread(r, "Thread-2");
        
        t1.start();
        t2.start();
    }
}

线程安全

多线程安全问题:多线程操作同一个数据时,对数据频繁的修改,导致数据最后出现非法数据现象。

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

我们通过一个案例,演示线程的安全问题:

景点要卖票,我们模拟景点的卖票过程。本次只能卖100张票。我们来模拟景点的售票窗口,实现多个窗口同时卖票(多个窗口一起卖这100张票)。需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。

代码:

//某个景点有4个售票员在同时售票
public class SynchronizedDemo {
    public static void main(String[] args) {
        //实例化4个售票员,用4个线程模拟
        Runnable r = new Runnable() {
            @Override
            public void run() {
                while (TicketCenter.restCount>0){
                    System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"+ --TicketCenter.restCount+"张");
                }
            }
        };
        //4个线程,模拟4个售票员,用线程名字模拟售票员名字
        Thread t1 = new Thread(r,"thread-1");
        Thread t2 = new Thread(r,"thread-2");
        Thread t3 = new Thread(r,"thread-3");
        Thread t4 = new Thread(r,"thread-4");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
//售票中心
class TicketCenter{
    //描述剩余的票的数量
    public static int restCount = 100;
}

运行结果发现:上面程序出现了问题,票出现了重复的票。

当多个线程操作同一数据时,这个数据就是临界资源。上述的票就是临界资源。

其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

什么情况下,需要使用同步解决线程安全?

答:当多线程操作同一个共享变量,并且对变量值进行修改。

线程同步(线程安全处理Synchronized)

Java中提供了线程同步机制,它能够解决上述的线程安全问题。

线程同步的方式有两种:

1、同步代码块:在代码块声明上加上synchronized

synchronized (锁对象) {
	可能会产生线程安全问题的代码
}

同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

使用同步代码块,对卖票案例中进行如下代码修改:

//实例化4个售票员,用4个线程模拟
Runnable r = new Runnable() {
    @Override
    public void run() {
        while (TicketCenter.restCount>0){
            //对象锁
            synchronized (""){
                System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"+ --TicketCenter.restCount+"张");
            }
        }
    }
};

运行结果为:

当票库里没有票时,我们不能让他们继续买票

//对临界资源上锁,需要保证多个线程看到的锁是同一把锁
//对象锁
//类锁
synchronized (SynchronizedDemo.class){
    if (TicketCenter.restCount<=0){
         return;
    }
    System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"+ --TicketCenter.restCount+"张");
}

当使用了同步代码块后,上述的线程的安全问题,解决了。

2、同步方法:在方法声明上加上synchronized。

public synchronized void method(){
   	可能会产生线程安全问题的代码
}

同步方法中的锁对象是this

使用同步方法,对卖票案例进行如下代码修改:

//某个景点有4个售票员在同时售票
public class SynchronizedDemo {
    public static void main(String[] args) {
        //实例化4个售票员,用4个线程模拟
        Runnable r = new Runnable() {
            @Override
            public void run() {
                while (TicketCenter.restCount>0){
                    //对临界资源上锁,需要保证多个线程看到的锁是同一把锁
                    //对象锁
                    //类锁
                    soldTicket();
                }
            }
        };
        //4个线程,模拟4个售票员,用线程名字模拟售票员名字
        Thread t1 = new Thread(r,"thread-1");
        Thread t2 = new Thread(r,"thread-2");
        Thread t3 = new Thread(r,"thread-3");
        Thread t4 = new Thread(r,"thread-4");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
    private  static synchronized void soldTicket(){
        if (TicketCenter.restCount<=0){
            return;
        }
        System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"+ --TicketCenter.restCount+"张");
    }
}
//售票中心
class TicketCenter{
    //描述剩余的票的数量
    public static int restCount = 100;
}

静态同步方法:在方法声明上加上static synchronized。

public static synchronized void method(){
	//可能会产生线程安全问题的代码
}

静态同步方法中的锁对象是 类名.class

显式锁ReenTrantLock

显式锁处理线程安全对上述代码进行修改

//实例化一个锁对象
ReentrantLock lock = new ReentrantLock();
//实例化4个售票员,用4个线程模拟
Runnable r = new Runnable() {
    @Override
    public void run() {
        while (TicketCenter.restCount>0){
            //对临界资源上锁,需要保证多个线程看到的锁是同一把锁
           lock.lock();
            if (TicketCenter.restCount<=0){
                return;
            }
            System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+ --TicketCenter.restCount+"张");

            //对临界资源解锁
            lock.unlock();
        }
    }
};

死锁

同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。

synchronzied(A锁){
    synchronized(B锁){
    }
}

我们进行死锁情况的代码演示:

public class DeadLock {
    public static void main(String[] args) {
        //死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁。
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                synchronized ("A") {
                    System.out.println("A线程持有了A锁,等待B锁");
                    synchronized ("B") {
                        System.out.println("A线程同时持有了A锁和B锁");
                    }
                }
            }
        };
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                synchronized ("B") {
                    System.out.println("B线程持有了B锁,等待A锁");
                    synchronized ("A") {
                        System.out.println("B线程同时持有了A锁和B锁");
                    }
                }
            }
        };
        Thread t1 = new Thread(runnable1);
        Thread t2 = new Thread(runnable2);
        t1.start();
        t2.start();
    }
}

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

解决死锁的方式:

1、专门的算法、原则

2、尽量减少同步资源的定义,多个线程访问同一个变量:count。

wait()和notify()方法解决死锁

public class DeadLock {
    public static void main(String[] args) {
        //死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁。

        //wait:等待,是Object类中的一个方法,当前的线程释放自己的锁标记,并且让出CPU资源。使得当前线程进入等待队列中。
        //notify:通知,是Object类中的一个方法,唤醒等待队列中的一个线程,使这个线程进入锁池。
        //notifyAll:通知,是Object类中的一个方法,唤醒等待队列中所有的线程,并使这些线程进入锁池。
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                synchronized ("A") {
                    System.out.println("A线程持有了A锁,等待B锁");

                    //释放已经持有的A锁标记,并进入等待队列
                    try {
                        "A".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized ("B") {
                        System.out.println("A线程同时持有了A锁和B锁");
                    }
                }
            }
        };
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                synchronized ("B") {
                    System.out.println("B线程持有了B锁,等待A锁");
                    synchronized ("A") {
                        System.out.println("B线程同时持有了A锁和B锁");

                        "A".notifyAll();//唤醒等待A的所有线程
                    }
                }
            }
        };

            Thread t1 = new Thread(runnable1);
            Thread t2 = new Thread(runnable2);
            t1.start();
            t2.start();
    }
}

多线程环境下的懒汉式单例

//单例类
class Boss{
    //私有化构造方法
    private Boss(){
        System.out.println("一个Boss对象被实例化了");
    }
    private static Boss Instance = null ;
    public static Boss getBoss(){
        if (Instance == null){
            Instance = new Boss();
        }
        return Instance;
    }
}

单线程下的单例

public class SingletonTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Boss.getBoss();
        }
    }
}

单线程→多线程

public class SingletonTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Boss.getBoss();
            }
        };
        for (int i = 0; i < 100; i++) {
            new Thread(runnable).start();
        }
    }
}

此时Instance为临界资源,解决问题:为Instance加锁。改变Boss类中的代码

//单例类
class Boss{
    //私有化构造方法
    private Boss(){
        System.out.println("一个Boss对象被实例化了");
    }
    private static Boss Instance = null ;
//    public static Boss getBoss(){
//        synchronized (""){
//            if (Instance == null){
//                Instance = new Boss();
//            }
//        }
//        return Instance;
//    }
    public static synchronized Boss getBoss(){
        if (Instance == null){
            Instance = new Boss();
        }
        return Instance;
    }
}

生产者消费者设计模式

//生产者
/**
 * 作用是生产产品
 * 生产逻辑:通过一个生产标记,判断是否需要生产产品
 * 如果需要生产:生产产品,并通知消费者消费
 * 如果不需要生产:等待
 */
//消费者
/**
 * 作用是消费产品
 * 消费逻辑:判断是否有足够的产品可以消费
 * 如果可以消费:获取产品,进行消费
 * 如果不可以消费:等待
 */

程序结构:

src
	com.coydone.procuctorAndConsumer
		Consumer:消费者
		Product:产品类
		Productor:生产者
		ProductPool:产品池
		Program:测试类
package com.coydone.procuctorAndConsumer;

/**
 * 产品类
 */
public class Product {
    private String name;

    public Product(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
package com.coydone.procuctorAndConsumer;

import java.util.LinkedList;
import java.util.List;

/**
 * 产品池
 */
public class ProductPool {
    //存储有所有产品的集合,生产者生产产品,往这个集合中添加元素;
    //消费者消费产品,从这个集合中取出元素
    private List<Product> productList;

    //产品池中产品的最大数量
    private int maxSize = 0;
    public ProductPool(int maxSize) {
        //对产品池进行实例化
        this.productList = new LinkedList<Product>();
        //限定产品的最大数量
        this.maxSize = maxSize;
    }

    /**
     * 生产者将生产好的产品放入产品池
     * @param product
     */
    public synchronized void push(Product product) {
        //判断是否还需要再生产产品
        if (this.productList.size() == maxSize){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //将产品添加到集合中
        this.productList.add(product);
        //通知其他人,有产品可以消费了
        this.notifyAll();

    }

    /**
     * 消费者从产品池中取出一件产品消费
     * @return
     */
    public synchronized Product pop(){
        //判断是否还有产品再去消费
        if (this.productList.size() == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //从产品池中取出一件产品
        Product product = this.productList.remove(0);
        //通知其他人,我取出了一件产品
        this.notifyAll();

        return product;
    }

}
package com.coydone.procuctorAndConsumer;
/**
 * 生产者
 */
public class Productor extends Thread{

    private  ProductPool pool;
    public Productor(ProductPool pool){
        this.pool = pool;
    }
    @Override
    public void run() {
        while (true) {
            String name = (int)(Math.random()*100)+"号产品";
            System.out.println("生产者生产了一件产品:"+name);
            Product product = new Product(name);
            this.pool.push(product);

        }
    }
}
package com.coydone.procuctorAndConsumer;

public class Consumer extends Thread{
    private  ProductPool pool;
    public Consumer(ProductPool pool){
        this.pool = pool;
    }
    @Override
    public void run() {
        while (true) {
            Product product = this.pool.pop();
            System.out.println("消费者消费了一件产品:"+product.getName());

        }
    }
}
package com.coydone.procuctorAndConsumer;

public class Program {
    public static void main(String[] args) {
        //实例化一个产品池
        ProductPool pool = new ProductPool(5);

        //实例化一个生产者
        new Productor(pool).start();

        //实例化一个消费者
        new Consumer(pool).start();
    }
}

0

评论区