线程基本知识

多线程基础

1. 线程相关概念

1.1 程序(program)

最为简单完成特点任务,用某种语言编写的一组指令的集合.

最简单的说:就是我们写的代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UuJZz9xG-1676541728695)(photo/image-20230209101938936.png)]

1.2 进程

1.进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。

2.进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

1.3 什么是线程

1.线程由进程创建的,是进程的一个实体

2.一个进程可以拥有多个线程,如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpVlAOZo-1676541728697)(photo/image-20230209102110670.png)]

1.4 其他相关概念

1.单线程:同一个时刻,只允许执行一个线程

2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dweiThI1-1676541728698)(photo/image-20230209102150136.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3m36W6e-1676541728699)(photo/image-20230209102216641.png)]

4.并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。并发和并行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9zkWPvF-1676541728700)(photo/image-20230209102247648.png)]

2. 线程基本使用

2.1 创建线程的两种方式

在java中线程来使用有两种方法。

1.继承Thread类,重写run方法

2.实现Runnable接口,重写run方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pbk8E3Ai-1676541728701)(photo/image-20230209102406622.png)]

2.2 线程应用案例 1-继承 Thread 类

创建Thread01.java com.yujianedu.threaduse
1)请编写程序,开启一个线程,该线程每隔1秒。在控制台输出“喵喵,我是小猫咪”

2)对上题改进:当输出80次喵喵,我是小猫咪,结束该线程

3)使用JConsole 监控线程执行情况,并画出程序示意图!

当你在运行一个多线程程序的时候,假如你的主线程已经结束,如果该主线程下还有子线程在运行,就不会导致整个程序的结束(当所有的线程都结束了才会导致程序结束).

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtNTsDnh-1676541728702)(photo/image-20230209110134389.png)]

代码如下:

package com.yujianedu.threaduse;

/**
 * 演示通过继承 Thread 类创建线程
 */
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //创建 Cat 对象,可以当做线程使用
        Cat cat = new Cat();
        /*
            //start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
            //真正实现多线程的效果, 是 start0(), 而不是 run
            private native void start0();
         */
        cat.start(); //启动线程-> 最终会执行 cat 的 run 方法

        //cat.run();//run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行
        //说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        //这时 主线程和子线程是交替执行.. System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字 main
        for(int i = 0; i < 60; i++) {
            System.out.println("主线程 i=" + i);
            //让主线程休眠
            Thread.sleep(1000);
        }
    }
}

//老韩说明
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run 方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的 run 方法
class Cat extends Thread{

    @Override
    public void run() { //重写 run 方法,写上自己的业务逻辑
        int times = 0;
        while (true){
            //该线程每隔 1 秒。在控制台输出 “喵喵, 我是小猫咪"
            System.out.println("喵喵,我是小妈咪"+(++times)+ " 线程名=" + Thread.currentThread().getName());
            //让该线程休眠 1 秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 8){
                break; //当 times 到 80, 退出 while, 这时线程也就退出
            }
        }
    }
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9CDq9B6-1676541728702)(photo/image-20230209115610483.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8Dle3ij-1676541728703)(photo/image-20230209115658739.png)]

2.3 线程应用案例 2-实现 Runnable 接口

  • 说明

    1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
    2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
  • 应用案例
    请编写程序,该程序可以每隔1秒。在控制台输出“hi!”,当输出10次后,自动退出。请使用实现Runnable接口的方式实现。Thread02.,java,这里底层使用了设计模式[代理模式]=>代码模拟实现Runnable接口开发线程的机制

  • 代码如下:

package com.yujianedu.threaduse;

/**
 * 通过实现Runnable 来开发线程
 */
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程正在运行"+ i);
        }
    }
}

class Dog implements Runnable{

    int count = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("小狗汪汪叫~~hi" + (++count) +Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10){
                break;
            }
        }
    }
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jgii5Y2E-1676541728709)(photo/image-20230209132317577.png)]

2.4 线程使用应用案例-多线程执行

请编写一个程序,创建两个线程,一个线程每隔1秒输出“hello,world",输出10次,退出,一个线程每隔1秒输出“hi”,输出5次退出.Thread03.java

package com.yujianedu.threaduse;

public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();

        Thread t1Thread = new Thread(t1);
        Thread t2Thread = new Thread(t2);
        t1Thread.start();
        t2Thread.start();
    }
}

class T1 implements Runnable{

    @Override
    public void run() {
        int count = 0;
        while (true){
            System.out.println("hello,world"+ (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10){
                break;
            }
        }
    }
}

class T2 implements Runnable{

