Java基础-多线程基础

文章目录

    • 1.线程相关概念
          • 1.程序
          • 2.进程
          • 3.线程
          • 4.单线程
          • 5.多线程
          • 6.并发
          • 7.并行
          • 查看当前电脑cpu数量
    • 2.线程基本使用
        • 1.线程类图
        • 2.继承Thread创建线程
          • 细节说明
          • 代码实例
        • 3.实现Runnable来创建线程(包括静态代理演示)
          • 代码实例
    • 3.多线程机制
        • 简要介绍
        • 代码实例
        • 为什么使用start开启线程?
          • 追源码
          • 示意图
    • 4.多个子线程案例
        • 题目1
          • 代码实例
          • 结果
        • 继承Thread和实现Runnable的区别
        • 题目2:多线程售票问题
          • 代码实例
          • 结果
          • 问题
    • 5.线程终止
        • 通知线程终止方法
          • 代码实例
          • 结果
    • 6.线程常用方法
        • 常用方法第一组(线程中断)
          • 注意事项
          • 代码实例
          • 结果
        • 常用方法第二组(线程插队)
          • 代码实例
          • 结果
        • 线程插队练习
          • 题目
          • 代码
          • 结果
        • 用户线程和守护线程
          • 基本介绍
          • 设置守护线程的代码实例
          • 结果
    • 7.线程的七大状态
          • 状态介绍
          • 代码实例
          • 结果
    • 8.线程同步机制
        • 基本介绍
        • 实现同步的具体方法
        • 互斥锁
        • 使用两种方法实现卖票(在方法上加锁)
          • 注意事项
          • 代码实例
        • 使用synchronized代码块的方式加锁(推荐)
          • 细节说明
          • 代码实例
          • 结果
    • 9.线程的死锁
        • 基本介绍
        • 代码实例
        • 结果
    • 10.释放锁
        • 以下操作会释放锁
        • 以下操作不会释放锁
    • 11.多线程练习题
        • 题目一
          • 代码
          • 结果
        • 题目二
          • 代码
          • 结果

1.线程相关概念

1.程序

为完成特定任务、用某种语言编写的一组指令的集合,简单来说就是我们写的代码

2.进程
  1. 指运行中的程序,没启动一个进程,操作系统就会为这个进程分配新的内存空间。比如启动了迅雷,系统就会为他分配内存空间
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程,有他自身产生、存在和消亡的过程
3.线程
  1. 线程是由进程创建的,是进程的一个实体
  2. 一个进程可以拥有多个线程
  3. 就相当于迅雷是一个进程,创建了多个下载线程image-20240107164210160
4.单线程

同一个时刻,只允许执行一个线程

5.多线程

同一个时刻,可以执行多个线程,比如:一个qq进程可以同时打开多个聊天窗口

6.并发

同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发

image-20240107165446496

7.并行

同一时刻,多个任务并发执行,多个cpu可以实现并行

image-20240107165453621

查看当前电脑cpu数量
/**
 * @author 孙显圣
 * @version 1.0
 */
public class CpuNum {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime(); //单例模式,获取Runtime实例
        int cpuNum = runtime.availableProcessors(); //获取当前cpu数量
        System.out.println(cpuNum);

    }
}

image-20240107170143573

2.线程基本使用

1.线程类图

image-20240107170445239

2.继承Thread创建线程
细节说明
  1. 当一个类继承了Thread类,该类就可以当做线程使用
  2. 我们会重写run方法,写上自己的业务代码
  3. Thread类,实现了Runnable接口的run方法
代码实例
package Thread_;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class Thread01 {
    public static void main(String[] args) {
        Cat cat = new Cat(); //创建线程对象
        cat.start();//线程开始
    }
}

class Cat extends Thread { //继承Thread类,重写run方法来写自己的逻辑
    int num = 0; //统计次数
    @Override
    public void run() {
        while (true) {
            System.out.println("猫猫在跳舞" + (++num));
            try {
                Thread.sleep(1000); //线程休眠1秒
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (num == 8) { //次数为8次则退出循环,从而结束run方法,结束线程
                break;
            }
        }
    }
}

image-20240107172638559

3.实现Runnable来创建线程(包括静态代理演示)
代码实例
package Thread_;

import org.junit.jupiter.api.Test;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog(); //创建线程类的实例
        Thread thread = new Thread(dog); //将线程类的实例传到Thread中
        thread.start(); //启动线程调用线程类的run方法

    }

    //用来测试静态代理模式
    @Test
    public void test() {
        Tiger tiger = new Tiger();
        Thread_ thread = new Thread_(tiger); //将实现了Runnable接口的类的实例传进去
        thread.start();
    }
}

