java学习笔记 - 第17章:多线程基础


总体内容

在这里插入图片描述


相关概念

程序

在这里插入图片描述

进程

在任务管理器中可以看到电脑现在启动的进程
在这里插入图片描述

线程

在这里插入图片描述

单线程多线程

在这里插入图片描述

并发并行

在这里插入图片描述
& 并发和并行可能同时存在
& 比如说:当一个cpu上有多个任务时,就并发了。一台计算机中只有2个cpu(A和B),有两个任务在A和B上并行执行,这时又来了第三个任务,第三个任务在A上执行,此时A上有两个任务,他们的执行就是并发的
& 现在的计算机一般是:2个cpu,每个cpu4个核,共8核

public class Excise {
    public static void main(String[] args) {
        //获取当前时间(currentRuntime)
        //RunTime是单例模式的 private static Runtime currentRuntime = new Runtime();
        Runtime runtime = Runtime.getRuntime();
        //获取当前电脑cpu/核心数 (available可获得的Processors处理器)
        int cpuNums = runtime.availableProcessors();
        System.out.println(cpuNums);//8
    }
}


线程的使用

创建线程

在这里插入图片描述

方式1:继承Thread类

代码演示

在这里插入图片描述

解读:

  • 当类继承了Thread类时,该类就可以当作线程使用(Thread:线程)
  • 重写run()方法,写上自己线程要实现的业务代码
  • Thread类中的run()方法 实现了 Runnable接口中的run()方法
  • 具体内容看代码上的标注吧,第三个问题在多线程机制里写
public class Excise {
    public static void main(String[] args) {
        //实例化Cat对象,该对象可以当作一个线程使用
        Cat cat = new Cat();
        //启动线程cat
        cat.start();
    }
}

//1. 当类继承了Thread类时,该类就可以当作线程使用(Thread:线程)
//2. 我们会重写run()方法,写上自己的业务代码
//3. Thread类中的run()方法 实现了 Runnable接口中的run()方法
/*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */
class Cat extends Thread {
    @Override
    public void run() {//重写run()方法,写上自己的业务代码
        //2. 实现:当输出8次时,结束该进程->增加循环,次数到8时,跳出循环
        int times = 0;
        while (true) {
            //1. 实现:该线程每隔一秒,在控制台输出“喵喵,我是小猫咪”
            System.out.println("喵喵,我是小猫咪" + (++times));
            //让该线程休眠一秒,Thread.sleep(1000)会报编译异常,用Ctrl+Alt+T
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 8) {
                break;
            }
        }
    }
}

效果:每隔一秒输出一条信息,直到第8条退出
在这里插入图片描述

多线程机制
机制解读

在这里插入图片描述
1&. 当程序运行时,就启动了一个进程,进程执行到main()方法时,创建一个main线程,main线程中执行到cat.start()时,会创建另外一个线程Thread-0(通过Thread.currentThread.getName()可以获取当前线程的名字)
2&. 要注意的是:main线程在开始Thread-0线程后,不会阻塞,也就是主线程不会等子线程执行完才继续执行后面的内容,这时主线程和子线程交替执行(并发)
在这里插入图片描述

JConsole监控线程

① 启动程序
② 在终端中启动jconsole
在这里插入图片描述
③ 对本程序:Excise进行连接
在这里插入图片描述
④ 看进程内线程的执行情况
在这里插入图片描述
⑤ 解读:
jconsole可以监控进程内线程的执行情况,连接方法上面都有,这里对线程执行情况进行解读:

& 编写代码时设置了:main()中的循环为60次,Cat类的run()中的循环为80次
& 开始时,主线程和子线程交替执行
& 当主线程中的循环执行到60次时,主线程结束(销毁),子线程继续执行
& 当子线程中的循环执行到80次时,子线程结束,此时进程也结束了
& 注意:主线程结束,不会导致子线程结束

为什么用start()

解读问题:start()方法最终会调用cat的run()方法,那为什么不直接调用cat的run()方法,而是要执行cat.start()呢?
cat.run()run()是一个普通的方法,没有真正启动一个线程。 run()方法和主线程的其它普通方法串行执行,也就是主线程阻塞到run()方法这里,等待run()执行结束后,再继续执行后面的普通方法
cat.start()start()会为cat对象启动一个线程,然后调用cat的run方法,这时主线程和子线程并发(交替)执行–>多线程
在这里插入图片描述
读start()源码