    @Override
    public void run() {
        int count = 0;
        while (true){
            System.out.println("hi"+ (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5){
                break;
            }
        }
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HegRbqdJ-1676541728710)(photo/image-20230209134020948.png)]

2.5 线程如何理解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3EoImai2-1676541728711)(photo/image-20230209134049357.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qvBq9M9c-1676541728712)(photo/image-20230209134059310.png)]

3. 继承 Thread vs 实现 Runnable 的区别

1.从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口

2.实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable

3.[售票系统],编程模拟三个售票窗口售票100,分别使用继承Thread 和实现Runnable方式,并分析有什么问题?SellTicket.java

代码如下:

package com.yujianedu.ticket;

/**
 * 使用多线程,模拟三个窗口同时售票 100 张
 */
public class SellTicket {
    public static void main(String[] args) {

        //测试
        //SellTicket01 sellTicket01 = new SellTicket01();
        //SellTicket01 sellTicket02 = new SellTicket01();
        //SellTicket01 sellTicket03 = new SellTicket01();

        //这里我们会出现超卖.
        //sellTicket01.start();
        //sellTicket02.start();
        //sellTicket03.start();

        System.out.println("===使用实现接口方式来售票=====");
        SellTicket02 sellTicket02 = new SellTicket02();
        new Thread(sellTicket02).start();//第 1 个线程-窗口
        new Thread(sellTicket02).start();//第 2 个线程-窗口
        new Thread(sellTicket02).start();//第 3 个线程-窗口

    }
}

//使用 Thread 方式
class SellTicket01 extends Thread{
    private static int ticketNum = 100; //让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0){
                System.out.println("售票结束...");
                break;
            }
            //休眠 50 毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
        }
    }
}

class SellTicket02 extends Thread{
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0){
                System.out.println("售票结束...");
                break;
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:

不管是使用继续Thread类或者实现Runnable接口,都会出现超卖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ka1VAP2K-1676541728712)(photo/image-20230209140440481.png)]

4. 线程终止

4.1 基本说明

1.当线程完成任务后,会自动退出。

2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

4.2 应用案例 ThreadExit_.java com.hspedu.exit_

需求:启动一个线程t,要求在main线程中去停止线程t,请编程实现.

代码如下:

package com.yujianedu.exit_;

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.start();
        System.out.println("main线程休息五秒");

        //如果希望main线程去控制t1进程的终止,可以去修改loop
        //让t1 退出run方法,从而终止 t1进程 -> 通知方式
        Thread.sleep(1000 * 5);
        t1.setLoop(false);
    }
}

class T1 extends Thread{

    private int count = 0;
    //设置一个控制遍历(让其他的进程可以来控制这个进程)
    private boolean loop = true;

    @Override
    public void run() {
        while (loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 正在运行中..."+(++count));
        }
    }

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

执行结果:

在代码运行开始,5秒后由主线程终止了t1进程

5. 线程常用方法

5.1 常用方法第一组

1.setName//设置线程名称,使之与参数 name相同

2.getName//返回该线程的名称

3.start// 使该线程开始执行;Java虚拟机底层调用该线程的 start0方法

4.run//调用线程对象run方法;

5.setPriority//更改线程的优先级

6.getPriority//获取线程的优先级

7.sleep/在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

8.interrupt //中断线程

5.3 注意事项和细节

1.start 底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程

2.线程优先级的范围

3.interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程

4.sleep:线程的静态方法,使当前线程休眠

5.3 应用案例 ThreadMethod01.java

代码如下:

package com.yujianedu.mtthod_;

public class ThreadMethod {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("yujian");
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();
        //测试优先级
        System.out.println("默认优先级"+Thread.currentThread().getPriority());
        //测试Interrupt,主线程休息5秒就让T中断sleep

        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi"+i);
        }
        t.interrupt();
    }
}

class T extends Thread{
    private int count = 0;
    @Override
    public void run() {
        while (true){
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+ "吃包子~~~~~");
            }
            System.out.println(Thread.currentThread().getName()+ "休眠中~~~~~");
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName()+ "被Interrupt了~~~~~");
            }
        }
    }
}

5.4 常用方法第二组

1.yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功

2.join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
案例:main线程创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1秒,输出hi,输出20次.要求:
两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T51B3GXu-1676541728713)(photo/image-20230209145853488.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5iknA2QZ-1676541728714)(photo/image-20230209145900853.png)]

5.5 应用案例

ThreadMethod02.java

测试 yield 和 join 方法 ,注意体会方法的特点,看老师代码演示

package com.yujianedu.mtthod_;

public class ThreadMthod02 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();

        for (int i = 1; i < 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程(老爸)吃了" + i + "个包子");
            if (i==5){
                System.out.println("主线程(老爸) 让 子线程(儿子)先吃完所有包子");
                //join : 线程插队
                t2.join();
                System.out.println("子线程(儿子) 吃完了,主线程(老爸)开始吃");
            }
        }
    }
}

