【Java进阶篇】第七章 多线程

news2024/9/23 21:23:55

文章目录

  • 一、多线程概述
    • 1、进程与线程
    • 2、进程与线程的关系
  • 二、多线程并发的实现
    • 1、线程的实现方式一
    • 2、线程的实现方式二
  • 三、线程的生命周期
    • 1、线程的五个生命周期
    • 2、常用方法
    • 3、线程的sleep
    • 4、终止线程的睡眠状态
    • 5、强行终止线程的执行
    • 6、合理终止一个线程的执行
  • 四、线程的调度
    • 1、线程调度的模型
    • 2、线程调度的方法---优先级
    • 3、线程调度---让位
    • 4、线程调度--线程合并
  • 五、线程安全
    • 1、同步与异步
    • 2、同步机制synchronized
    • 3、有线程安全的变量
    • 4、synchronized出现在实例方法上
    • 5、synchronized的三种写法
  • 六、死锁
    • 1、原理
    • 2、代码实现
  • 七、线程守护
    • 1、线程守护的概述
    • 2、实现守护线程
    • 3、定时器
    • 4、实现定时器
    • 5、实现线程的第三种方式
  • 八、wait和notify
    • 1、概述
    • 2、生产者和消费者模式

一、多线程概述

1、进程与线程

  • 进程是一个应用程序(一个进程是一个软件)
  • 线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程

举个例子:

DOS窗口运行java HelloWorld,先启动JVM,JVM是一个进程,JVM启动一个主线程调用main方法,同时再启动一个垃圾回收线程来负责看护、回收垃圾。(也就是说Java程序至少两线程并发,main方法对应的主线程+GC)

2、进程与线程的关系

把进程看作是现实生活中的公司,如京东。线程则可看作是其下的某一个职能部门,负责完成某任务,如开发部门。

  • 进程A和进程B的内存独立不共享
  • Java中,线程A和线程B,堆内存和方法区内存共享,但栈内存独立,一个线程一个栈

如启动了10个线程,就会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。

🍁Java中的多线程机制,目的就是为了提高程序的处理效率, 如火车站看成是一个进程,则每个售票小窗口就是一个个线程,甲在窗口1买票,乙在窗口2买票,谁也不用等谁 一个个售票窗口就像一个个栈,有自己独立的空间。售票大厅这个共用空间就像堆和方法区。

多线程
引入多线程以后,main方法的结束,不再意味着程序的结束。main方法结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈

二、多线程并发的实现

真正的多线程并发是:t1线程执行t1,t2线程执行t2,t1不影响t2,t2不影响t1。4核的CPU,在同一时间点,可以真正的有4个进程并发执行,单核的CPU,在某一个时间点上实际只能处理一件事情,但由于CPU的处理速度极快,多个线程之间频繁切换,从而造成了多个事情在同时处理的视觉假象。

public class Thread1 {
    public static void main(String[] args) {
        m1();    
    }
    public static void m1(){
        m2();    
    }
    public static void m2(){
        m3();
    }
    public static void m3(){
        System.out.println("m3 excute");
    }
    
}

分析以上:只有一个主线程,主栈,没有分支线程被启动
示意图

1、线程的实现方式一

编写一个类,直接继承java.lang.Thread,重写run方法

class MyThread extends Thread{
	//这段代码运行在分支线程中
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println("分支线程" + i);
        }
    }
}
public class ThreadTest{
    public static void main(String[] args) {
    	//创建分支线程对象
        MyThread myThread = new MyThread();
        //启动分支线程
        myThread.start();
        //这里仍然运行在主线程当中
        for(int i=0;i<100;i++){
            System.out.println("主线程" + i);
        }
    }
}

start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这个任务完成后,这段代码就瞬间执行结束了

启动成功后的线程会自动调用我重写的run方法,并且run方法在分支栈的栈底部(main方法在主栈的底部,故run和main是平级的)

myThread.run();

如果直接调用我重写的run方法,则不会启动线程,不会分配新的分支栈,此时,就是单线程了。
内存图
运行结果:
run

2、线程的实现方式二

编写一个类,实现java.lang.Runable接口,重写run方法