public synchronized void start() {//①
	start0();
}
//satrt0()是本地方法,由JVM调用,底层是C/C++实现
//真正实现多线程效果的是start0(),而不是run()
private native void start0();//②

在这里插入图片描述

方式2:实现Runnable接口

在这里插入图片描述

代码演示:线程

在这里插入图片描述

import java.util.Date;

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
//        dog.start();//这里不能掉哟个start()
        //创建Thread对象,把dog对象(实现了Runnable接口),放入thread
        Thread thread = new Thread(dog);
        thread.start();
    }
}

class Dog implements Runnable{

    @Override
    public void run() {
        int count = 0;
        while (true){
            System.out.println("hi 小狗狗"+(++count)+" "+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count==10){
                break;
            }
        }
    }
}
代码演示:代理模式

上面的那种方法,底层用了设计模式=>代理模式
代码模拟:实现Runnable接口 开发线程机制=>极简的Thread类

解读:
① 先模拟一个极简Thread类。Thread类实现了Runnable接口,所以我们编写一个ThreadProxy类也实现Runnable接口
② ThreadProxy类中的内容:target属性存放自定义线程对象,run()调用target的run(),start()调用start0(),start0()是jvm调用的,它把target的线程状态转换为可运行态,当分配到cpu时,target的run()方法被执行,这里简写为start0()调用run()方法
③ 编写自定义线程类。该类已经继承了一个类,所以只能通过实现Runnable接口来实现线程,重写run方法,写上该线程要做的逻辑
④ 调用自定义线程的run()。先声明线程,前面提到了为什么用start()调用run(),而不是直接调用run(),所以这里不再阐述。因为Runnable接口中没有start()方法,所以我们要通过Thread类来调用start()方法,首先我们把自定义线程对象传到Thread类中,再调用Thread类的start()方法
⑤ 接④来讲解流程。传入以后,target存放着自定义线程对象,然后start()调用start0(),start0()调用run(),run()调用target.run()
⑥ 这个模拟用到了静态代理模式

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        ThreadProxy threadProxy = new ThreadProxy(dog);//自定义线程dog=>target
        threadProxy.start();//start()调用start0(),start0()调用run(),run()调用target.run()
    }
}

class Animal {
}

//因为java单继承机制,所以不能再继承Thread,只能实现Runnable接口
class Dog extends Animal implements Runnable {

    @Override
    public void run() {
        System.out.println("小狗汪汪叫..");
    }
}

//线程代理类,模拟一个极简的Thread类
class ThreadProxy implements Runnable {
    private Runnable target = null;//属性,类型是Runnable

    @Override
    public void run() {//run方法:调用自定义线程类的run()方法
        if (target != null) {
            target.run();
        }
    }

    public ThreadProxy(Runnable target) {//构造器:传入自定义线程类的对象
        this.target = target;
    }

    public void start() {
        start0();
    }

    private void start0() {
        run();//调用代理类中的run()方法
    }
}

继承Thread vs 实现Runnable接口

在这里插入图片描述

线程使用案例

多线程如何理解

在这里插入图片描述
在这里插入图片描述

实例:多线程执行(易)

在这里插入图片描述
使用实现Runnable方法来实现

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        Hello hello = new Hello();
        Hi hi = new Hi();
        Thread thread = new Thread(hello);
        thread.start();
        Thread thread1 = new Thread(hi);
        thread1.start();
    }
}

class Hello implements Runnable {
    private int count;

    @Override
    public void run() {
        while (true) {
            System.out.println("hello world" + (++count) + "  线程名为:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

class Hi implements Runnable {
    private int count;

    @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 == 5) {
                break;
            }
        }
    }
}

输出结果:
在这里插入图片描述

实例:多线程售票出现的问题

在这里插入图片描述

① 因为实现Runnable接口时,多个线程可以共享一个资源(都可以访问自定义线程类中的内容),所以自定义线程类中的属性不需要定义成静态属性
② 但是继承Thread类时,属性必须定义成静态,才能被多个线程(自定义线程类的对象)共同访问
③ 可能出现的问题:当票数=1时,三个线程共同判断票数是否≤0,此时三个线程都判断为false,继而都进行了票数–的操作,导致出现了负票数的现象
这时就需要引入同步和互斥了。
在这里插入图片描述

/**
 * 使用多线程,模拟三个窗口同时售票(100张票)==>出现问题
 */
public class Excise {
    public static void main(String[] args) throws InterruptedException {
        //1、 继承Thread
        //创建三个窗口(线程),因为三个都是SellTicket1的对象,所以访问同一个ticketNums
//        SellTicket1 sellTicket1 = new SellTicket1();
//        SellTicket1 sellTicket2 = new SellTicket1();
//        SellTicket1 sellTicket3 = new SellTicket1();
//        sellTicket1.start();//启动线程
//        sellTicket2.start();
//        sellTicket3.start();
        //2、 实现Runnable接口
        SellTicket2 sellTicket2 = new SellTicket2();
        new Thread(sellTicket2).start();//启动三个线程,共享自定义线程类sellTicket2中的内容
        new Thread(sellTicket2).start();
        new Thread(sellTicket2).start();
    }
}

class SellTicket1 extends Thread {
    private static int ticketNums = 100;//设置成静态变量,只要是SellTicket类的对象,就都能访问

