Java笔记14 多线程基础

14.1 线程相关概念

14.1.1 程序 program

是为了完成特定任务,用某种语言编写的一组指令的集合

简单地说就是我们写的代码

14.1.2 进程

  1. 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
  2. 进程是程序的一次执行过程,或者正在运行的一个程序。是动态过程:具有它自身的产生、存在和消亡的过程

14.1.3 线程

  1. 线程是由进程创建的,是进程的一个实体
  2. 一个进程可以有多个线程

14.1.4 其他相关概念

  1. 单线程:同一个时刻,只允许执行一个线程
  2. 多线程:同一个时刻,可以执行多个进程。比如:一个QQ进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
  3. 并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单来说,单核CPU实现的多任务就是并发
  4. 并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行
  5. 并发和并行可以同时进行

14.2 线程的基本使用

14.2.1 创建线程的两种方式

  1. 继承 Thread 类,重写 run 方法
  2. 实现 Runnable 接口,重写 run 方法
    1. Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这是再用继承 Thread 的方法来创建线程显然不可能了
    2. 因此Java设计者提供了另一种方式创建线程,就是通过实现 Runnable 接口来创建线程

在这里插入图片描述

14.2.2 继承Thread类实例

  1. 请编写程序,开启一个线程,该线程每隔1秒。在控制台输出“喵喵,我是小猫咪"
  2. 对上题改进:当输出80次喵喵,我是小猫咪,结束该线程
  3. 使用JConsole监控线程执行情况,并画出程序示意图!
public class ExtendsThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        //创建 Cat 对象,可以当做线程使用
        Cat cat = new Cat();
        cat.start();
        /*
        (1)
        public synchronized void start() {
            start0();
        }
        (2)
        //start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
        //真正实现多线程的效果, 是 start0(), 而不是 run
        private native void start0();
         */

        //cat.run();//run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行

        //说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        //这时 主线程和子线程是交替执行.
        for(int i = 0; i < 60; i++) {
            System.out.println("主线程 " + Thread.currentThread().getName() + " 继续执行 i=" + i);
            //主线程 main 继续执行 i=0
            //让主线程休眠
            Thread.sleep(1000);
        }
    }
}