public class ThreadTest {
    public static void main(String[] args) {
    	//创建一个可运行的对象
        MyRunnable r = new MyRunnable();
        //给Thread类的构造方法传入Runnable类的对象
        //将可运行的对象封装成一个线程对象
        Thread t = new Thread(r);
        t.start();
        for(int i=0;i<100;i++){
            System.out.println("主线程"+ i);
        }
    }
}

//这不是线程,仅仅是一个可运行的类
class MyRunnable implements Runnable{
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println("分支线程"+i);
        }
    }
}

总结线程的实现:

🍁编写一个类继承Thread类并重写run方法

public class MyThread extends Thread{
	public void run(){
	}
}

MyThread t = new MyThread();
t.start();

🍁编写一个类,实现Runnable接口并重写run方法

public class MyRunnbale implements Runnable{
	public void run(){
	}
}
 
Thread t = new Thread(new MyRunnable());
t.start();

由于Java的单继承,第一种方式中,不能再继承别的类了,而第二种可以,面向接口编程更优。

//方式二的匿名内部类写法:

Thread t2 = new Thread(new MyRunnable(){
				       public void run(){
				           for(int i=0;i<100;i++){
				               System.out.println(i);
				           }
				       }
				   });

tip

run()方法中要是有异常,也只能捕捉,不能上抛,因为run方法在父类中没有抛出任何异常,做为子类,重写时不能比父类抛出更多的异常。

三、线程的生命周期

1、线程的五个生命周期

生命周期图

  • 🍁 新建状态:
    刚new出来的线程对象

  • 🍁就绪状态:
    又叫做可运行状态,表示当前线程具有抢夺CPU时间片的能力(CPU时间片就是执行权)当一个线程抢夺到CPU时间片后,开始执行run方法,run方法的执行标志着线程进入运行状态。

  • 🍁运行状态:
    run方法开始执行,线程进入运行状态,当之前占有的CPU时间片用完之后,重新回到就绪状态继续抢夺CPU时间片,待抢到后,重新进入run方法上次执行的地方继续执行

  • 🍁死亡状态:
    run方法执行结束,线程到达死亡状态

  • 🍁阻塞状态:
    当一个线程遇到阻塞事件,如接收用户键盘输入,sleep方法,则进入阻塞状态,此时线程会放弃之前抢占到的CPU时间片

2、常用方法

获取线程的名字getName()

MyThread myThread = new MyThread();
String tName = myThread.getName();
//Thread-0
System.out.println(tName);
//更改
myThread.setName("code-9527 's Thread");
System.out.println(myThread.getName());

获取当前线程对象currentThread()

//静态方法获取当前线程对象,返回一个Thread类型对象
Thread currentThread = Thread.currentThread();
//返回线程名
System.out.println(currentThread.getName());

3、线程的sleep

//毫秒
static void sleep(Long millis);

作用是让当前线程进入休眠,进入阻塞状态,放弃占有CPU时间片,让给其他线程去使用,出现的地方,对应的线程休眠

public static void main(String[] args) {
      try{
          Thread.sleep(1000*5); //让当前线程(main)休眠五秒
      }catch(InterruptedException e){
          e.printStackTrace();
      }
      //五秒后被输出
      System.out.println("sleep结束");
}

实现间隔特定的时间去执行一段特定的代码

sleep是静态方法,若上面的Thread.sleep改成:

Thread t = new MyThread();
...
t.sleep(1000*5);

执行的时候,t.sleep(1000*5);还是会被当作Thread.sleep(1000*5);,被休眠的也还是main线程,而不是t线程。

4、终止线程的睡眠状态

t.interrupt();

干扰,即中断t线程的睡眠,执行后sleep()出现异常,即catch(InterruptedException e),这种中断睡眠状态的方式,依靠的时Java的异常处理机制。

class MyThread extends Thread{
    public void run(){
        try{
            Thread.sleep(1000*5);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        for(int i=0;i<100;i++){
            System.out.println("分支线程" + i);
        }
    }
}
class ThreadTest1{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        //线程myThread计划sleep5秒
        //扔出interrupt后就提前醒来了
        myThread.interrupt();
     }
}

示意图

5、强行终止线程的执行

线程对象的引用.stop()

stop方法已过时,容易丢数据。这种方式是直接将线程杀死了,线程没有保存的数据会丢失

MyThread myThread = new MyThread();
myThread.start();
//终止
myThread.stop();

强行终止

