线程概述
计算机的操作系统大多采用多任务和分时设计,多任务是指在一个操作系统中可以同时运行多个程序,例如,可以在使用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();
}
}
评论区