//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run 方法,写上自己的业务代码
//3. Thread 类 实现了 Runnable 接口的 run 方法
/*
    @Override
        public void run() {
            if (target != null) {
                target.run();
        }
    }
*/
class Cat extends Thread {
    int times = 0;
    @Override
    public void run() {//重写run方法,写上自己的业务逻辑

        while (times < 80){
            System.out.println("喵喵,我是小猫咪" + ++times + "线程名:" + Thread.currentThread().getName());
            //喵喵,我是小猫咪1线程名:Thread-0
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

14.2.3 start()方法

在这里插入图片描述

真正创建线程的是start()方法,start()方法调用start0()方法后,该线程不一定会立马执行,只是将线程变成了可运行状态(Ready状态),具体什么时候执行(Running状态),取决于CPU,由CPU统一调度。

14.2.4 实现Runnable接口实例

请编写程序,该程序可以每隔1秒,在控制台输出“hi!",当输出10次后,自动退出。请使用实现Runnable接口的方式实现。【这里底层使用了设计模式[静态代理模式]】

public class RunnableThread {
    public static void main(String[] args) {
        Dog dog = new Dog();
//        dog.start();//这里不能调用start()
        Thread thread = new Thread(dog);
        thread.start();
    }
}

class Dog implements Runnable {//通过实现Runnable接口创建线程
    int count = 0;
    @Override
    public void run() {
        while (count < 10) {
            System.out.println("小狗汪汪叫..hi " + ++count + " 线程名:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

代码模拟实现Runnable接口开发线程的机制

public class SimulateRunnable {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
        
    }
}

class Animal {}

class Tiger extends Animal implements Runnable {

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

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

    public ThreadProxy(Runnable target) {
        this.target = target;//动态绑定(运行类型Tiger)
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    public void start() {
        start0();//这个方法时真正实现多线程方法
    }

    public void start0() {
        run();
    }

}

14.2.5 多线程执行案例

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

public class MultithreadDemo {
    public static void main(String[] args) {
        new Thread(new T1()).start();
        new Thread(new T2()).start();
    }
}
class T1 implements Runnable {

    @Override
    public void run() {
        //每隔1秒输出"hello,world",输出10次
        int count = 0;
        for (int i = 0; i < 10; i++){
            System.out.println("hello,world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class T2 implements Runnable {

    @Override
    public void run() {
        //每隔1秒输出"hello,world",输出10次

        for (int i = 0; i < 5; i++){
            System.out.println("hi");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

14.2.6 继承 Thread 和 实现 Runnable 的区别

  1. 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了 Runnable 接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable

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

public class SellTicket {
    public static void main(String[] args) {
        //继承Thread方法
//        new SellTicket01().start();
//        new SellTicket01().start();
//        new SellTicket01().start();
        //会出现票数超卖现象

        //实现Runnable接口方法
        SellTicket02 sellTicket02 = new SellTicket02();
        new Thread(sellTicket02).start();
        new Thread(sellTicket02).start();
        new Thread(sellTicket02).start();
        //同样会出现票数超卖现象
    }
}

class SellTicket01 extends Thread {
    private static int ticketNum = 100;//多个线程共享,用static
    @Override
    public void run() {
        super.run();
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                break;
            }
            //休眠50ms
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() +
                    " 售出了一张票,还剩 " + --ticketNum);
        }
    }
}

class SellTicket02 implements Runnable {
    private int ticketNum = 100;//一个对象,不用static
    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                break;
            }
            //休眠50ms
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() +
                    " 售出了一张票,还剩 " + --ticketNum);
        }
    }
}

14.3 线程终止

14.3.1 说明

  1. 当线程完成任务后,会自动退出
  2. 还可以通过 使用变量 来控制 run 方法退出的方式停止线程,即通知方式

14.3.2 示例

启动一个线程t,要求在 main 方法中去停止线程t

public class NotifyExit {
    public static void main(String[] args) {
        AThread aThread = new AThread();
        new Thread(aThread).start();
        for (int i = 0; i < 60; i++) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main线程运行中 " + i);
            if (i == 20) {
                aThread.setLoop(false);
                System.out.println("停止了AThread");
            }
        }
    }
}
class AThread implements Runnable {
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("AThread运行中...");
        }
    }

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

14.4 线程常用方法

14.4.1 第一组方法

  1. setName:设置线程名称,使之与参数 name 相同
  2. getName:返回线程的名称
  3. start:使该线程开始执行,Java虚拟机底层调用该线程的 start0 方法
  4. run:调用该线程对象的 run 方法
  5. setPriority:更改线程的优先级
  6. getPriority:获取线程的优先级
  7. sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt:中断线程

14.4.2 使用细节

  1. start 底层会创建新的线程,调用run。run 就是一个简单的方法调用,不会启动新的线程
  2. 线程优先级范围:
    1. MAX_PRIORITY:10
    2. MIN_PRIORITY:1
    3. NORM_PRIORITY:5
  3. interrupt 中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠的线程
  4. sleep:线程的静态方法,使当前线程休眠

14.4.3 示例

public class ThreadMethod {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("tom");
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();

        //主线程输出 5 个 "hi" 然后就中断 子线程休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + i);
        }

        System.out.println(t.getName() + " 线程的优先级:" + t.getPriority());
        t.interrupt();//执行到这里就会中断t线程的休眠

    }
}

class T extends Thread {
    @Override
    public void run() {
        super.run();
        while (true){
            for (int i = 0; i < 100; i++) {
                //Thread.currentThread().getName() 获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + "  吃包子~~~ " + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                //当线程执行到一个 interrupt 方法时,就会 catch 一个异常,可以加入自己的业务代码
                //InterruptedException 是捕获到一个中断异常
                System.out.println(Thread.currentThread().getName() + " 被 interrupt 了");
            }
        }
    }
}

14.4.4 第二组方法

  1. yield:线程的礼让。让出 cpu ,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插队线程的所有任务

14.4.5 示例

  1. 主线程每隔1s,输出hi,一共10次
  2. 当输出到hi 5时,启动一个子线程(要求 实现Runnable),每隔1s输出hello,等 该线程输出10次hello后,退出
  3. 主线程继续输出hi,直到主线程退出.
public class ThreadCutInLine {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new HelloThread());
        for (int i = 1; i < 11; i++) {
            System.out.println("hi " + i);
            Thread.sleep(1000);
            if (i == 5) {
                thread.start();
                thread.join();
            }
        }
    }
}