//继承Runnable接口来创建线程
class Dog implements Runnable {

    @Override
    public void run() {
        System.out.println("Dog线程启动");
    }
}


class Tiger implements Runnable {

    @Override
    public void run() {
        System.out.println("老虎在叫");
    }
}

//模拟Thread,静态代理模式
class Thread_ implements Runnable {
    private Runnable target = null; //使用属性接受那个实现了Runnable接口的实例


    public Thread_(Runnable target) { //传入一个实现了Runnable接口的实例
        this.target = target;
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

    public void start() { //1.创建当前实例,调用start方法
        start0();
    }

    public void start0() { //2.调用run
        run();
    }

}

image-20240107202443199

3.多线程机制

简要介绍
  1. 当程序开始执行的时候,系统会开启一个进程,可以在Terminal输入jconsole可以查看
  2. 这个进程会先开启main线程,然后在下面的代码实例中可以看出main线程开启了一个子线程Cat
  3. 这样主线程并不会被阻塞,而是会和子线程交替执行
代码实例
package Thread_;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat(); //创建线程对象
        cat.start();//线程开始

        //当main线程启动一个子线程的时候,主线程不会阻塞,会继续执行
        for (int i = 0; i < 60; i++) {
            System.out.println("主线程" + i);
            Thread.sleep(1000); //主线程休眠
        }
    }
}

class Cat extends Thread { //继承Thread类,重写run方法来写自己的逻辑
    int num = 0; //统计次数
    @Override
    public void run() {
        while (true) {
            System.out.println("猫猫在跳舞" + (++num));
            try {
                Thread.sleep(1000); //线程休眠1秒
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (num == 80) { //次数为8次则退出循环,从而结束run方法,结束线程
                break;
            }
        }
    }
}

image-20240107184358023

为什么使用start开启线程?
追源码
  1. 打断点image-20240107185327269
  2. 跳入,走几步image-20240107185416568
  3. 这里实际上是调用了start0方法,而这个方法是native的方法,是虚拟机调用的,用来启动线程,可以理解为是虚拟机调用了run方法
  4. 注意:如果直接调用run方法并不能开启线程,只能说是串行化执行,它将会作为一个普通方法在main线程里串行化执行
示意图

image-20240107185821671

4.多个子线程案例

题目1

image-20240107202616489

代码实例
package Thread_;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class Thread03 {
    public static void main(String[] args) {
        //启动第一个线程
        Thread1 thread1 = new Thread1();
        thread1.start();

        //启动第二个线程
        Thread2 thread2 = new Thread2();
        Thread thread = new Thread(thread2);
        thread.start();

    }
}

// 第一个线程继承Thread类
class Thread1 extends Thread {
    private int count = 0;

    @Override
    public void run() {

        while (true) {

            System.out.println("hello world");
            count++;

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            if (count == 10) {
                break;
            }
        }
    }
}

// 第二个线程实现Runnable接口
class Thread2 implements Runnable {
    private int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("hi");
            count ++;

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            if (count == 5) {
                break;
            }
        }
    }
}

结果

image-20240107204606732

继承Thread和实现Runnable的区别
  1. 通过继承Thread和实现Runnable接口来创建线程本质上并没有区别
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制(建议使用)

image-20240107205225393

题目2:多线程售票问题

image-20240108105244964

代码实例
package Thread_;

/**
 * @author 孙显圣
 * @version 1.0
 * 多线程售票
 */
public class SellTicket {
    public static void main(String[] args) {
        //创建三个线程
//        for (int i = 0; i < 3; i++) {
//            new SellWin01().start();
//        }

        //创建三个线程,使用实现接口的方法
        for (int i = 0; i < 3; i++) {
            new Thread(new SellWin02()).start();
        }

    }
}

class SellWin01 extends Thread {
    public static int tickets = 100; //多个实例共享100张票