    @Override
    public void run() {
        while (true) {
            //判断票数量
            if (ticketNums <= 0) {
                System.out.println("售票结束...");
                break;
            }
            //休眠 => 售票员休息
            try {
                Thread.sleep(70);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票 剩余票数 = " + (--ticketNums));

        }
    }
}

class SellTicket2 implements Runnable {
    //这里不需要设置成静态变量,因为用实现Runnable方式时,线程可以共享SellTicket类中的内容
    private int ticketNums = 100;

    @Override
    public void run() {
        while (true) {
            //判断票数量
            if (ticketNums <= 0) {
                System.out.println("售票结束...");
                break;
            }
            //休眠 => 售票员休息
            try {
                Thread.sleep(70);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票 剩余票数 = " + (--ticketNums));

        }
    }
}

通知线程退出

在这里插入图片描述
代码演示
在这里插入图片描述

步骤:

  1. 在自定义线程类中,定义一个控制变量loop,默认值为ture。
    private boolean loop = true;//设置一个控制变量(控制run()方法的终止)
  2. 将loop作为while的循环条件
    while(loop){}
  3. 编写loop的set()方法。因为如果想在main线程中控制该线程的结束,main线程中必须能修改循环条件loop的值,所以得写一个set()方法
    public void setLoop(boolean loop) { this.loop = loop; }
  4. 在main线程中设置loop值为false
    t1.setLoop(false);//main线程中通知t1线程退出

解读:

  1. 前面写的案例都是让线程执行完毕后,自动退出
  2. 这个案例的目的是让 线程被动退出(线程1通知线程2退出)。
  3. 线程退出就是让run()方法结束,因为run()方法中有while循环,所以要让循环结束,这样run()方法也就结束了。所以使用变量来控制while循环,从而控制run()方法的结束,当run()结束时,这个线程也就结束了
public class Excise {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();
        //如果希望主线程控制子线程的终止,必须可以修改loop=>给loop写一个set()方法
        //目的:让t1退出run()方法,从而终止t1线程-->通知的方式
        //让主线程休眠10秒,再通知 t1线程退出
        System.out.println("主线程休眠5秒");
        Thread.sleep(5 * 100);
        t1.setLoop(false);//main线程中通知t1线程退出
    }
}

class T extends Thread {
    private int count;
    private boolean loop = true;//设置一个控制变量(控制run()方法的终止)

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

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

输出结果:
在这里插入图片描述


线程常用方法

在这里插入图片描述
在这里插入图片描述

线程中断

注意:interrupt()的使用,是中断线程,不是结束线程,一般用于中断线程休眠
下面是代码的输出结果
在这里插入图片描述

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("王胖子");//1. setName()设置这个线程的名字
        t.setPriority(Thread.MIN_PRIORITY);//2. 设置线程优先级:1
        System.out.println(t.getPriority());
        ;//3. 获取线程优先级
        t.start();//4. start()启动t线程
        for (int i = 1; i <= 5; i++) {
            Thread.sleep(1000);//5. sleep()主线程休眠
            System.out.println("主线程正在输出" + i);
        }
        //6. 终止t线程的休眠。
        //当调用interrupt()时,sleep就会抛出一个InterruptedException异常
        //然后被catch捕获到,继而停止休眠,继续执行
        t.interrupt();
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            //输出100条语句
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " 正在学习..." + i);//7. 获取当前线程的名字
            }
            //休眠20秒
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中...");
                Thread.sleep(20 * 1000);
            } catch (InterruptedException e) {
                //当执行到interrupt()方法时,就会捕获一个中断异常,这一个代码块可以加自己的业务代码
                //InterruptedException 是中断异常
                System.out.println(Thread.currentThread().getName() + " 的休眠被中断...");
            }
        }
    }
}