class HelloThread implements Runnable {

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

14.4.6 用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  3. 常见的守护线程:垃圾回收机制
  4. 通过调用setDaemon(true);方法将线程设为守护线程

14.4.7 示例

public class DaemonThread {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread dt = new MyDaemonThread();
        dt.setDaemon(true);
        dt.start();
        for (int i = 1; i < 21; i++) {
            Thread.sleep(500);
            System.out.println("主线程执行中...");
        }
    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (; ; ) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MyDaemonThread守护线程正在执行...");
        }
    }
}

14.5 线程的生命周期

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

在这里插入图片描述

14.5.2 线程状态转换图

在这里插入图片描述

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        TS ts = new TS();
        System.out.println(ts.getName() + " 的状态:" + ts.getState());
        ts.start();

        while (Thread.State.TERMINATED != ts.getState()) {
            System.out.println(ts.getName() + " 的状态:" + ts.getState());
            Thread.sleep(500);
        }
        System.out.println(ts.getName() + " 的状态:" + ts.getState());
    }
}

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

14.6 线程的同步

14.6.1 线程同步机制

  1. 在多线程编程,一些敏感数据不允许多个线程同时访问,此时就使用同步访问计数,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
  2. 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不能对这个内存地址进行操作,知道该线程完成操作,其他线程才能对该内存地址进行操作

14.6.2 同步具体方法 synchronized

  1. 同步代码块:

    synchronized (对象) { //得到对象的锁,才能操作同步代码
        //需要被同步的代码
    }
    
  2. 同步方法:

    public synchronized void 方法名(参数列表) { //同一时刻只能有一个线程操作该方法
        //需要被同步的代码
    }
    

14.6.3 互斥锁

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只 能有一个线程访问该对象
  3. 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时, 表明该对象在任一时刻只能由一个线程访问
  4. 同步的局限性:导致程序的执行效率要降低
  5. 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
  6. 同步方法(静态的)的锁即为当前类本身

使用细节:

  1. 同步方法如果没有使用 static 修饰(对象锁):默认锁对象为 this
  2. 如果方法使用 static 修饰(类锁):默认锁对象为当前类.class
  3. 带你真的理解synchronize的对象锁和类锁的使用
  4. 关于synchronized,对象锁的理解
  5. 实现步骤:
    1. 先分析需要上锁的代码
    2. 选择同步代码块或同步方法
    3. 要求多个线程的锁对象为同一个即可

14.6.4 售票问题

public class SellTicket2 {
    public static void main(String[] args) {
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
    }
}

//同步方法
class SellTicket03 implements Runnable {
    private int ticketNum = 100;//一个对象,不用static
    private boolean loop = true;

    //1. public synchronized void sell() {} 就是一个同步方法
    //2. 这时锁在 this 对象
    //3. 也可以在代码块上写 synchronize =>同步代码块, 互斥锁还是在 this 对象
    public synchronized void sell() {
        if (ticketNum <= 0) {
            System.out.println("售票结束");
            loop = false;
            return;
        }
        //休眠50ms
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() +
                " 售出了一张票,还剩 " + --ticketNum);

    }
    @Override
    public void run() {
        while  (loop) {
            sell();
        }
    }
}

//同步代码块
class SellTicket04 implements Runnable {
    private int ticketNum = 100;//一个对象,不用static
    private boolean loop = true;
    Object object = new Object();
    public void sell() {
        synchronized (/*this或*/object){
            //synchronized (对象)表示对象锁,可以是当前对象this,也可以是其他任意对象
            //如果线程进入,则得到当前对象锁,那么别的线程在该类所有对象上的任何操作都不能进行
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                loop = false;
                return;
            }
            //休眠50ms
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() +
                    " 售出了一张票,还剩 " + --ticketNum);
        }
    }
    @Override
    public void run() {
        while  (loop) {
            sell();
        }
    }
}