    @Override
    public void run() { //线程操作:售票

        while (true){ //循环卖票
            //判断退出条件
            if (SellWin01.tickets == 0) {
                return;
            }
            //卖票
            System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + (--SellWin01.tickets) + "张票");
            //休眠
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

class SellWin02 extends SellWin01 implements Runnable {
}
结果

image-20240108105502913

问题
  1. 多个线程出现卖的是一张票的问题
  2. 说明在第一个线程没有卖完,第二个线程就进去了并且也卖票了

5.线程终止

通知线程终止方法
  1. 通过使用变量,来控制run方法退出
  2. 将一个布尔型变量设置为私有的,然后设置他的set方法
  3. 使用这个布尔型变量控制循环,这样就可以在其他线程来设置这个变量从而控制线程退出
代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        //开启子线程
        ThreadExit_ threadExit = new ThreadExit_();
        new Thread(threadExit).start();

        Thread.sleep(10*1000); //主线程睡眠10s
        //通知子线程退出
        threadExit.setLoop(false);
    }
}

//写一个线程
class ThreadExit_ implements Runnable {
    private boolean loop = true; //用来控制循环

    public void setLoop(boolean loop) { //使用主线程来控制这个循环条件
        this.loop = loop;
    }

    @Override
    public void run() {
        while (loop) {
            System.out.println("子线程运行中");

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("子线程退出");
    }
}

结果

image-20240108111258251

6.线程常用方法

常用方法第一组(线程中断)

image-20240108134009741

注意事项
  1. start底层会创建新的线程,调用run,run是一个简单的方法调用,不会启动新的线程
  2. 线程优先级的范围(1到10)image-20240108134429741
  3. interrupt,中断线程,但是并没有真正的结束线程,所以一般用于中断正在休眠的线程
  4. sleep:线程的静态方法,使当前线程休眠
代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class ThreadMethod01 {
    public static void main(String[] args) {
        //主线程设置一个子线程
        ThreadM threadM = new ThreadM();
        // setName 设置线程名字
        threadM.setName("Sun");
        // setPriority 设置线程优先级
        threadM.setPriority(Thread.MIN_PRIORITY); //这里是调用了Thread的默认静态常量设置的优先级为1
        // 获取线程名字和优先级
        System.out.println(threadM.getName() + "的优先级为" + threadM.getPriority());
        //启动线程
        threadM.start();

        //主线程打印十个hello
        for (int i = 0; i < 10; i++) {
            System.out.println("hello");
        }
        //调用线程中断,中断线程的休眠,执行中断逻辑
        threadM.interrupt();
    }
}

//写一个线程类
class ThreadM extends Thread {
    @Override
    public void run() {
        //线程一直执行
        while (true) {
            //吃十个包子
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "吃包子" + i); //获取当前线程的名字
            }
            //然后休眠10s
            try {
                Thread.sleep(10*1000);
            } catch (InterruptedException e) { //这个捕捉的就是中断异常,在main线程中调用中断则会触发
                // 这里可以写上自己的业务逻辑
                System.out.println("吃包子的休眠被中断了!!!");
            }
            //在这里中断逻辑处理完之后会继续循环
        }
    }
}
结果

image-20240108135813332

常用方法第二组(线程插队)

image-20240108142054091

代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        //启动子线程
        ThreadQueueCutting threadQueueCutting = new ThreadQueueCutting();
        Thread thread = new Thread(threadQueueCutting);
        thread.start();

        //主线程每隔一秒输出hi,输出20次
        int count = 0;
        for (int i = 0; i < 20; i++) {
            //在输出完五次的时候,让子线程插队,会先执行完子线程所有的内容,然后再继续执行主线程
            if (count == 5) {
//                thread.join(); //线程插队
                Thread.yield(); //线程礼让,如果资源丰富则不会成功
            }
            System.out.println("hi");
            count ++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

//子线程,每隔一秒输出hello, 输出20次
class ThreadQueueCutting implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("hello");
            //间隔一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

结果

image-20240108142244685

线程插队练习
题目

image-20240108143120549

代码
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        //启动子线程
        ThreadCut threadCut = new ThreadCut();
        threadCut.start();

        //主线程,每隔1s,输出hi,一共10次
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                threadCut.join(); //当执行完五次之后子线程插队
            }
            System.out.println(Thread.currentThread().getName() + " : hi!");
            Thread.sleep(1000);
        }

    }
}

//子线程,每隔1s输出hello,输出10次
class ThreadCut extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.cur  entThread().getName() + " : hello!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}


