Java多线程超详解

article/2025/10/9 13:18:35

引言

随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。
那么话不多说,今天本帅将记录自己线程的学习。

程序,进程,线程的基本概念+并行与并发:

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

即:线程《线程(一个程序可以有多个线程)
程序:静态的代码 进程:动态执行的程序
线程:进程中要同时干几件事时,每一件事的执行路径成为线程。

并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

线程的相关API

//获取当前线程的名字
Thread.currentThread().getName()

1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活

判断是否是多线程

一条线程即为一条执行路径,即当能用一条路径画出来时即为一个线程
例:如下看似既执行了方法一,又执行了方法2,但是其实质就是主线程在执行方法2和方法1这一条路径,所以就是一个线程

public class Sample{public void method1(String str){System.out.println(str);}public void method2(String str){method1(str);}public static void main(String[] args){Sample s = new Sample();s.method2("hello");}
}

在这里插入图片描述

线程的调度

调度策略:
时间片:线程的调度采用时间片轮转的方式
抢占式:高优先级的线程抢占CPU
Java的调度方法:
1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
2.对高优先级,使用优先调度的抢占式策略

线程的优先级

等级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5

方法:
getPriority():返回线程优先级
setPriority(int newPriority):改变线程的优先级

注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

多线程的创建方式

1. 方式1:继承于Thread类

1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法

start与run方法的区别:

start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)
在这里插入图片描述