线程插队

  • yield() 当资源充足时,就不会礼让了
    ① 两个人吃10个包子,A怕B不够吃,所以A礼让B会成功(cpu把资源先给B)
    ② 两个人吃一万个包子,A就没必要礼让B了,这时cpu就不会把资源先给B了
  • join() 一定会插队成功:B插队,则会把资源先给B,B执行完后,再执行A

在这里插入图片描述
案例:
在这里插入图片描述

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();
        for (int i = 1; i <= 5; i++) {
            Thread.sleep(50);
            System.out.println("主线程(小弟)吃了 " + i + " 个包子");//开始时,主线程和子线程交替执行
            //实现:主线程执行3次后,让子线程先执行完,主线程再执行
            if (i == 3) {
                System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
                t.join();//把子线程 插到 主线程前面
                System.out.println("子线程(老大) 吃完了 主线程(小弟) 接着吃");
            }
        }
    }
}

class T extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大)吃了 " + i + " 个包子");
        }
    }
}

输出效果
在这里插入图片描述
线程插队练习
在这里插入图片描述

线程B插入到线程A中,让线程B先执行
① 在线程A中启动线程B b.start();
② 在线程A中插入线程B b.join()
③ 如果是实现Runnable接口的线程,需要通过Thread对象调用start()和join()
Thread thread = new Thread(new B());
thread.start();//启动子线程
thread.join();//将子线程插入,先执行子线程

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new T());
        for (int i = 1; i <= 10; i++) {
            System.out.println("Hi " + i);
            if (i == 5) {
                thread.start();//启动子线程
                thread.join();//立即将子线程插入,先执行子线程
            }
            Thread.sleep(1000);
        }
    }
}

class T implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {//可以把for换成while+变量count
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Hello " + i);
        }
    }
}

守护线程

在这里插入图片描述
1Q. 为什么把一个线程设置成守护线程(Daemon):
① 前面写的都是用户线程,在主线程中启动一个子线程,当主线程退出后,如果子线程run()没有执行完,那么子线程会继续执行。
② 如果要实现:主线程退出时,子线程也跟着退出,则要把子线程设置成主线程的守护线程

2Q. 怎样把一个线程设置成守护线程
如果想监控其它进程/获取其它线程的信息,就可以为它做一个守护线程。

MyDaemon myDaemon = new MyDaemon();//1. 创建子线程
myDaemon.setDaemon(true);//2. 将子线程设置成主线程的守护线程
myDaemon.start();//3. 启动该子线程

完整代码:

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        MyDaemon myDaemon = new MyDaemon();//创建子线程
        myDaemon.setDaemon(true);//将子线程设置成主线程的守护线程
        myDaemon.start();//启动该子线程
        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程....." + i);
            Thread.sleep(1000);
        }
    }
}

class MyDaemon extends Thread {
    @Override
    public void run() {
        for (; ; ) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程.....");
        }
    }
}

输出结果:
在这里插入图片描述


线程的生命周期

在这里插入图片描述
Runnable状态(可运行)可以细分为:Ready(就绪) 和 Running(运行)
所以可以细分成7种
在这里插入图片描述
写一个程序查看线程状态

public class Excise {
    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()) {//t线程的状态不为终止时 执行
            System.out.println(t.getName() + " 的状态是:" + t.getState());
            Thread.sleep(500);//主线程休眠
        }
        System.out.println(t.getName() + " 的状态是:" + t.getState());
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(1000);//子线程休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程输出.....");
            }
            break;
        }
    }
}

输出结果:
在这里插入图片描述


同步机制(Synchronized)

机制与使用

在这里插入图片描述
比如:去厕所,一个人进去把门锁了,等到出去时,又把门打开,下一个人才能进去
在这里插入图片描述

实例:解决多线程售票问题

解决票超卖的问题

  • 把卖票的方法sell()写成同步方法(synchronized)。这里把卖票的代码单独写到了一个方法中,让run()调用sell()方法。使得同一时刻只有一个线程可以进入sell()中,该线程执行完后,其它线程才能有机会进入
  • 添加属性loop来结束while循环。 因为把卖票代码提到另一个方法后,卖票结束时,只能return结束sell()方法,不能break结束while循环,就导致了while无法停止循环,已知重复调用sell(),输出"售票结束"
    ① 定义loop
    ② while的循环条件改为loop
    ③ 售票结束后,修改loop值