class T2 extends Thread{

    @Override
    public void run() {

        for (int i = 1; i < 20; i++) {
            try {
                Thread.sleep(1000); //休眠一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(儿子)吃了" + i + "个包子");
        }
    }
}

5.6 课堂练习

ThreadMethodExercise.java 5min
1.主线程每隔1s,输出hi,一共10次

2.当输出到hi5时,启动一个子线程(要求实现Runnable),每隔1s输出hello,等该线程输出10次hello后,退出

3.主线程继续输出hi,直到主线程退出.

4.如图,完成代码其实线程插队…

5.代码如下

package com.yujianedu.mtthod_;

public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i < 10; i++) {
            Thread.sleep(1000);
            System.out.println("h1"+i);
            if (i == 5){ //说明主线程输出了 5 次 hi
                SonThread sonThread = new SonThread();
                Thread thread = new Thread(sonThread);
                thread.start(); // 启动子线程 输出 hello...
                thread.join();//立即将 t3 子线程,插入到 main 线程,让 t3 先执行
            }
        }
    }
}

class SonThread implements Runnable{
    @Override
    public void run() {
        for (int i = 1;i<=10;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello"+i);
        }
    }
}

6.执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRKmYFtH-1676541728714)(photo/image-20230209152349220.png)]

5.7 用户线程和守护线程

1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

3.常见的守护线程:垃圾回收机制

5.8 应用案例 ThreadMethod03.java

下面我们测试如何将一个线程设置成守护线程

package com.yujianedu.mtthod_;

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //如果我们希望当我们主线程结束后,子进程自动结束
        //只需将子线程设置为守护进程
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();
        for (int i = 1; i <= 10; i++) {
            System.out.println("宝强辛苦的工作");
            Thread.sleep(1000);
        }
    }
}

class MyDaemonThread extends Thread{
    @Override
    public void run(){
        for ( ; ;){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋集快乐聊天~~~");
        }
    }
}

当主线程运行完毕后,假如此时此线程还没有运行结束(也会被强制停止)

6. 线程的生命周期

6.1 JDK 中用 Thread.State 枚举表示了线程的几种状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZqQXht3-1676541728715)(photo/image-20230210095906916.png)]

6.2 线程状态转换图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mldTQVoV-1676541728715)(photo/image-20230210094447227.png)]

6.3 写程序查看线程状态 ThreadState_.java

代码如下

package com.yujianedu.state_;

public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + "状态" + t.getState());
        t.start();
        //当线程状态不等于终止状态就一直运行
        while (Thread.State.TERMINATED != t.getState()){
            System.out.println(t.getName() + "状态" + t.getState());
            Thread.sleep(1000);
        }
        System.out.println(t.getName() + "状态" + t.getState());
    }
}

class T extends Thread{
    @Override
    public void run() {
        while (true){
            for (int i = 0; i < 10; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
} 

执行结果:

Thread-0状态NEW
Thread-0状态RUNNABLE
hi 0
Thread-0状态TIMED_WAITING
hi 1
Thread-0状态TIMED_WAITING
hi 2
Thread-0状态TIMED_WAITING
hi 3
Thread-0状态TIMED_WAITING
hi 4
Thread-0状态TIMED_WAITING
hi 5
Thread-0状态TIMED_WAITING
hi 6
Thread-0状态TIMED_WAITING
hi 7
Thread-0状态TIMED_WAITING
hi 8
Thread-0状态TIMED_WAITING
hi 9
Thread-0状态TIMED_WAITING
Thread-0状态TERMINATED

7. 线程的同步

7.1 先看一个问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EgWdzNvq-1676541728716)(photo/image-20230210101248235.png)]

8. Synchronized

8.1 线程同步机制

1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。

2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。

8.2 同步具体方法-Synchronized

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QuJWjfqF-1676541728716)(photo/image-20230210101417136.png)]

9. 分析同步原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vut3NQPd-1676541728716)(photo/image-20230210104257688.png)]

10. 互斥锁

10.1 基本介绍

1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

3.关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问

4.同步的局限性:导致程序的执行效率要降低

5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)

6.同步方法(静态的)的锁为当前类本身。

10.2 使用互斥锁来解决售票问题

代码如下:

package com.yujianedu.sync;

/**
 * 使用多线程,模拟三个窗口同时售票 100 张(使用线程同步,让其不会超卖)
 */
public class SellTicket {
    public static void main(String[] args) {

        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第 1 个线程-窗口
        new Thread(sellTicket03).start();//第 2 个线程-窗口
        new Thread(sellTicket03).start();//第 3 个线程-窗口

    }
}