class SellTicket05 extends Thread {
    public void m3() {
        //当前为继承Thread类的方式创建线程
        //创建的方式是:
        //new SellTicket05().start();
        //new SellTicket05().start();
        //每次都用不同的对象创建线程、执行相应方法
        //若此时任用同步代码块锁this对象,每个对象的this都为不同对象
        //因此这时用当前对象锁多个线程争夺的不是同一把锁,不能起到作用
        //可以用类锁
        synchronized (this) {
            System.out.println("m3");
        }
    }
}

//类锁
class SellTicket06 extends Thread {
    //同步方法(静态的)的锁为当前类本身
    //此时在同一时刻,只允许一个当前类(通过类名调用静态成员)或者当前类的对象进行操作
    
    //m1() 锁是加在 SellTicket03.class
    public synchronized static void m1() {
    }
    //如果在静态方法中,实现一个同步代码块
    public static void m2() {
        synchronized (SellTicket06.class) {
            System.out.println("m2");
        }
    }
}

在这里插入图片描述

14.6.5 死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。死锁非常危险,尽量避免

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");
                }
            }
    }
}

14.6.6 释放锁

以下操作会释放锁:

  1. 当前线程的同步方法、同步代码块执行结束
    1. 案例:上厕所,完事出来
  2. 当前线程在同步代码块、同步方法中遇到break, return。
    1. 案例:没有正常的完事,经理叫他修改bug,不得已出来
  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
    1. 案例:没有正常的完事,发现忘带纸,不得已出来
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
    1. 案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

以下操作不会释放锁:

  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方 法暂停当前线程的执行,不会释放锁
    1. 案例:上厕所,太困了,在坑位上眯了一会
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起, 该线程不会释放锁。 提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

14.7 练习

  1. /**
     * 在main方法中启动两个线程
     * 第一个线程循环随机打印100以内的整数
     * 直到第二个线程从键盘读取到了“Q”命令
     */
    public class ThreadExer01 {
        public static void main(String[] args) {
            AThread aThread = new AThread();
            BThread bThread = new BThread(aThread);
            new Thread(aThread).start();
            new Thread(bThread).start();
        }
    }
    
    class AThread implements Runnable {
        private boolean loop = true;
    
        public void setLoop(boolean loop) {
            this.loop = loop;
        }
    
        @Override
        public void run() {
            while (loop) {
                System.out.println((int)(Math.random()*100) + 1);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("A线程退出...");
        }
    }
    
    class BThread implements Runnable {
        private AThread aThread;
        private Scanner sc = new Scanner(System.in);
    
        public BThread(AThread aThread) {
            this.aThread = aThread;
        }
    
        @Override
        public void run() {
            while (true){//接受用户输入
                System.out.println("请输入你的指令(Q退出):");
                char key = sc.next().toUpperCase().charAt(0);
                if (key == 'Q') {
                    aThread.setLoop(false);
                    System.out.println("B线程退出...");
                    break;
                }
            }
        }
    }
    
  2. /**
     * 有两个用户分别从同一张卡上取钱(总额:10000)
     * 每次都取1000,当余额不足时,就不能取款了
     * 不能出现超取现象=》线程同步问题
     */
    public class ThreadExer02 {
        public static void main(String[] args) {
            Count count = new Count();
            Usr usr1 = new Usr(count);
            Usr usr2 = new Usr(count);
            new Thread(usr1).start();
            new Thread(usr2).start();
        }
    }
    class Count {
        private static int balance = 10000;
    
        public int getBalance() {
            return balance;
        }
    
        public void setBalance(int balance) {
            Count.balance = balance;
        }
        public void withdrawMoney(int amount) {
            Count.balance -= amount;
        }
    }
    
    class Usr implements Runnable {
        private final Count count;
    
        public Usr(Count count) {
            this.count = count;
        }
    
        @Override
        public void run() {
            while (true){
                synchronized (count) {
                    if (count.getBalance() <= 0){
                        System.out.println("余额为0," + Thread.currentThread().getName() + "停止取钱...");
                        break;
                    }
                    count.withdrawMoney(1000);
                    System.out.println(Thread.currentThread().getName() + "取出了1000,余额还有:" + count.getBalance());
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值