多线程例子(火车站多窗口卖票问题)

	package com.example.paoduantui.Thread;import android.view.Window;/**** 创建三个窗口卖票,总票数为100张,使用继承自Thread方式* 用静态变量保证三个线程的数据独一份* * 存在线程的安全问题,有待解决** */public class ThreadDemo extends Thread{public static void main(String[] args){window t1 = new window();window t2 = new window();window t3 = new window();t1.setName("售票口1");t2.setName("售票口2");t3.setName("售票口3");t1.start();t2.start();t3.start();}}class window extends Thread{private static int ticket = 100; //将其加载在类的静态区,所有线程共享该静态变量@Overridepublic void run() {while(true){if(ticket>0){//                try {//                    sleep(100);//                } catch (InterruptedException e) {//                    e.printStackTrace();//                }System.out.println(getName()+"当前售出第"+ticket+"张票");ticket--;}else{break;}}}}

2. 方式2:实现Runable接口方式

1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()

具体操作,将一个类实现Runable接口,(插上接口一端)。
另外一端,通过实现类的对象与线程对象通过此Runable接口插上接口实现

	package com.example.paoduantui.Thread;public class ThreadDemo01 {public static  void main(String[] args){window1 w = new window1();//虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票Thread t1=new Thread(w);Thread t2=new Thread(w);Thread t3=new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}}class window1 implements Runnable{private int ticket = 100;@Overridepublic void run() {while(true){if(ticket>0){//                try {//                    sleep(100);//                } catch (InterruptedException e) {//                    e.printStackTrace();//                }System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");ticket--;}else{break;}}}}

比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

3.新增的两种创建多线程方式

1.实现callable接口方式:

与使用runnable方式相比,callable功能更强大些:
runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

package com.example.paoduantui.Thread;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 创建线程的方式三:实现callable接口。---JDK 5.0新增*是否多线程?否,就一个线程** 比runable多一个FutureTask类,用来接收call方法的返回值。* 适用于需要从线程中接收返回值的形式* * //callable实现新建线程的步骤:* 1.创建一个实现callable的实现类* 2.实现call方法,将此线程需要执行的操作声明在call()中* 3.创建callable实现类的对象* 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象* 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)* * *///实现callable接口的call方法
class NumThread implements Callable{private int sum=0;////可以抛出异常@Overridepublic Object call() throws Exception {for(int i = 0;i<=100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName()+":"+i);sum += i;}}return sum;}
}public class ThreadNew {public static void main(String[] args){//new一个实现callable接口的对象NumThread numThread = new NumThread();//通过futureTask对象的get方法来接收futureTask的值FutureTask futureTask = new FutureTask(numThread);Thread t1 = new Thread(futureTask);t1.setName("线程1");t1.start();try {//get返回值即为FutureTask构造器参数callable实现类重写的call的返回值Object sum = futureTask.get();System.out.println(Thread.currentThread().getName()+":"+sum);} catch (ExecutionException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}
}
使用线程池的方式:

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

JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池。

Executors工具类,线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool()创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n)创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n)创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

线程池构造批量线程代码如下:

package com.example.paoduantui.Thread;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 创建线程的方式四:使用线程池(批量使用线程)*1.需要创建实现runnable或者callable接口方式的对象* 2.创建executorservice线程池* 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。* 4.关闭线程池** */class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0;i<=100;i++){if (i % 2 ==0 )System.out.println(Thread.currentThread().getName()+":"+i);}}
}class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0;i<100; i++){if(i%2==1){System.out.println(Thread.currentThread().getName()+":"+i);}}}
}public class ThreadPool {public static void main(String[] args){//创建固定线程个数为十个的线程池ExecutorService executorService = Executors.newFixedThreadPool(10);//new一个Runnable接口的对象NumberThread number = new NumberThread();NumberThread1 number1 = new NumberThread1();//执行线程,最多十个executorService.execute(number1);executorService.execute(number);//适合适用于Runnable//executorService.submit();//适合使用于Callable//关闭线程池executorService.shutdown();}}

目前两种方式要想调用新线程,都需要用到Thread中的start方法。

java virtual machine(JVM):java虚拟机内存结构

程序(一段静态的代码)——————》加载到内存中——————》进程(加载到内存中的代码,动态的程序)
进程可细分为多个线程,一个线程代表一个程序内部的一条执行路径
每个线程有其独立的程序计数器(PC,指导着程序向下执行)与运行栈(本地变量等,本地方法等)
在这里插入图片描述

大佬传送门:https://blog.csdn.net/bluetjs/article/details/52874852

线程通信方法:

wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

由于wait,notify,以及notifyAll都涉及到与锁相关的操作
wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。

所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

线程的分类:

java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)

若JVM中都是守护线程,当前JVM将退出。(形象理解,唇亡齿寒)

线程的生命周期:

JDK中用Thread.State类定义了线程的几种状态,如下:

线程生命周期的阶段描述
新建当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪处于新建状态的线程被start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能
阻塞在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
死亡线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

在这里插入图片描述

线程的同步:在同步代码块中,只能存在一个线程。

线程的安全问题:

什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

上述例子中:创建三个窗口卖票,总票数为100张票
1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
4.在java中,我们通过同步机制,来解决线程的安全问题。

方式一:同步代码块
使用同步监视器(锁)
Synchronized(同步监视器){
//需要被同步的代码
}
说明:

  1. 操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
  2. 共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
  3. 同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)

Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

方式二:同步方法
使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身。继承自Thread。class

方式三:JDK5.0新增的lock锁方法