class SellTicket03 implements Runnable {
    private int ticketNum = 100;

    //注意:
    //1.public synchronized void m1(){} ,这里的锁是加在 SellTicket03.class 上的
    public synchronized static void m1(){
    }
    public static void m2(){
        synchronized (SellTicket03.class){ //在静态方法中,同步代码块中也是加入该类
            System.out.println("m2");
        }
    }

    // 1.private synchronized void sell() {}这是一个同步方法
    // 2.这时锁在 this对象上
    // 3.也可以在代码块上写 synchronized ,同步代码块
    private /*synchronized*/ void sell() { //同步方法,在同一时刻,只能有一个线程执行sell方法
        synchronized (this){
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                return;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
        }
    }

    @Override
    public void run() {
        while (ticketNum > 0) {
            sell();
        }
    }
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zyMQkLwD-1676541728717)(photo/image-20230210102155617.png)]

10.3 注意事项和细节

1.同步方法如果没有使用static修饰:默认锁对象为this

2.如果方法使用static修饰,默认锁对象:当前类.class

3.实现的落地步骤:

  • 需要先分析上锁的代码
  • 选择同步代码块或同步方法
  • 要求多个线程的锁对象为同一个即可!

11. 线程的死锁

11.1 基本介绍

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.

11.2 应用案例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IN5GgUD7-1676541728717)(photo/image-20230210110145944.png)]

11.3 应用案例 DeadLock_.java

package com.yujianedu.sync;

public class DeadLock_ {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A 线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B 线程");
        A.start();
        B.start();
    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }

    @Override
    public void run() {
        //下面业务逻辑的分析
        //1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
        //2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
        //3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
        //4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
        if (flag) {
            synchronized (o1) {//对象互斥锁, 下面就是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入 1");
                synchronized (o2) { // 这里获得 li 对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入 2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入 3");
                synchronized (o1) { // 这里获得 li 对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入 4");
                }
            }
        }
    }
}

执行结果:线程会一直卡着

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BWKpoZFi-1676541728718)(photo/image-20230210110225720.png)]

12. 释放锁

12.1 下面操作会释放锁

1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来

2.当前线程在同步代码块、同步方法中遇到break、return。
案例:没有正常的完事,经理叫他修改bug,不得已出来

3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束 案例:没有正常的完事,发现忘带纸,不得已出来

4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

12.2 下面操作不会释放锁

1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会

2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

13. 本章作业

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AT7Peg0Z-1676541728718)(photo/image-20230210111019237.png)]

思路分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tCSqIhlf-1676541728718)(photo/image-20230210111843033.png)]

代码如下:

package com.yujianedu.homework;

import java.util.Scanner;

public class HomeWork01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);
        a.start();
        b.start();
    }
}
class A extends Thread{
    private boolean loop = true;

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

    @Override
    public void run() {
        //输出1-100的数
        while (loop){
            System.out.println((int) (Math.random() * 100 +1 ));
            //休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("a线程退出...");
    }
}
class B extends Thread{
    private A a;
    private Scanner in = new Scanner(System.in);

    public B(A a){ //构造器中,直接传入A对象
        this.a = a;
    }

    @Override
    public void run() {
        //接收用户的输入
        System.out.println("请输入你的指令(Q),表示退出:");
        while (true){
            char key = in.next().toUpperCase().charAt(0);
            if (key == 'Q'){
                //以通知的方式结束a线程
                a.setLoop(false);
                System.out.println("b进程退出.");
                break;
            }
        }
    }
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NI28IfNB-1676541728719)(photo/image-20230210121255864.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6kdYr2om-1676541728719)(photo/image-20230210121315874.png)]

代码如下:

package com.yujianedu.homework;

public class Homewok02 {
    public static void main(String[] args) {
        bank bank = new bank();
        new Thread(bank).start();
        new Thread(bank).start();
    }
}

class bank implements Runnable {

    private int price = 10000;

    @Override
    public void run() {
        while (true) {

            //解读
            //1. 这里使用 synchronized 实现了线程同步
            //2. 当多个线程执行到这里时,就会去争夺 this对象锁
            //3.哪个线程争夺到线程锁,就执行 synchronize 代码块,执行完后,会释放this对象锁
            //4.争夺不到this对象锁,就blocked,准备继续争夺
            //5. this对象锁是非公平锁
            synchronized (this) {
                //判断余额是否够
                if (price >= 1000) {
                    System.out.println("线程" + Thread.currentThread().getName() + "取走了1000元");
                    System.out.println("当前银行卡还剩下:" + (price -= 1000));
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("银行卡余额不足...");
                    break;
                }
            }
        }
    }
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p3bFfFB3-1676541728719)(photo/image-20230210134214659.png)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值