1 线程概述
1.1进程和程序
- 程序是指令和数据的集合,其本身没有任何运行的含义,是一个静态的概念,换句话说,程序就是一些代码.
- 而进程是程序执行的一次过程,是一个动态的概念,是系统资源分配的最小单位.
- 通常一个进程中可以包含若干个线程,线程是CPU调度执行的最小单位.同一个进程里的多个线程共享进程的资源.
注意: 真正的多线程是指有多个CPU,即多核.
1.2关于线程
- 线程是独立的执行路径
- 在线程运行时,即使没有自己的创建的线程,后台也会有多个线程,如主线程,gc线程(垃圾回收线程)
- main()成之为主线程,为系统的入口,用于执行整个程序
- 在一个线程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是操作系统紧密相关的,先后顺序是不能认为干预的.
- 对同一份资源操作时,会存在资源争夺的问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存中交互,内存控制不当会造车数据的不一致.
1.3并发与并行
- 同一时刻,多个任务交替执行,造成一种"貌似同时"的错觉,简单的说,单核cup是实现的多任务就是并发
- 同一时刻都多个任务同时执行,多核CPU可以实现并行.
1.4 java中怎么创建线程类的三种方式
- 继承Thread类的方式
- 实现Runnable接口的方式
- 实现Callable接口的方式,这个方式有返回值的,可以带回反回值.
方式一:继承Thread类
package hai.bok.thread.dem01;
/**
* @auther 海文宇
* @Date 2022/3/15 22:46
* @Description
*/
public class MyThread extends Thread{
//重写run方法
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("子线程:"+i);
}
}
}
package hai.bok.thread.dem01;
import hai.bok.thread.dem01.MyThread;
/**
* @auther 海文宇
* @Date 2022/3/15 22:43
* @Description
* 线程启动的两种方式
* 1.继承呢过Thread类
* 步骤:1.自定义一个类继承Thread类
* 2.重写run方法
* 3.创建线程
* 4.启动线程
* 注意:
* 1.开启线程是调用的线程对象是start方法,不是run方法
* 调用run方法 没有多线程的效果,就跟调用你的普通方法一样.
* 2.线程不能多次启动,只能启动一次,(只能调用start方法一次)
* 2.实现Runnable类
* 步骤:1.自定义一个类实现Runnable类
* 2.重写run方法,编写子线程的任务
* 3.创建Thread类,用构造方法把Runnable接口的子类作为参数传入
* 4.启动线程
*
*/
public class TestThread {
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//启动线程对
myThread.start();//有启动的话会出现线程抢占的效果
// myThread.run();//可以执行,但是没有线程抢占的效果
//主线程
for (int i = 0; i < 10000; i++) {
System.out.println("主线程:"+i);
}
}
}
方式二:实现Runnable接口
package hai.bok.thread.dem01;
/**
* @auther 海文宇
* @Date 2022/3/15 23:10
* @Description
* 使用Runnable接口实现
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1000; i < 2000; i++) {
System.out.println("子线程:"+i);
}
}
}
package hai.bok.thread.dem01;
import hai.bok.thread.dem01.MyRunnable;
/**
* @auther 海文宇
* @Date 2022/3/15 23:12
* @Description
*/
public class TestRunnable {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
//创建Thread类,用构造方法把Runnable接口的子类作为参数传入
Thread thread=new Thread(mr);
//启动线程
thread.start();
//主线程
for (int i = 1; i <= 100; i++) {
System.out.println("主线程:"+i);
}
}
}
方式三:实现Callable接口的方式
package hai.bok.thread.dem02;
import java.util.concurrent.Callable;
/**
* @auther 海文宇
* @Date 2022/3/15 23:36
* @Description
* 实现Callable的接口的方式
*/
public class CallableThread implements Callable<Integer> {
private int m;
private int n;
public CallableThread() {
}
public CallableThread(int m, int n) {
this.m = m;
this.n = n;
}
public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
//run方法有返回值,还带了一个异常
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = m; i <=n; i++) {
System.out.println("子线程"+i);
sum+=i;
}
return sum;
}
}
package hai.bok.thread.dem02;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* @auther 海文宇
* @Date 2022/3/15 23:32
* @Description
* 之前的线程的启动,run()方法没有返回值,在主线程中没有办法获取子线程的返回结果
* 所有这里可以使用Callable接口的方式来开启线程
* 需求:计算m~n的和
*/
public class Test01 {
public static void main(String[] args) {
//使用Callable接口,创建类
CallableThread ct=new CallableThread(1,100);
//创建FutureTast对象,有返回值类型是Integer;
FutureTask<Integer> task=new FutureTask<Integer>(ct);
//创建线程对象,把task当做参数传递
Thread thread=new Thread(task);
//启动线程
thread.start();
//主线程
for (int i = 0; i < 1000; i++) {
System.out.println("主线程:"+i);
}
try {
Integer sum = task.get();//获取子线程的结果
System.out.println("子线程的计算结果:"+sum);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.4 使用匿名内部类的方式实现线程类
- 使用匿名内部类 方式开启线程,节约内存资源,同时也会更加简洁
- 当匿名内部类方式同时继承Thread类方式和实现Runnable接口方式
package hai.bok.thread.dem03;
/**
* @auther 海文宇
* @Date 2022/3/16 0:12
* @Description
* 1.使用匿名内部类 方式开启线程,节约内存资源,同时也会更加简洁
* 2.当匿名内部类方式同时继承Thread类方式和实现Runnable接口方式两种启动,以Thread类方式优先
*/
public class Test01 {
public static void main(String[] args) {
//没有重写run方法分配任务,匿名线程对象启动.
new Thread().start();
//匿名内部类线程对象的启动,没有run方法,也没有分配任务
new Thread(){}.start();
//匿名内部类线程对象的启动,重写run方法
new Thread(){
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("匿名内部类继承Thread类线程对象的启动方式:"+i);
}
}
}.start();
//匿名内部类Tread类线程对象实现Runnable接口
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("匿名内部类Tread类线程对象实现Runnable接口:"+i);
}
}
}).start();
//匿名内部类继承Tread类线程对象同时实现Runnable接口的方式
new Thread(new Runnable() {
@Override
public void run() {
//使用Runnable方式
for (int i = 0; i < 1000; i++) {
System.out.println("匿名内部类实现Runnable接口2:"+i);
}
}
}){
//继承Thread类实现方式
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("匿名内部类继承Thread类线程对象的启动方式2:"+i);
}
}
}.start();;
}
}
2 线程的常用方法
1 设置名称
构造方法:
- Thread(Runnable target,String)传入Runnable方式,命名.
- Thread(String name)命名
方法:
- String getName()获取某个线程的名称
- void setName(String name)设置线程的名称
- Static Thread.currentThread()获取当前的线程.
2 设置优先级
2.1.线程优先级的特点:
- 随机性和不确定性,系统随机分配,哪个线程抢到了执行权我们是不能控制的
- java使用的是抢占式调度模式(即优先级高的线程使用cpu,优先级相同的任意选择一个执行),优先级高的线程会相对来说获得CPU时间片段多一点
2.2 设置优先级的字段和方法
- static int MAX_PRIPORITY=10: 线程可以拥有的最大优先级
- static int MIN_PRIPORITY=1: 线程可以拥有的最小优先级
- static int NORM_PRIPORITY=5: 配给线程的默认优先级
- void setPripority(int newPripority)更改此线程的优先级
- int getPripority()返回此线程的优先级
注意
设置了优先级,也不能够从优先级的测试具体的体现一定性,(也就是说,设置了优先级,优先级高的不一定就是一定先执行完.),只是说线程或得CUP执行的时间会多一点,线程的任务耗费时间设置的长一点,效果体现更加明显.
package hai.bok.thread.dem05;
/**
* @auther 海文宇
* @Date 2022/3/16 21:05
* @Description
*/
public class PriporityThread extends Thread {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(getName()+":"+i);
}
}
}
package hai.bok.thread.dem05;
/**
* @auther 海文宇
* @Date 2022/3/16 21:04
* @Description
* 练习优先级
*/
public class Test01 {
public static void main(String[] args) {
//差创建3个子线程
PriporityThread p1 = new PriporityThread();
PriporityThread p2 = new PriporityThread();
PriporityThread p3 = new PriporityThread();
//设置三个线程的名字
p1.setName("李四");
p2.setName("张三");
p3.setName("王五");
//设置优先级,优先级的范围是1~10,超出这个范围会报错
p1.setPriority(Thread.MAX_PRIORITY);//10
p2.setPriority(Thread.NORM_PRIORITY);//5
p3.setPriority(Thread.MIN_PRIORITY);//1
//启动线程
p1.start();
p2.start();
p3.start();
}
}
3 线程休眠的方法
- static void sleep(long millis):使当前正在执行的线程指定的毫秒数暂停(暂时停止执行)具体取决于系统器,和调度程序的精确度和准确性.
- 可以使用线程休眠实现时钟效果.
package hai.bok.thread.dem05;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @auther 海文宇
* @Date 2022/3/16 21:37
* @Description
*/
public class SleepTread extends Thread {
@Override
public void run() {
//死循环
while (true){
//每休眠一秒显示一次时间
String format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
System.out.println(getName()+":"+format);
try {
Thread.sleep(1000);//休眠一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package hai.bok.thread.dem05;
/**
* @auther 海文宇
* @Date 2022/3/16 21:45
* @Description
*/
public class TestSleepThread {
public static void main(String[] args) {
SleepTread st = new SleepTread();
st.setName("时钟");
st.start();
}
}
4 线程加入的方法
- void join() 等待这个线程死亡
- 调用join()方法会让某个线程优先执行结束
- 同样的这也不是绝对的,设置了join,只是相对优先执行的概论会大一点,没有绝对的就一定是执行完.
package hai.bok.thread.dem05;
/**
* @auther 海文宇
* @Date 2022/3/16 21:55
* @Description
*/
public class JoinThread extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName()+":"+i);
}
}
}
package hai.bok.thread.dem05;
/**
* @auther 海文宇
* @Date 2022/3/16 21:57
* @Description
*/
public class TestJoinThread {
public static void main(String[] args) {
//创建3个线程
JoinThread j1 = new JoinThread();
JoinThread j2 = new JoinThread();
JoinThread j3 = new JoinThread();
//设置名字
j1.setName("张三");
j2.setName("李四");
j3.setName("王五");
//打开线程
j1.start();
try {
//j1子线程加入,优先让这个线程执行.
j1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
j2.start();
j3.start();
}
}
5 线程礼让的方法
- static void yield() 唯一一个可以让出资源的方法,让出当前线程的执行权,让出后自己排队等待cup的执行权.也会继续抢夺cpu资源
package hai.bok.thread.dem05;
/**
* @auther 海文宇
* @Date 2022/3/16 22:45
* @Description
*/
public class YieldThread extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName()+":"+i);
//让出线程执行权,让出后自己又会去排队等待cup的执行权,会和其他的线程抢夺资源
Thread.yield();
}
}
}
package hai.bok.thread.dem05;
/**
* @auther 海文宇
* @Date 2022/3/16 22:49
* @Description
*/
public class TestYield {
public static void main(String[] args) {
YieldThread t1 = new YieldThread();
YieldThread t2 = new YieldThread();
t1.setName("哥哥");
t2.setName("妹妹");
t1.start();
t2.start();
}
}
3 线程的5种状态
- 创建状态:new线程类后就创建了
- 就绪状态:线程创建后就就进入了就绪态,开始的等待CUP资源
- 运行状态:调用start方法开始运行.
- 阻塞状态:线程遇到输入输出等,进入阻塞状态,请求资源
- 结束状态:运行结束后,线程结束.
4 线程同步(同步方法,同步代码块)
- 需求:火车售票,总共有100票,3个窗口
- 使用Runnable接口的方式和继承Thread类的方式实现
1.1 第一种方式:继承Thread类的方式
- 解决方式一:同步代码块synchronized(锁对象){}
- 解决方式二:同步方法public synchronized 返回值 方法名(参数){}
- 解决方式三:Java提供的锁机制Lock锁
package hai.bok.thread.dem06;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @auther 海文宇
* @Date 2022/3/16 23:41
* @Description
* 方式一:继承Thread类的方式
*/
public class SellTickeys extends Thread{
private static int tickets=100;//100张票属于3个窗口,100属于共享数据
public static Lock lock = new ReentrantLock();
//java.util.concurrent.locks Interface Lock
//Lock锁对象,void lock() 获得锁。void unlock() 释放锁。
@Override
public void run() {
//模拟售票
//解决方式一:同步代码块synchronized(锁对象){}
/*synchronized (MyLock.LOCK){//可以使用this,当前类,定义的类作为一把锁
while(true){
if(tickets>0){
//设置休眠
try {
//线程休眠
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"正在售出第"+(tickets--)+"张票");
}
}
}*/
//解决方式二:
//sellTickets();
//解决方式三:Java提供的锁机制Lock锁
//使用try...finally{}来加锁和释放锁,如果try块有异常,一定会执行finally释放锁的操作
try {
//加锁
lock.lock();
while (true){
if (tickets > 0){
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + "正在售出第" + (tickets--) + "张票");
}
}
} finally {
//释放锁
lock.unlock();
}
//使用JDK提供Lock锁,不需要我们自己定义锁的
//直接使用它的方法就好了,这个使用更加方便,更加满足面向对象思想
}
// 解决方式二:同步方法public synchronized 返回值 方法名(参数){}
/**
* 三个子线程对象都可以调用这个普通方法,而不是三个方法属
* 于三个子线程对象的,应该是一份这个方法给三个子线程对象使用,这样才能真正上锁
* 所以方法得用static修饰
* */
public static synchronized void sellTickets(){//
while(true){
//解决方式一:同步代码块synchronized(锁对象){}
if(tickets>0){
//设置休眠
try {
//线程休眠
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"正在售出第"+(tickets--)+"张票");
}
}
}
}
//定义一把锁类
class MyLock{
public static final MyLock LOCK=new MyLock();
}
package hai.bok.thread.dem06;
/**
* @auther 海文宇
* @Date 2022/3/16 23:49
* @Description
*/
public class TestSellTickets {
public static void main(String[] args) {
SellTickeys t1 = new SellTickeys();
SellTickeys t2 = new SellTickeys();
SellTickeys t3 = new SellTickeys();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
//启动
t1.start();
t2.start();
t3.start();
}
}
1.2 第二种方式:实现Runnable接口的方式
- 解决方式一:同步代码块synchronized(锁对象){}
- 解决方式二:同步方法public synchronized 返回值 方法名(参数){}
- 解决方式三:Java提供的锁机制Lock锁
package hai.bok.thread.dem06;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @auther 海文宇
* @Date 2022/3/17 1:06
* @Description
*/
public class SellTicketsRunnable implements Runnable{
private static int tickets=100;
private static Lock lock=new ReentrantLock();
@Override
public void run() {
// 解决方式一:同步代码块
/* synchronized (MyLock_.LOCK){
while (true){
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"正在售出第"+(tickets--)+"张票");
}
}
}*/
//解决方式二;
// sellTickets();
//解决方式三
try{
lock.lock();
while(true){
//解决方式一:同步代码块synchronized(锁对象){}
if(tickets>0){
//设置休眠
try {
//线程休眠
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"正在售出第"+(tickets--)+"张票");
}
}
}finally {
lock.unlock();
}
}
public static synchronized void sellTickets(){//
while(true){
//解决方式一:同步代码块synchronized(锁对象){}
if(tickets>0){
//设置休眠
try {
//线程休眠
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"正在售出第"+(tickets--)+"张票");
}
}
}
}
class MyLock_ {
public static final MyLock LOCK=new MyLock();
}
package hai.bok.thread.dem06;
/**
* @auther 海文宇
* @Date 2022/3/17 1:09
* @Description
*/
public class Test01 {
public static void main(String[] args) {
SellTicketsRunnable tr = new SellTicketsRunnable();
Thread thread1 = new Thread(tr);
Thread thread2 = new Thread(tr);
Thread thread3 = new Thread(tr);
thread1.setName("窗口1:");
thread2.setName("窗口2:");
thread3.setName("窗口3:");
thread1.start();
thread2.start();
thread3.start();
}
}
5 线程死锁
- 线程同步的优点:接c解决数据安全问题
- 线程同步的缺点:容易造成死锁现象
死锁: 死锁指多个线程在执行过程中,他们在抢夺资源的时候,产生的一种相互等待的现象.
6 线程分组
线程组:把多个线程分为一个组,方便统一管理多个线程.
Java API提供这些对线程的一个分组,目的就是可以把一个组的多个线程当做一个单元的一个组的,对一个组的线程再访问的时候就方便直接对他们由一个操作.
1.1线程的分组
- Thread(ThreadGroup group, Runnable target) 分配新的 Thread 对象。
- Thread(ThreadGroup group, Runnable target, String name) 分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。
- Thread(ThreadGroup group, Runnable target, String name, long stackSize) 分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。
- Thread(ThreadGroup group, String name) 分配新的 Thread 对象。
package hai.bok.thread.dem08;
/**
* @auther 海文宇
* @Date 2022/3/18 16:06
* @Description
*/
public class Test01MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package hai.bok.thread.dem08;
import java.util.ArrayList;
import java.util.List;
/**
* @auther 海文宇
* @Date 2022/3/18 16:05
* @Description
*/
public class Test01 {
public static void main(String[] args) {
Test01MyRunnable myRunnable=new Test01MyRunnable();
//第一组
ThreadGroup tg1=new ThreadGroup("三国演义");//分组名称
Thread t1=new Thread(tg1,myRunnable,"刘备");
Thread t2=new Thread(tg1,myRunnable,"曹操");
Thread t3=new Thread(tg1,myRunnable,"孙权");
//第二组
ThreadGroup tg2=new ThreadGroup("西游记");
Thread t4=new Thread(tg2,myRunnable,"孙悟空");
Thread t5=new Thread(tg2,myRunnable,"沙悟净");
Thread t6=new Thread(tg2,myRunnable,"猪八戒");
//启动
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
// activeCount() 返回此线程组中活动线程的估计数
// getName()返回此线程组的名称。
System.out.println(t1.getName());
System.out.println(t3.getThreadGroup().getName());
System.out.println(tg2.activeCount());
tg1.setDaemon(true);//设置守护线程
tg2.stop();//终止一个组
//获取主线程的名称
Thread main=Thread.currentThread();
String name = main.getThreadGroup().getName();
System.out.println(name);
}
}
1.2等待唤醒机制
- void notify() 唤醒正在等待对象监视器的单个线程。
- void notifyAll() 唤醒正在等待对象监视器的所有线程。
- void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
生产者消费者模型
- 玩具类
- 编写生产者
- 编写消费者
- 编写测试类
生产者
先判断是否有玩具
如果有,就等待消费者消费,wait
如果没有 ,就需要生产,生产完之后通知消费者,notify
消费者
先判断是否有玩具
如果有,就消费,
如果没有,就等待生产者生产
Object类中等待和唤醒方法:
- wait():让某个线程是等待状态(阻塞状态)会将线程放到等待池中,而且会释放锁
- notify():唤醒在同一把锁上处于等待的某个线程
- notifyAll():唤醒在同一把锁上处于等待的所有线程
注意: 顺序和加锁,不会抛出异常,如果没有按照顺序先唤醒后等待,执行的时候会抛出异常。
玩具类
package hai.bok.thread.dem09;
/**
* @auther 海文宇
* @Date 2022/3/18 21:34
* @Description
* 生产者消费者问题
* 玩具类
*/
public class Toy {
//属性:名称,数量,是否有商品玩具
private String name;
private int num;
private boolean flag;//true,有玩具,false,没有玩具
public Toy() {
}
public Toy(String name, int num, boolean flag) {
this.name = name;
this.num = num;
this.flag = flag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
生产者类:
package hai.bok.thread.dem09;
/**
* @auther 海文宇
* @Date 2022/3/18 21:38
* @Description
* 生产者
* 先判断是否有玩具
* 如果有,就等待消费者消费,wait
* 如果没有 ,就需要生产,生产完之后通知消费者,notify
*/
public class setThread extends Thread{
//玩具类
private Toy t;
//玩具种类有很多,可以生产多种,或者来控制生产个数
private int i;
public setThread() {
}
public setThread(Toy t) {
this.t = t;
}
public Toy getT() {
return t;
}
public void setT(Toy t) {
this.t = t;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
@Override
public void run() {
while (true){
//锁
synchronized (t){
//判断是否有玩具类
if(t.isFlag()){
//有,就等待
try {
t.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有,就需要生产,生产完之后通知消费者,notify
if(i%2==0){//偶数,生产叮当猫玩具,生产数量为10个
t.setName("叮当猫");
t.setNum(10);
}else {//基数,生产汤姆猫
t.setName("汤姆猫");
t.setNum(5);
}
i++;
//要生产,设置true,代表有数据,设置false,代表没有数据.
//设置标志位,代表这里有数据了
t.setFlag(true);
//生产完之后通知消费者,notify
t.notify();
}
}
}
}
消费者类:
package hai.bok.thread.dem09;
/**
* @auther 海文宇
* @Date 2022/3/18 21:58
* @Description
* 消费者
* 先判断是否有玩具
* 如果没有有,就等待生产者生产
* 如果有,就消费
*/
public class GetThread extends Thread {
//玩具类
private Toy t;
public GetThread() {
}
public GetThread(Toy t) {
this.t = t;
}
@Override
public void run() {
while (true){
//一把锁
synchronized (t){
//先判断是否有玩具
if(!t.isFlag()){//如果没有,就等待生产者生产
try {
t.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有,就消费
String toyName = t.getName();//获取玩具名称
int toyNum = t.getNum();//获取数量
System.out.println(toyName+"|"+toyNum);
//消费后,数量减一
t.setNum(--toyNum);
//消费完了,就通知生产者生产
if(toyNum<=0){
//设置标记位,代表没有玩具
t.setFlag(false);
//通知生产
t.notify();
}
}
}
}
}
测试类:
package hai.bok.thread.dem09;
/**
* @auther 海文宇
* @Date 2022/3/18 22:12
* @Description
*/
public class Test {
public static void main(String[] args) {
//玩具类
Toy toy = new Toy();
//生产者线程
setThread st = new setThread(toy);
//消费者线程
GetThread gt = new GetThread(toy);
st.start();
gt.start();
}
}