结果

image-20240108143154528

用户线程和守护线程
基本介绍
  1. 用户线程:也叫工作线程,当线程的任务执行完成或以通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  3. 常见的守护线程:垃圾回收机制
设置守护线程的代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        //设置子线程为守护线程,这样即使他是无限循环的,但是,只要没有线程正在执行,那么他就会退出
        DaemonThreads daemonThreads = new DaemonThreads();
        Thread thread = new Thread(daemonThreads);
        thread.setDaemon(true);
        thread.start();

        //主线程
        for (int i = 0; i < 10; i++) {
            System.out.println("宝强在辛苦的工作");
            Thread.sleep(1000);
        }
    }
}

//守护线程
class DaemonThreads implements Runnable {

    @Override
    public void run() {
        for (;;) { //无限循环
            System.out.println("马蓉和宋吉在快乐的聊天");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

结果

image-20240108145209798

7.线程的七大状态

状态介绍
  1. NEW
  2. Runnable
    1. Ready
    2. Running
  3. TimeWaiting(超时等待,调用sleep时)
  4. Waiting(等待,调用join时)
  5. Blocked
  6. Teminated(终止)

image-20240108152228764

代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        ThreadS threadS = new ThreadS();
        System.out.println(threadS.getState()); //NEW状态
        threadS.start();
        while (threadS.getState() != Thread.State.TERMINATED) { //只要子线程的状态不是终止状态,就一直输出子线程的状态
            System.out.println(threadS.getState());
            Thread.sleep(1000);
        }
        //当子线程结束时输出子线程的状态
        System.out.println(threadS.getState());
    }
}

//子线程
class ThreadS extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("执行" + i);
            try {
                Thread.sleep(500); //可以引起超时等待状态TimneWaiting
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }


        }
    }
}
结果

image-20240108153336183

8.线程同步机制

基本介绍

image-20240108164116684

实现同步的具体方法

image-20240108164049286

互斥锁
  1. 使用关键字synchronized来与对象的互斥锁联系
  2. 虽然实例方法的代码是所有实例共享的,但是在并发中,每个实例对象拥有独立的实例方法(这么理解)
  3. 非静态同步:直接使用synchronized,这样的同步是对象锁,也就是给每个对象的实例方法加锁,这样其他非本类型的实例只能同时有一个访问本类型对象的实例方法
  4. 静态同步:使用synchronized static, 这样的同步是类锁,由于加了static,则这个方法是被本类型的所有实例共享的,这样本类型的所有实例只能同时有一个访问这个静态方法
  5. 简单来说静态同步是对本类型实例实现互斥的,非静态同步是对非本类型实例实现互斥的
使用两种方法实现卖票(在方法上加锁)
注意事项
  1. 这个继承Thread类的就相当于一个线程,所以要创建三个线程实例并访问run方法的sell方法,这就是本类型的互斥(静态同步)
  2. 而实现了Runnable接口的类X并不是一个线程,而Thread类的对象才是一个线程,如果是创建了X的对象,并创建了三个Thread类的对象来调用X的方法,那么对于X的方法sell来说就是要实现非本类型的互斥(非静态同步)
  3. 同步方法如果没用static修饰,默认锁对象为this
  4. 同步方法如果使用static修饰,默认锁对象为当前类.class
代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class Syn {
    public static void main(String[] args) {
        
//        for (int i = 0; i < 3; i++) { //三个线程对象,访问三个对象各自的方法,所以要使用静态同步
//            new SellTickets().start();
//        }
        
        
        SellTickes_ sellTickes = new SellTickes_(); //三个线程访问一个对象的同一个,所以使用非静态同步(对象锁)即可
        for (int i = 0; i < 3; i++) {
            new Thread(sellTickes).start();
        }
    }
}

// synchronized解决售票问题,使得同时只能有一个线程售票


// 使用静态同步
class SellTickets extends Thread {
    public static int ticket = 100; //100张票