6、合理终止一个线程的执行

  • 编写的类中加入run属性:boolean run = true;
  • 重写run方法的时候,if(run)…
  • else中写终止线程之前要保存的数据和操作+return;
  • 以后则只需改线程对象的run属性即可终止线程
class MyRun implements Runnable{
    boolean run = true;
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (this.run) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                System.out.println(this.run);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("这是一些终止线程前要做的事");
                System.out.println("保存数据中..终止线程成功!");
                return;
            }
        }
    }
}
public class ThreadTest2 {
    public static void main(String[] args) {
        MyRun r = new MyRun();
        Thread t = new Thread(r);
        t.start();
        //sleep主线程三秒
        try{
            Thread.sleep(3000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        //终止,改run属性为false
        r.run = false;

    }
}

运行效果:
run

四、线程的调度

1、线程调度的模型

  • 🍁抢占式调度模型:
    哪个线程的优先级比较高,抢到CPU时间片的概率就高一些/多一些。Java中采用的就是抢占式调度模型。

  • 🍁均分式调度模型:
    平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样

2、线程调度的方法—优先级

  • 设置线程的优先级
void setPriority(int newPriority)
  • 获取线程的优先级
int getPriority()
//最低优先级为1
static int MIN_PRIORITY
//默认优先级为5
static int NORM_PRIORITY
//最高优先级为10
static int MAX_PRIORITY

System.out.println(Thread.MAX_PRIORITY);

举例:

Thread currentThread = Thread.currentThread();

System.out.println(currentThread.getName()+"的优先级是:"+ currentThread.getPriority());

currentThread.setPriority(9);

3、线程调度—让位

//静态方法

static void yield()

暂停当前正在执行的线程对象,去执行其他线程,yield方法的执行会让当前线程从运行状态进入就绪状态 ,注意不是阻塞状态。

class MyRunnable2{
    public void run(){
        for(int i=0;i<101;i++){
        	//每循环10次,让当前线程暂停让位一下
            if(i%10 == 0){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + i);
        }
        
    }
}

4、线程调度–线程合并

实例方法void join(),注意线程合并不是栈的合并

MyThread t = new MyThread();
//让当前线程阻塞,t线程执行,直到t线程执行结束,当前线程才执行
t.join();

五、线程安全

线程安全问题的产生条件:

  • 多线程并发
  • 有贡献数据
  • 共享数据有修改行为

线程安全
线程安全问题的解决–线程同步机制

线程同步即线程排队执行,不能并发了(可能会牺牲一部分效率,但数据安全是一切的前提)

1、同步与异步

  • 🍁异步模型:
    异步就是并发,线程t1和线程t2各自执行各自的,t1不管t2,t2不管t1,谁也不用等谁,即多线程并发,效率较高。

  • 🍁同步模型:
    线程t1和t2,t1执行的时候,必须等待t2执行结束,效率较低。

❀❀❀账户安全问题的代码模拟:


/**
 * 账户类
 */
public class Account {
    private String actno;
    private double balance;

    public Account(){

    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 取款方法
     * @param money
     */
    public void withdraw(double money){
        double before = this.getBalance();
        double after = before - money;
        //别立即更新余额,使用休眠模拟网络延迟
        try{
            Thread.sleep(1000*5);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        this.setBalance(after);
    }
}

public class AccountThread extends Thread{
    //该类的对象有Account属性
    //某人“有一个账户”
    private Account account;
    //通过构造方法传递账户对象
    public AccountThread(Account account){
        this.account = account;
    }
    public void run(){
        double money = 5000;
        account.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "对账户:" + account.getActno() +
                "取款:" + money + ",余额:"+ account.getBalance());
    }
}

class Test{
    public static void main(String[] args) {
        Account account = new Account("act-001",10000);
        //两个线程共用一个账户对象
        AccountThread t1 = new AccountThread(account);
        AccountThread t2 = new AccountThread(account);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}

运行结果:

run

2、同步机制synchronized

🍁语法:

synchronized(){

线程同步代码块

}

小括号中传的是多个线程共享的对象,若有t1、t2、t3、t4、t5线程,t1、t2、t3需要排队,t4、t5不用,则()中是一个t1、t2、t3共享的对象,而这个对象t4、t5不共享

由此,上面例题中的取款方法变为:

public void withdraw(double money){
     synchronized(this) {
         double before = this.getBalance();
         double after = before - money;
         try {
             Thread.sleep(1000 * 5);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         this.setBalance(after);
     }
 }

简单的说就是synchronized内部放的是要排队执行的代码块

🍁对象锁:

在Java中,任何一个对象都有“一把锁”,这把锁其本质是一个标记,一个对象一把锁。

当运行状态的线程遇到synchronized关键字:

  • 在锁池lockpool中找共享对象的对象锁。线程进入锁池找共享对象的对象锁之前,会释放之前占有的CPU时间片
  • 若找到了,则进入就绪状态继续抢夺CPU时间片,若没找到,则在锁池中等待

🍺例:
当t1和t2线程并发:
t1先遇到synchronized,自动找所共享对象的对象锁,找到之后,占有这把锁,然后执行同步代码块中的代码, 直到同步代码块执行结束,这个锁才释放

—>>>

t1占有对象锁后,t2线程若也遇到了synchronized,在找对象锁时,发现被t1占有,则t2在同步代码块外等待t1结束并释放对象锁后,再占有对象锁、执行同步代码块

3、有线程安全的变量

存在于堆区中的实例变量、存在于方法区中的静态变量,因为堆和方法区均只有一个,且是多线程共享的,有可能存在安全问题。

局部变量存在于栈区中,永远不会有线程安全问题,因为一个线程一个栈。

4、synchronized出现在实例方法上

旧版
改为:

public synchronized void withdraw(double money){
        double before = this.getBalance();
        double after = before - money;
        try{
            Thread.sleep(1000*5);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        this.setBalance(after);
    }

这样写的缺点:

  • synchronized出现在实例方法上,表示整个方法都需要同步(实际只有其中一部分代码需要同步),这样就扩大了同步的范围,导致程序的执行效率变低
  • synchronized出现在实例方法上,锁的就一定是this,就不能是其他对象了

5、synchronized的三种写法

🍁

synchronized(线程共享对象){
		同步代码块;
}

🍁
在实例方法中使用synchronized,表示共享的对象一定是this,并且同步的代码块是整个方法体

🍁
在静态方法中使用synchronized,表示找类锁,类锁永远只有1把(对象锁是100个对象就有100个对象锁)

六、死锁

1、原理

死锁示意图
t1线程执行某同步代码块,用到了对象1和2,即t1线程需要先锁对象1,再锁对象2,全锁以后,算同步代码块执行结束,然后一下释放两个对象锁

t2线程执行另一个同步代码块,需要先锁对象2,再锁对象1才算这个同步代码块执行结束,然后释放两个对象锁。

如此:
t1锁到对象2的时候,发现已被锁,则等待,而另一边:t2锁到对象1的时候,发现对象1已被锁,两个线程同时陷入无休止的等待…尬住了

2、代码实现

class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1, Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        //同步代码块开始
        synchronized(o1){
            try{
                //别着急锁o2,为了保证死锁必现,这里等两秒
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            //synchronized的嵌套
            //从而实现:对象o1和o2都锁了才算同步代码块结束
            synchronized(o2){

            }
        }
        //同步代码块结束
    }
}

class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o2){
            try{
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            synchronized(o1){

            }
        }
    }
}

public class DeadLock{
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        //启动两个线程,共用对象o1和o2
        MyThread1 t1 = new MyThread1(o1,o2);
        MyThread2 t2 = new MyThread2(o1,o2);
        t1.start();
        t2.start();
    }
}

运行结果:

run
死锁发生后,程序不出异常,也不报错,会一直僵持着,不易发现并调试。

tips

关于synchronized死锁和线程安全的优化:

synchronized会让程序执行效率变低,系统吞吐量降低,用户体验变差。解决线程安全,可考虑:

  • 使用局部变量代替实例变量和静态变量
  • 若必须使用实例变量,考虑多创建几个对象,别对象共享了也就没有安全问题了
  • 若以上两条都做不到,则用synchronized

七、线程守护

1、线程守护的概述

Java中,线程分为两大类:

  • 用户线程,如主线程main线程
  • 守护线程,如经典的垃圾回收线程

守护线程的特点是:

一般守护线程是一个死循环,所有用户线程结束的时候,守护线程自动结束

2、实现守护线程

通过实例方法setDaemon:

public class DaemonTest {
    public static void main(String[] args) {
        Thread t = new BackupThread();
        t.setName("备份守护线程");
        //传入true,则普通线程变守护线程
        t.setDaemon(true);
        t.start();
    }

}
class BackupThread extends Thread{
    public void run(){
        //要进行的守护动作写在run方法中即可
        int i = 0;
        while(true){
            //即使是死循环,但做为守护线程,当所有线程都结束的时候,也会自动结束
        }
    }
}

3、定时器

🍁作用:
间隔特定的时间,执行特定的程序

🍁应用场景:
如每周进行银行账户的总账操作,每天进行数据库的备份操作

🍁实现方式:

  • 用sleep方法,设置线程睡眠,睡到某时刻醒来执行代码完成任务
  • 用java.util.Timer
  • 用Spring框架中的SpringTask框架(底层还是java.util.Timer)

4、实现定时器

用到的java.util.Timer类中的方法

  • Time类的无参构造方法,创建定时器对象
Timer timer = new Timer();
  • Timer类的有参构造
//传入true,表示以守护线程的方式
Timer timer = new Timer(true);
  • schedule方法
/**安排任务从指定时间开始进行重复固定的延迟执行
* TimerTask是一个抽象类
* Date firstTime即第一次执行的时间
* Long period 即间隔多少毫秒
*/
void schedule(TimerTask task, Date firstTime , Long period)

//安排任务在指定时间执行任务task
void schedule(TimerTask task, Date time)

❀代码实现–编写一个定时器任务类记录日志

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class LogTimerTask extends TimerTask {
    /**
     * 重写父类TimerTask中的抽象方法run
     * TimerTask类实现了Runnable接口,所以有run方法
     */
    public void run(){
        //这里写要执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + "日志记录成功");
    }
}

class TimerTest{
    public static void main(String[] args) {
        Timer timer = new Timer();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date firstTime = sdf.parse("2022-12-06 08:10:06");
            timer.schedule(new LogTimerTask(),firstTime,1000*5);
        } catch (ParseException e) {
            e.printStackTrace();
        }

    }
}

运行效果:
run

5、实现线程的第三种方式

JDK8新特性,可实现Callable接口。这种方式实现的线程可以获得线程的返回值。前两种实现方式不能获取返回值,因为run方法的返回值类型是void。

优点:
可获取到线程的执行结果

缺点:
效率较低,在获取t线程执行的结果时,当前线程需要等待,等拿到返回值以后再往下执行其余code

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadTest0 {
    public static void main(String[] args) {
        //创建一个”未来任务类“的对象,传参为Callable接口实现类的对象
        FutureTask task = new FutureTask(new Callable(){  //匿名内部类
            //call方法相当于run方法,不过其有返回值
            public Object call() throws Exception{
                System.out.println("call method begin!");
                Thread.sleep(1000*6);
                System.out.println("call method end");
                int a = 100;
                int b = 200;
                //线程执行一个任务,执行完可能有返回结果
                //这里自动装箱
                return a+b;
            }

        });
        Thread t = new Thread(task);
        t.start();
        try {
            //当前为主线程,获取t线程的执行结果
            Object obj = task.get();
            System.out.println("线程执行结果:" + obj);
            //此处get方法要拿另一个线程的执行结果,可能要很久
            //但这导致了下面main线程要等待get执行结束。
            //这就导致了”当前线程的阻塞“
            System.out.println("这里要等get拿到线程的返回值才能执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

运行结果:
run

八、wait和notify

1、概述

wait 和notify方法是Java中任何一个Java对象都有的方法,因为这两个方法是Object类自带的。不是给线程对象用的,所以别t.wait();

Object o = new Object();
o.wait();

以上让正在o对象上活动的线程进入等待状态,且为无限等待,直到被唤醒为止

示意图

T线程在o对象上活动,o.wait()后,T线程进入无限期等待,并且释放o对象的对象锁

o.notify()让正在o对象上等待的线程被唤醒

notifyAll()方法是唤醒o对象上处于等待的所有线程


2、生产者和消费者模式

生产者-消费者

🍁代码实现:

import java.util.ArrayList;
import java.util.List;

/**
 * 生产线程
 */
class Producer implements Runnable{
    /**
     * 仓库
     */
    private List list;
    public Producer(List list){
        this.list = list;
    }
    public void run(){
        while(true){
            synchronized(list){
                if(list.size()>0){ //仓库有东西,则生产线程wait
                    try {
                        list.wait(); //进入等待状态,释放之前占有的list的对象锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //能到这说明仓库空了,开始生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "-->" + obj);
                list.notifyAll(); //生产完了以后唤醒消费线程来消费
            }
        }
    }
}

/**
 * 消费线程
 */
class Consumer implements Runnable{
    private List list;
    public Consumer(List list){
        this.list = list;
    }
    public void run(){
        while(true){
            synchronized(list){
                if(list.size() == 0){ //仓库已空,暂停消费
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //消费
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "-->" + obj);
                //唤醒生产线程来生产
                list.notifyAll();
            }
        }
    }
}

/**
 * 测试
 */
public class PC_Moudle {
    public static void main(String[] args) {
        //创建一个仓库,生产和消费线程共享
        List list = new ArrayList();
        Thread t1 = new Thread(new Producer(list));
        Thread t2 = new Thread(new Consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        t1.start();
        t2.start(); 
    }
}

运行效果:
run

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/67309.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

apache html调用bash脚本案例

首先安装apache服务,采用yum的方式即可&#xff0c;因为用到的都是apache的基本功能&#xff0c;不需要编译安装 yum -y install httpd 然后准备html页面&#xff0c;这个页面其实就是调用bash脚本的页面&#xff0c;提供页面操作然后调用服务器上的脚步文件 网页布局建议用…

【嵌入式UI框架:LVGL】使用恩智浦GUI设计工具,像Qt一样开发MCU界面

LVGL是一个免费的开源嵌入式图形库&#xff0c;它提供创建嵌入式GUI所需的功能&#xff0c;具有易于使用的图形元素、精美的视觉效果和低内存占用。完整的图形框架包括供您在创建GUI时所用的各种小部件&#xff0c;并支持更高级的功能&#xff0c;例如动画和抗锯齿。 一、工具&…

springcloud入门

微服务架构介绍 微服务架构&#xff0c; 简单的说就是将单体应用进一步拆分&#xff0c;拆分成更小的服务&#xff0c;每个服务都是一个可以独 立运行的项目。 微服务架构的常见问题 一旦采用微服务系统架构&#xff0c;就势必会遇到这样几个问题&#xff1a; 这么多小服务…

MYSQL——毫秒值和日期类型数据的转换,DATE_SUB的用法

MYSQL——毫秒值和日期类型数据的转换&#xff0c;DATE_SUB的用法一、毫秒值转换成日期数据类型二、日期数据类型转换成毫秒值三、DATE_SUB的用法一、毫秒值转换成日期数据类型 语法&#xff1a;FROM_UNIXTIME(毫秒值字段,‘%Y-%m-%d %h:%i:%s’) 举例&#xff1a; select id…

spring-boot-starter-aop及其使用场景说明

如今&#xff0c;AOP&#xff08;Aspect Oriented Programming&#xff09;已经不是什么崭新的概念了&#xff0c;在经历了代码生成、动态代理、字节码增强甚至静态编译等不同时代的洗礼之后&#xff0c;Java 平台上的 AOP 方案基本上已经以 SpringAOP 结合 AspectJ 的方式稳固…

基于独立分量分析进行模态分解(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

[附源码]计算机毕业设计基于Springboot在线教育系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Mysql详细安装步骤

目录 1、解压服务端Mysql安装包 2.复制改变my.ini文件 3、安装MySQL服务 4、启动mysql服务 6.记录初始密码&#xff0c;利用初始密码登录 &7.改变MySQL链接密码 1、解压服务端Mysql安装包 解压之后的目录就是以上这样的。 2.复制改变my.ini文件 把my.ini文件添加…

【计算机图形学入门】笔记3:变换Transformation(二维与三维)

第三章.Transformation变换&#xff08;二维与三维&#xff09;1.为什么要学习变换&#xff1f;2.变换的几种形式1.缩放2.相对于y轴翻转3.Shear Matrix 切片4.旋转3.齐次坐标1.平移变换2.齐次坐标的引入3.使用齐次坐标表示上述变换4.组合变换5.分解变换6.三维空间中的变换1.三维…

QT 系统学习 day03 了解各种控件,文件操作,消息框,windows应用界面的 生成菜单栏, 状态栏,中心部件,工具栏,

1. 控件&#xff0c; 旋钮 &#xff0c;进度条&#xff0c; 我也不知道叫啥&#xff0c; 相关的代码&#xff1b; 首先是函数 1.旋钮函数 &#xff08;槽函数都有说明&#xff09;&#xff08;Dial&#xff09; ui->dial->setRange(0,100);//设置旋钮的范围ui->di…

Three.js一学就会系列:01 第一个3D网站

文章目录前言一、Three.js是什么&#xff1f;官网官网示例效果尝鲜二、使用步骤1.引入three.js库2.使用方法创建一个场景创建一个透视摄像机将渲染器添加到页面上创建一个立方体渲染场景立方体动起来效果总结前言 最近开始入坑前端3D建站&#xff0c;跟大家一起慢慢深入three.…

Android 面试拒收Offer篇,这样做对吗?

作者&#xff1a;如梦 如梦朦胧 朋友们的劝说下&#xff0c;有了换工作的躁动,然后投了某度的Android岗位,本以为像我这种非211、985没工作经验的渣渣只能被直接pass,结果却意外的收到了电话,真是受宠若惊.经过电面,技术三面,然后就是等通知到最后拿到了OFFER,如梦一般,真是挺…

性能优化:Redis使用优化(1)

参考资料&#xff1a; 《Redis为什么变慢了&#xff1f;一文讲透如何排查Redis性能问题 | 万字长文》 相关文章&#xff1a; 《Redis&#xff1a;内存淘汰机制》 《Redis&#xff1a;持久化RDB与AOF》 《Redis&#xff1a;主从复制》 写在开头&#xff1a;本文为学习后的总…

Spring中事务失效的场景

文章目录1 抛出检查异常导致事务不能正确回滚1.1 异常演示1.2 解决办法2 业务方法内自己 try-catch 异常导致事务不能正常回滚1.1 异常演示1.2 解决办法3 aop切面顺序导致事务不能正确回滚3.1 异常演示3.2 解决办法4 非 public 方法导致事务的失效4.1 异常演示4.2 解决办法5 父…

深度学习-全卷积神经网络(FCN)

1. 简介 全卷积神经网络&#xff08;Fully Convolutional Networks&#xff0c;FCN&#xff09;是Jonathan Long等人于2015年在Fully Convolutional Networks for Semantic Segmentation一文中提出的用于图像语义分割的一种框架&#xff0c;是深度学习用于语义分割领域的开山之…

【目标检测】【DDPM】DiffusionDet:用于检测的概率扩散模型

文章目录摘要一、Introduction二、相关工作三、方法1.准备工作2.架构3、训练4.预测过程四. 实验1.训练策略2.main property3.消融实验五、代码分析1.测试 demo.py2.训练 train-net.py总结摘要 我们提出了扩散det&#xff0c;一个新的框架&#xff0c;将目标检测作为一个从噪声…

OpenGL基础程序结构

用OpenGL编写的程序结构类似于用其他语言编写的程序。实际上&#xff0c;OpenGL是一个丰富的三维图形函数库&#xff0c;编写OpenGL程序并非难事&#xff0c;只需在基本C语言中调用这些函数&#xff0c;用法同Turbo C、Microsoft C等类似&#xff0c;但也有许多不同之处。   …

[附源码]Python计算机毕业设计Django校园招聘系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

功能测试求职难,现在不懂自动化测试连外包都进不去了?

功能测试求职难 最近因为公司政策原因&#xff0c;部分外包被裁员&#xff0c;其中不乏能力还不错&#xff0c;工作也挺踏实的&#xff0c;比较可惜&#xff0c;为了帮助他们尽快找到下家&#xff0c;我这边也开始帮他们关注招聘情况&#xff0c;发现一个挺让我意外的事情。在…

Java中CAS详解

一、什么是CAS 什么是CAS机制 CAS机制是一种数据更新的方式。在具体讲什么是CAS机制之前&#xff0c;我们先来聊下在多线程环境下&#xff0c;对共享变量进行数据更新的两种模式&#xff1a;悲观锁模式和乐观锁模式。 悲观锁更新的方式认为&#xff1a;在更新数据的时候大概率…