public class Excise {
    public static void main(String[] args){
        SellTicket3 sellTicket3 = new SellTicket3();
        Thread thread1 = new Thread(sellTicket3);
        Thread thread2 = new Thread(sellTicket3);
        Thread thread3 = new Thread(sellTicket3);
        thread1.setName("窗口1");//设置线程名
        thread2.setName("窗口2");
        thread3.setName("窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//解决票超卖的问题:使用线程同步
class SellTicket3 implements Runnable {
    private int ticketNums = 100;//实现Runnable接口,可以让多个线程访问该属性
    //控制while循环的变量:很巧妙的让while停止循环
    // (不控制的话,sell()中的return只会退出sell(),但没有停止while,还会一遍遍的调用sell)
    private boolean loop = true;

    //这里不直接给run()加同步了,因为run里面可能还有别的代码可以同时被访问,
    // 所以把需要同步的代码,单独写在一个方法里
    @Override
    public void run() {
        while (loop) {
            sell();//同步方法
        }
    }

    public synchronized void sell() {//同步方法,同一时刻只能有一个线程来执行sell方法
        if (ticketNums == 0) {//判断票数量
            System.out.println(Thread.currentThread().getName() + " 售票结束...");
            loop = false;//售票结束后,让while循环也停止
            return;
        }
        try {
            Thread.sleep(70);//休眠 => 售票员休息
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 售出一张票 剩余票数 = " + (--ticketNums));
    }
}

输出结果:
在这里插入图片描述
分析:
这个的"钥匙" 叫互斥锁
在这里插入图片描述


互斥锁

在这里插入图片描述

为同步方法/代码块加互斥锁:
① 静态同步方法/代码块的锁:类本身(类.class)
② 非静态同步方法/代码块的锁:this(当前对象)/同一其它对象
注意1:售票案例中是使用实现Runnable接口的方法,所以三个线程使用了同一个自定义线程对象,所以this指向相同的对象,但是继承Thread类就不是了
在这里插入图片描述
注意2:锁为相同的一个对象,是为了保证三个线程抢同一个锁,如果是不同的对象,那三个线程就不用抢了

说明:object是自定义线程类的属性,所以也是同一个对象啦
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


死锁

在这里插入图片描述
死锁的代码说明:
在这里插入图片描述


释放锁

在这里插入图片描述
在这里插入图片描述


本章作业

作业1

在这里插入图片描述

思路:
在这里插入图片描述

import java.util.Scanner;

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

/**
 * 线程1:循环随机打印100以内的整数
 */
class T1 extends Thread {
    private boolean loop = true;//控制while循环

    @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(Thread.currentThread().getName() + " 退出!");
    }

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

/**
 * 线程2:键盘读取到Q,就终止线程1
 */
class T2 extends Thread {
    Scanner scanner = new Scanner(System.in);
    private T1 t1;//关联线程1

    public T2(T1 t1) {//构造器
        this.t1 = t1;
    }

    @Override
    public void run() {
        //线程2:键盘读取到Q,就终止线程1
        while (true) {
            System.out.println("输入一个字符(输入Q停止输出):");
            System.out.println("输入字符后马上回车!");
            char key = scanner.next().charAt(0);
            if (key == 'Q') {
                t1.setLoop(false);//以通知的方式结束线程1(t1)
                break;
            }
        }
        System.out.println(Thread.currentThread().getName() + " 退出!");
    }
}

输出结果:
在这里插入图片描述

作业2:取钱

在这里插入图片描述

思路:
在这里插入图片描述

public class Excise {
    public static void main(String[] args) throws InterruptedException {
        Card card = new Card();
        Thread thread1 = new Thread(card);
        Thread thread2 = new Thread(card);
        thread1.setName("张三");
        thread2.setName("王五");
        thread1.start();
        thread2.start();
    }
}

class Card implements Runnable {
    private int money = 10000;

    @Override
    public void run() {
        while (true) {
            //两个线程争夺 this对象锁(非公平锁=>得抢)
            //争夺到就执行,执行完释放对象锁,继续争夺
            //争夺不到就继续阻塞,准备继续争夺
            synchronized (this) {
                if (money < 1000) {
                    System.out.println("本卡余额不足...");
                    break;
                }
                money -= 1000;
                System.out.println(Thread.currentThread().getName() + " 取了1000,当前余额 = " + money);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果=>没有出现超取现象(用到线程同步):
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值