package com.example.paoduantui.Thread;import java.util.concurrent.locks.ReentrantLock;class Window implements Runnable{private int ticket = 100;//定义一百张票//1.实例化锁private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {//2.调用锁定方法locklock.lock();if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票");ticket--;} else {break;}}}
}public class LockTest {public static void main(String[] args){Window w= new Window();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口1");t3.setName("窗口1");t1.start();t2.start();t3.start();}}

总结:Synchronized与lock的异同?

相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)

优先使用顺序:
LOCK-》同步代码块-》同步方法

判断线程是否有安全问题,以及如何解决:

1.先判断是否多线程
2.再判断是否有共享数据
3.是否并发的对共享数据进行操作
4.选择上述三种方法解决线程安全问题

例题:

	package com.example.paoduantui.Thread;/**** 描述:甲乙同时往银行存钱,存够3000*** *///账户class Account{private double balance;//余额//构造器public Account(double balance) {this.balance = balance;}//存钱方法public synchronized void deposit(double amt){if(amt>0){balance +=amt;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);}}}//两个顾客线程class Customer extends Thread{private Account acct;public Customer(Account acct){this.acct = acct;}@Overridepublic void run() {for (int i = 0;i<3;i++){acct.deposit(1000);}}}//主方法,之中new同一个账户,甲乙两个存钱线程。public class AccountTest {public static void main(String[] args){Account acct = new Account(0);Customer c1 = new Customer(acct);Customer c2 = new Customer(acct);c1.setName("甲");c2.setName("乙");c1.start();c2.start();}}

解决单例模式的懒汉式的线程安全问题:

单例:只能通过静态方法获取一个实例,不能通过构造器来构造实例
1.构造器的私有化:
private Bank(){}//可以在构造器中初始化东西
private static Bank instance = null;//初始化静态实例

public static Bank getInstance(){
if(instance!=null){
instance = new Bank();
}
return instance;
}

假设有多个线程调用此单例,而调用的获取单例的函数作为操作共享单例的代码块并没有解决线程的安全问题,会导致多个线程都判断实例是否为空,此时就会导致多个实例的产生,也就是单例模式的线程安全问题。

解决线程安全问题的思路:

  1. 将获取单例的方法改写成同部方法,即加上synchronized关键字,此时同步监视器为当前类本身。(当有多个线程并发的获取实例时,同时只能有一个线程获取实例),解决了单例模式的线程安全问题。
  2. 用同步监视器包裹住同步代码块的方式。

懒汉式单例模式的模型,例如:生活中的限量版的抢购:
当一群人并发的抢一个限量版的东西的时候,可能同时抢到了几个人,他们同时进入了房间(同步代码块内)
但是只有第一个拿到限量版东西的人才能到手,其余人都不能拿到,所以效率稍高的做法是,当东西被拿走时,我们在门外立一块牌子,售罄。
这样就减少了线程的等待。即下面效率稍高的懒汉式写法:

package com.example.paoduantui.Thread;public class Bank {//私有化构造器private Bank(){}//初始化静态实例化对象private static  Bank instance = null;//获取单例实例,此种懒汉式单例模式存在线程不安全问题(从并发考虑)public static  Bank getInstance(){if(instance==null){instance = new Bank();}return  instance;}//同步方法模式的线程安全public static synchronized Bank getInstance1(){if(instance==null){instance = new Bank();}return  instance;}//同步代码块模式的线程安全(上锁)public  static Bank getInstance2(){synchronized (Bank.class){if(instance==null){instance = new Bank();}return  instance;}}//效率更高的线程安全的懒汉式单例模式/*** 由于当高并发调用单例模式的时候,类似于万人夺宝,只有第一个进入房间的人才能拿到宝物,* 当多个人进入这个房间时,第一个人拿走了宝物,也就另外几个人需要在同步代码块外等候,* 剩下的人只需要看到门口售罄的牌子即已知宝物已经被夺,可以不用进入同步代码块内,提高了效率。* * * */public static Bank getInstance3(){if (instance==null){synchronized (Bank.class){if(instance==null){instance = new Bank();}}}return  instance;}
}

线程的死锁问题:

线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续

package com.example.paoduantui.Thread;/*** 演示线程的死锁问题** */
public class Demo {public static void main(String[] args){final StringBuffer s1 = new StringBuffer();final StringBuffer s2 = new StringBuffer();new Thread(){@Overridepublic void run() {//先拿锁一,再拿锁二synchronized (s1){s1.append("a");s2.append("1");synchronized (s2) {s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();//使用匿名内部类实现runnable接口的方式实现线程的创建new Thread(new Runnable() {@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");synchronized (s1) {s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}).start();}}

运行结果:
1.先调用上面的线程,再调用下面的线程:
在这里插入图片描述
2.出现死锁:
在这里插入图片描述
3.先调用下面的线程,再调用上面的线程。
在这里插入图片描述

死锁的解决办法:

1.减少同步共享变量
2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
3.减少锁的嵌套。

线程的通信

通信常用方法:

通信方法描述
wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
notifyAll一旦执行此方法,就会唤醒所有被wait()的线程

使用前提:这三个方法均只能使用在同步代码块或者同步方法中。

package com.example.paoduantui.Thread;/*** 线程通信的例子:使用两个线程打印1—100,线程1,线程2交替打印** 当我们不采取线程之间的通信时,无法达到线程1,2交替打印(cpu的控制权,是自动分配的)* 若想达到线程1,2交替打印,需要:* 1.当线程1获取锁以后,进入代码块里将number++(数字打印并增加)操作完以后,为了保证下个锁为线程2所有,需要将线程1阻塞(线程1你等等wait())。(输出1,number为2)* 2.当线程2获取锁以后,此时线程1已经不能进入同步代码块中了,所以,为了让线程1继续抢占下一把锁,需要让线程1的阻塞状态取消(通知线程1不用等了notify()及notifyAll()),即应该在进入同步代码块时取消线程1的阻塞。** */class Number implements Runnable{private int number = 1;//设置共享数据(线程之间对于共享数据的共享即为通信)//对共享数据进行操作的代码块,需要线程安全@Overridepublic synchronized void run() {while(true){//使得线程交替等待以及通知交替解等待notify();//省略了this.notify()关键字if(number<100){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+":"+number);number++;try {wait();} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}
}public class CommunicationTest {public static void main(String[] args){//创建runnable对象Number number = new Number();//创建线程,并实现runnable接口Thread t1 = new Thread(number);Thread t2 = new Thread(number);//给线程设置名字t1.setName("线程1");t2.setName("线程2");//开启线程t1.start();t2.start();}}

sleep和wait的异同:

相同点:一旦执行方法以后,都会使得当前的进程进入阻塞状态
不同点:
1.两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
2.调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放

经典例题:生产者/消费者问题:

生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

这里可能出现两个问题:
生产者比消费者快的时候,消费者会漏掉一些数据没有收到。
消费者比生产者快时,消费者会去相同的数据。

package com.example.paoduantui.Thread;/*** 线程通信的应用:生产者/消费者问题** 1.是否是多线程问题?是的,有生产者线程和消费者线程(多线程的创建,四种方式)* 2.多线程问题是否存在共享数据? 存在共享数据----产品(同步方法,同步代码块,lock锁)* 3.多线程是否存在线程安全问题? 存在----都对共享数据产品进行了操作。(三种方法)* 4.是否存在线程间的通信,是,如果生产多了到20时,需要通知停止生产(wait)。(线程之间的通信问题,需要wait,notify等)** */class Clerk{private int productCount = 0;//生产产品public synchronized void produceProduct() {if(productCount<20) {productCount++;System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");notify();}else{//当有20个时,等待waittry {wait();} catch (InterruptedException e) {e.printStackTrace();}}}//消费产品public synchronized void consumeProduct() {if (productCount>0){System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");productCount--;notify();}else{//当0个时等待try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}}class Producer extends Thread{//生产者线程private Clerk clerk;public Producer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {try {sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+";开始生产产品......");while(true){clerk.produceProduct();}}}class Consumer implements Runnable{//消费者线程private Clerk clerk;public Consumer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+":开始消费产品");while(true){try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}clerk.consumeProduct();}}}public class ProductTest {public static void main(String[] args){Clerk clerk = new Clerk();Producer p1 = new Producer(clerk);p1.setName("生产者1");Consumer c1 = new Consumer(clerk);Thread t1 = new Thread(c1);t1.setName("消费者1");p1.start();t1.start();}}

http://chatgpt.dhexx.cn/article/noRM7fJG.shtml

相关文章

java多线程(超详细)

1 - 线程 1.1 - 进程 进程就是正在运行中的程序&#xff08;进程是驻留在内存中的&#xff09; 是系统执行资源分配和调度的独立单位 每一进程都有属于自己的存储空间和系统资源 注意&#xff1a;进程A和进程B的内存独立不共享。 1.2 - 线程 线程就是进程中的单个顺序控制…

JAVA线程

一、线程相关概念 &#xff08;一&#xff09;程序、进程和线程的区别 程序 程序是含有指令和数据的文件&#xff0c;被存储在磁盘或其他的数据存储设备中&#xff0c;也就是说程序是静态的代码。 进程 进程是程序的一次执行过程&#xff0c;是系统运行的基本单位&#xf…

Java 线程 基础知识总结

线程基础 很不严谨的说&#xff0c;线程是什么&#xff1f;线程就是为了让很多个东西并发执行&#xff0c;大大的提高程序执行的效率啊 三个非常重要的概念&#xff1a; 程序&#xff1a;一组写好了的静态代码块&#xff08;就我们写的那些代码玩意&#xff09;进程&#xf…

Java多线程(超详解)

目录 1. 线程简介 1.1 程序 1.2 进程 1.3 线程 1.4 多线程 1.5 普通方法调用和多线程 2. 线程创建 2.1 继承Thread类 2.2 实现Runnable接口 2.3 实现Callable接口&#xff08;了解&#xff09; 2.4 网图下载 2.4.1 通过继承Thread类实现网图下载 2.4.2 通…

java 线程详解

一、线程的基本概念 一个程序最少需要一个进程&#xff0c;而一个进程最少需要一个线程。关系是线程–>进程–>程序的大致组成结构。所以线程是程序执行流的最小单位&#xff0c;而进程是系统进行资源分配和调度的一个独立单位。 一个线程就是在进程中的一个单一的顺序…

JAVA多线程详解(超详细)

目录 一、线程简介1、进程、线程2、并发、并行、串行3、进程的三态 二、线程实现1、继承Thread类2、实现Runnable接口3、实现Callable接口&#xff08;不常用&#xff09; 三、线程常用方法1、线程的状态2、线程常用方法 四、多线程1、守护&#xff08;Deamon&#xff09;线程2…

Java多线程(超详细!)

1、什么是进程&#xff1f;什么是线程&#xff1f; 进程是:一个应用程序&#xff08;1个进程是一个软件&#xff09;。 线程是&#xff1a;一个进程中的执行场景/执行单元。 注意&#xff1a;一个进程可以启动多个线程。 eg. 对于java程序来说&#xff0c;当在DOS命令窗口中…

count/count if函数的基本用法

count函数&#xff0c;用来计算单元格的数的个数&#xff0c;只是用来计数&#xff0c;并且只有只记录数子的个数&#xff0c;文本的个数是不被记录的。 但是很少会用到单纯的count函数&#xff0c;往往在工作中计数是带有条件的。就会用到countif函数 COUNTIF函数需要注意的点…

EXCEL COUNTIF()的一些奇特的用法

文章目录 前言一、统计第几次重复二、统计不重复的数量三、通配符模糊统计四、防止重复录入五、忽略错误值或空值统计六、重复值填充背景色总结 前言 日常工作中需要度娘很多知识点或者方法&#xff0c;但每次用了就忘&#xff0c;下次遇到就需要继续度娘&#xff0c;故在此记…

Excel多条件计数——COUNTIFS【获奖情况统计】

问题描述 当前&#xff0c;我们需要对表格中的获奖情况进行统计 奖励级别&#xff1a;院级、校级、国家级、国际级奖励内容&#xff1a;特等奖、一等奖、二等奖、三等奖、优胜奖 功能要求 对所有奖励级别进行统计根据级别&#xff0c;计算内容数量 当有人的选项内容如下时 …

如何在Microsoft Excel中使用COUNTIF函数

COUNTIF 是一个 Excel 函数,用于对满足单个条件的区域中的单元格进行计数。COUNTIF可用于计算包含日期、数字和文本的单元格。COUNTIF 中使用的条件支持逻辑运算符(>、<、<>、=)和通配符(*、?)进行部分匹配。 例如,我们想计算包含 Google或 Facebook 的单元…

COUNT函数的使用

一、问题描述 今天在随手练习sql的时候&#xff0c;发现count查出来的数量和实际的数量不对&#xff0c;下面是我查询的sql 我想看看suitName字段一共有多少种数据 SELECTCOUNT(DISTINCT suitName) AS suitNameNum FROMrace_goods 得到的结果是suitName条数是22条&#xff0c…

countif是什么意思,如何运用?

countif是什么意思&#xff1f;countif函数是excel中对指定区域中符合指定条件的单元格计数的一个函数&#xff0c;简单来说就是算出某个参数的数量。那么&#xff0c;countif函数具体是如何运用的呢&#xff1f;小编分为两种情景为大家总结了操作步骤。 情景一&#xff1a;计算…

excel通过sumproduct和countifs不重复计数(数据中包含空白单元)

1. 常规情况&#xff0c;数据中不包含空白单元格&#xff0c;如下图&#xff1a; SUMPRODUCT((A2:A24E2)*(B2:B24F2)*(1/COUNTIFS(A2:A24,A2:A24,B2:B24,B2:B24,C2:C24,C2:C24))) 2. 数据中包含空白单元格&#xff0c;如下图&#xff1a; 数据中包含空白单元的情况&#xff0c…

countif怎么读(countif怎么读)

SUM,AVERAGE,MAX,MIN,PRODUCT,CUONT,RANK,IF,SUMIF,COUNTIF怎么读呀&#xff01;谢谢 SUM(桑母) 求和函数&#xff1b; AVERAGE(哎五瑞之) 求平均数函数&#xff1b; MAX(麦克斯) 最大值函数&#xff1b; MIN(民) 最小值函数&#xff1b; PRODUCT(普若达克特) 求积函数&#xf…

[Excel常用函数] countif countifs函数

countif函数 1.countif函数的含义 在指定区域中按指定条件对单元格进行计数&#xff08;单条件计数&#xff09; 2.countif函数的语法格式 countif&#xff08;range&#xff0c;criteria&#xff09; 参数range 表示条件区域——对单元格进行计数的区域。 参数criteria …

Microsoft Excel 教程:如何在 Excel 中使用 COUNTIF 函数?

欢迎观看 Microsoft Excel 教程&#xff0c;小编带大家学习 Microsoft Excel 的使用技巧&#xff0c;了解如何在 Excel 中使用 COUNTIF 函数。 COUNTIF 是一个统计函数&#xff0c;用于统计满足某个条件的单元格的数量&#xff1b;例如&#xff0c;某个姓名在单元格区域中出现…

[Excel函数] COUNT函数 | COUNTIF函数 | COUNTIFS函数

1.COUNT函数 语法: COUNT(value1,[value2],...) COUNT函数用于计算区域中包含数字的单元格的个数 (不统计文本性数字) COUNT函数很少单独使用&#xff0c;一般和其他函数嵌套使用 案例: 统计数字的个数 注意: A13该单元格数字为文本格式&#xff0c;COUNT函数不统计文本性数字 …

Excel如何使用COUNTIF函数

COUNTIF函数是我们在工作中经常使用的函数&#xff0c;今天就给大家分享一下如何使用countif函数。 1、COUNTIF函数主要用于对区域中满足单个指定条件的单元格进行计数。它的语法结构是COUNTIF(统计区域&#xff0c;统计条件) 注意要点 1.COUNTIF不区分大小写。 2.在条件中可…

sum与countif、countifs函数套用

1、sum与countif套用&#xff0c;实现多条件计数求和 公式&#xff1a;sum(countif(条件区域,{"条件1","条件2","条件3"})) 示例&#xff1a; 2、sum与countifs套用&#xff0c;实现多区域多条件计数求和 公式&#xff1a;sum(countifs(条件区…