    @Override
    public void run() {
        while (true) { //一直售票,当票数为0则退出
            try {
                sell();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    //售票方法:同时只能有一个线程调用,这里必须将方法设置成静态的,因为只有这样所有的实例才共享这个方法的锁
    public synchronized static void sell() throws InterruptedException {
        if (SellTickets.ticket <= 0) {
            System.exit(0); //只要有一个线程进入这个方法,判断票数不够,就直接系统退出
        }
        System.out.println(Thread.currentThread().getName() + "售出一张票,剩余 " + (--SellTickets.ticket));
    }
}


// 使用对象锁
class SellTickes_ implements Runnable {

    @Override
    public void run() {
        while (true) { //一直售票,当票数为0则退出
            try {
                sell();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public synchronized  void sell() throws InterruptedException { //对象锁
        if (SellTickets.ticket <= 0) {
            System.exit(0); //只要有一个线程进入这个方法,判断票数不够,就直接系统退出
        }
        System.out.println(Thread.currentThread().getName() + "售出一张票,剩余 " + (--SellTickets.ticket));
    }
}

image-20240108171022324

使用synchronized代码块的方式加锁(推荐)
细节说明
  1. 类锁:当多个实例访问一个类来调用run方法的时候使用
  2. 对象锁:当多个实例访问一个实例来调用那个实例的run方法的时候使用,一般使用this,但是其实只要这个对象是多个实例共享的也可以,是几个对象访问的都要是同一个,可以理解为这个对象就是门的钥匙,但是只有一把
  3. 对象锁举一个例子:就是我下面的那个对象锁,使用的是Dog类(随便的一个类),原因是使用实现Runnable的方式其实是几个Thread实例访问SellT2的实例,从而调用run方法,所以需要使用对象锁,而他们每次访问的一定是一个对象,所以我在类中初始化了一个Dog类,这个类是属于对象的在内存中唯一的一个实例,所以就相当于把这个对象当做一个钥匙,其他实例通过抢它才能够得到进入代码块的机会
  4. 再简单点来说,只要是多个线程共享的那个资源就可以当锁,因为线程需要取得那个资源才能进入代码块
  5. 一个线程类的实例就相当于一个线程,每个实例都拥有自己的run方法代码(这样理解)
代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class Syn_ {
    public static void main(String[] args) {
//        for (int i = 0; i < 3; i++) {
//            new SellT1().start(); //开启线程
//        }
        SellT2 sellT2 = new SellT2();
        for (int i = 0; i < 3; i++) {
            new Thread(sellT2).start();
        }
    }
}

//使用继承Thread的方式——类锁
class SellT1 extends Thread {
    public static int sticket = 100; //100张票

    @Override
    public void run() {
            while (true) {
                //这个方式是创建三个实例来调用本类的方法,所以应该使用类锁
                synchronized (SellT1.class) {
                    //在这里写售票逻辑
                    if (SellT1.sticket <= 0) {
                        return;
                    }
                    System.out.println(Thread.currentThread().getName() + "已经售出一张票,剩余 " + (-- SellT1.sticket));
                }
                try {
                    Thread.sleep(10); //休眠一会
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    }
}

//使用实现Runnable的方式——对象锁
class SellT2 implements Runnable {
    public static int sticket = 100;
    Dog dog = new Dog();

    @Override
    public void run() {

        while (true) {
            //这个方式是创建三个Thread类型的实例来访问一个类型的实例,所以使用对象锁
            synchronized (dog) { //这个实例锁,可以是任何类型,只要是几个实例共享的即可
                if (SellT2.sticket <= 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "已经售出一张票,剩余 " + (-- SellT2.sticket));
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
结果

image-20240108194531716

9.线程的死锁

基本介绍

image-20240108203508949

代码实例
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class DeadLock {
    public static void main(String[] args) {
        new DeadLockDemo(false).start();
        new DeadLockDemo(true).start();
    }
}

// 线程类
class DeadLockDemo extends Thread {
    //两个Object类型的互斥锁,满足多个线程共享的条件
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    boolean flag;

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag == false) {
            synchronized (lock1) { //第一个线程会抢占这个lock1,再想抢lock2但是lock2被第二个线程先抢到了,所以无法释放lock1
                System.out.println("进入1");
                synchronized (lock2) {
                    System.out.println("进入2");
                }
            }

        }
        else {
            synchronized (lock2) { //第二个线程会抢占这个lock2,再想抢lock1但是lock1被第一个线程先抢到了,所以无法释放lock2
                System.out.println("进入2");
                synchronized (lock1) {
                    System.out.println("进入1");
                }
            }

        }
    }
}


结果

10.释放锁

以下操作会释放锁

image-20240108211024357

以下操作不会释放锁

image-20240108211219564

11.多线程练习题

题目一

image-20240109093008556

代码
package Thread__;

import java.util.Scanner;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class HomeWork01 {
    public static void main(String[] args) {
        Th1 th1 = new Th1();
        new Th2(th1).start();
        th1.start();
    }
}


class Th1 extends Thread {
    //设置一个私有属性通知变量
    private boolean loop = true;
    //设置get和set方法

    public boolean isLoop() {
        return loop;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        while (loop){
            System.out.println(((int) (Math.random() * 100))); // 0 到 100
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Th2 extends Thread {
    //线程一类型的属性
    private Th1 th1;
    //设置一个构造方法获取线程一的实例

    public Th2(Th1 th1) {
        this.th1 = th1;
    }

    @Override
    public void run() {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("请输入英文字母:");
            char re = scanner.next().charAt(0);
            if (re == 'Q') {
                System.out.println("通知线程一退出");
                th1.setLoop(false);
            }
        }
    }
}

结果

image-20240109093126638

题目二

image-20240109093145253

代码
package Thread__;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class HomeWork02 {
    public static void main(String[] args) {
        Tori tori = new Tori();
        for (int i = 0; i < 2; i++) {
            new Thread(tori).start();
        }
    }
}

// 取钱线程类
class Tori implements Runnable {
    //设计一个实例变量即可,因为这个实例变量就是两个线程共享的了(原因是这个是实现接口的方式)
    private double sal = 10000;
    @Override
    public void run() {
        //一直取钱
        while (true) {
            //取钱逻辑需要加锁
            synchronized (Tori.class) { //这里加一个类锁就可以,因为是多线程共享的
                if (sal <= 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "取出1000,剩余" +  (sal -= 1000));
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
结果

image-20240109095331781

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

qtxlsx 学习

简介&#xff1a; QXlsx是一个可以读写Excel文件的库。不依赖office以及wps组件&#xff0c;可以在Qt5支持的任何平台上使用。 QXlsx和QAxObject 比较 QAxObject使用需要系统中安装了offie或wps&#xff0c;这种方法不推荐使用&#xff1b; 因为如果安装了wps&#xff0c;可…

puzzle(1122)连线迷宫

目录 一&#xff0c;连线迷宫-经典模式 1&#xff0c;规则 2&#xff0c;策略 3&#xff0c;调整的局部性 4&#xff0c;八连通端点的线条合并 taptap小游戏 迷宫解谜 连线迷宫模式 一&#xff0c;连线迷宫-经典模式 1&#xff0c;规则 2&#xff0c;策略 分2步&#x…

Kali远程操纵win7

一.准备 1.介绍 攻击方&#xff1a;kali IPV4:192.168.92.133 被攻击方&#xff1a;win7 IPV4:192.168.92.130 2.使用永恒之蓝漏洞 (1.使用root权限 (2.进入msfconsole (3.添加rhosts (4.run进行一下 二.进行远程操作 1.获取用户名和密码 在cmd5查询 2.获取syste…

Windows下如何查看端口被谁占用

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 开始时经常遇到这种…

LVS负载均衡-DR模式配置

LVS&#xff1a;Linux virtual server ,即Linux虚拟服务器 LVS自身是一个负载均衡器&#xff08;Director&#xff09;&#xff0c;不直接处理请求&#xff0c;而是将请求转发至位于它后端的真实服务器real server上。 LVS是四层&#xff08;传输层 tcp/udp&#xff09;负载均衡…

SpringBoot SpringMVC (详解)

6. SpringBoot Spring 的诞⽣是为了简化 Java 程序的开发的&#xff0c;⽽ Spring Boot 是为了快速开发 Spring 程序开发而诞生的。 Spring Boot 的优点&#xff1a; 快速集成框架&#xff0c;Spring Boot 提供了启动添加依赖的功能&#xff0c;⽤于秒级集成各种框架。内置运⾏…

来了!小学生Python创意编程(视频教学版)

目录 写在前面 推荐图书 推荐理由 写在最后 写在前面 在最好的年纪&#xff0c;一起来学Python吧&#xff01;本期博主给大家推荐一本适合小学生阅读的书籍&#xff0c;一起来看看吧~ 推荐图书 小学生Python创意编程&#xff08;视频教学版&#xff09; 直达链接&#x…

uniapp输入框事件(防抖)

一、描述 在输入框输入内容或者说输入关键词的时候&#xff0c;往往都要进行做防抖处理。如果不做防抖&#xff0c;你输入什么&#xff0c;动态绑定的数据就会保持一致。这样不好吗&#xff0c;同步获取。有个业务场景&#xff0c;如果是搜索框&#xff0c;你每次一个字符&…

【差分约束+并查集】第十三届蓝桥杯省赛C++ A组 Java A组/研究生组《推导部分和》(C++)

【题目描述】 【输入格式】 【输出格式】 【数据范围】 【输入样例】 5 3 3 1 5 15 4 5 9 2 3 5 1 5 1 3 1 2 【输出样例】 15 6 UNKNOWN 【思路】 题解来源&#xff1a;AcWing 4651. $\Huge\color{gold}{推导部分和}$ - AcWing 【代码】 #include<bits/stdc.h> #define…

基于深度学习的心律异常分类算法

基于深度学习的心律异常分类系统——算法设计 第一章 研究背景算法流程本文研究内容 第二章 心电信号分类理论基础心电信号产生机理MIT-BIH 心律失常数据库 第三章 心电信号预处理心电信号噪声来源与特点基线漂移工频干扰肌电干扰 心电信号读取与加噪基于小波阈值去噪技术的应用…

mamba的学习记录

最近新出了一种很火的架构mamba&#xff0c;听说吊打transformer&#xff0c;特此学习一下&#xff0c;总结一下学习的内容。 state-spaces/mamba (github.com)3个月8Kstar&#xff0c;确实有点受欢迎。 目录 1.先验 RNN​ LSTM ​2.mamba State Space Models​ Selecti…

两个有序序列的中位数(全网首篇递归、分治解决)

题目描述 已知有两个等长非降序序列 S 1 S_1 S1​和 S 2 S_2 S2​。先将 S 1 S_1 S1​和 S 2 S_2 S2​ 合并为 S 3 S_3 S3​ &#xff0c;求 S 3 S_3 S3​的中位数。 输入描述 第一行&#xff0c;序列 S 1 S_1 S1​ 和 S 2 S_2 S2​ 的长度 N N N , 第二行&#xff…

Uibot6.0 (RPA财务机器人师资培训第4天 )批量开票机器人案例实战

类似于小北之前发布的一篇博客&#xff08;不能说很像&#xff0c;只能说是一模一样&#xff09; Uibot (RPA设计软件&#xff09;财务会计Web应用自动化(批量开票机器人&#xff09;-CSDN博客https://blog.csdn.net/Zhiyilang/article/details/136782171?spm1001.2014.3001.…

JAVA面试大全之并发篇

目录 1、并发基础 1.1、多线程的出现是要解决什么问题的? 本质什么? 1.2、Java是怎么解决并发问题的? 1.3、线程安全有哪些实现思路? 1.4、如何理解并发和并行的区别? 1.5、线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式? 1.6、通常线程有哪几…

【Android 源码】Android源码下载指南

文章目录 前言安装Repo初始化Repo选择分支没有梯子替换为清华源 有梯子 下载源码下载开始参考 前言 这是关于Android源码下载的过程记录。 环境&#xff1a;Windows上通过VMware安装的Ubuntu系统 安装Repo 创建Repo文件目录 mkdir ~/bin PATH~/bin:$PATH下载Repo工具&#…

Elastic Search 8.13: 为开发者简化嵌入和排名

作者&#xff1a;来自 Elastic Alvin Richards, Ranjana Devaji Elasticsearch 8.13 扩展了能力&#xff0c;使开发者能够利用人工智能和机器学习模型创建快速和卓越的搜索体验。与 Apache Lucene 9.10 集成&#xff0c;测量向量搜索性能在基准测试中超过 2 倍&#xff0c;扩展…

基于springboot+vue+Mysql的酒店管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

git配置SSH 密钥

git配置SSH 密钥 1.window配置ssh1.安装ssh2.安装 Git&#xff08;安装教程参见安装Git&#xff09;并保证版本大于 1.9![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e59f4e16b83c45649f1d9d7bd6bf92c0.png)3.SSH 尽量保持最新&#xff0c;6.5之前的版本由于使用…

【保姆级讲解如何Chrome安装Vue-devtools的操作】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

ruoyi-nbcio-plus基于vue3的flowable其它元素(目前主要是元素文档)的升级修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…