它是一组用某种语言完成特定任务和编写的指令的集合。简单地说:这是我们写的代码。
进程- 过程是指操作中的程序。例如,当我们使用QQ时,我们启动了一个过程,操作系统将为过程分配内存空间。当我们使用快雷并启动另一个过程时,操作系统将为快雷分配新的内存空间。
- 这个过程是一个程序执行过程,或者是一个正在运行的程序。这是一个动态过程:它有自己的生成、存在和消亡过程
- 线程是由过程创建的,是过程的实体
- 一个过程可以有多个线程,如下图所示
- 单线程:同时,只允许执行一个线程。
- 多线程:多线程可以在同一时间执行,比如一个qq过程,多个聊天窗口可以同时打开,一个快雷过程可以同时下载多个文件。
- 并发:同时,多个任务交替执行,造成“似乎同时”的错觉。简单来说,单核cpu实现的多个任务是并发的。
- 并行:同时执行多个任务。多核cpu可以并行实现。
获取cpu的数量/核心数
package com.hspedu;public class CpuNum { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); ///获取当前计算机的cpu数量/核心数 int cpuNums = runtime.availableProcessors(); System.out.println(目前有cpu 个数=" + cpuNums); }}
线程基本上使用两种方法来创建线程
使用java中线程有两种方法。
1.继承Thread类,重写run方法
2.实现Runnable接口,重写Run方法
线程应用案例1-继承Threadad 类一个进程在运行程序时相当启动,一个main线程在进入main时打开,
1)请编写程序,每隔1秒打开一个线程。在控制台输出“喂食”。我是一只小猫。
2)改进上述问题:输出80次后,我是一只小猫,结束线程
3)使用JConsole 监控线程执行情况,并绘制程序示意图!
JConsole可直接输入控制台进行过程操作。
主线程已经挂断,但子线程仍在执行中,这不会导致应用程序的结束。说明: 当main线程启动子线程时 Thread-0, 主线程不会堵塞, 此时将继续执行(执行完成后不会执行) 交替执行主线程和子线程。
package com.hspedu.threaduse;/** * 通过继承演示 Thread 类创建线程 */public class Thread01 { public static void main(String[] args) throws InterruptedException { //创建Cat对象,可作为线程使用 Cat cat = new Cat(); // 读源码 /* (1) public synchronized void start() { start0(); } (2) //start0() 本地方法,JVM调用, 底层是c/c++实现 ///真正实现多线程效果, start0(), 而不是 run private native void start0(); */ cat.start();// 启动线程-> catrun方法最终将执行 //cat.run();//run方法是一种常见的方法, 如果没有真正启动线程,run方法将在向下执行之前完成。因此,为了真正实现多线程,应该使用start方法。 //说明: 当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方法如下/* @Override public void run() { if (target != null) { target.run(); } } */class Cat extends Thread { int times = 0; @Override public void run() {//重写run方法,写下自己的业务逻辑 while (true) { ////该线程每隔1秒。输出控制台 “喵喵, 我是小猫” System.out.println("喵喵, 我是小猫”“我是小猫” + (++times) + " 线程名=" + Thread.currentThread().getName()); ////让线程休息1秒钟 ctrl+alt+t try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(times == 80) { break;//当times 到80, 退出while, 这时,线程也会退出.. } } }}
start()方法调用start0()方法后,线程不一定会立即执行,只会将线程变成可操作状态。具体何时执行取决于CPU,由CPU统一调度。
线程应用案例2-Runnable实现 接口- java是单继承的,在某些情况下,一个类可能已经继承了一个父类,显然不可能通过继承thread类来创建线程。
- java设计师提供了另一种创建线程的方式,即通过实现Runnable接口创建线程
应用案例
请每隔1秒编写一次程序。在控制台输出“hi!输出10次后,自动退出。请使用实现Runnable接口。
底层采用设计模式[代理模式]=>实现Runnnable接口开发线程的代码模拟机制
package com.hspedu.threaduse;/** * 接口Runnnable通过实现 来开发线程 */public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); //dog.start(); startttartt不能在这里调用 ///创建了Thread对象,把 dog对象(实现Runnable),把Thread放进去 Thread thread = new Thread(dog); thread.start();// Tiger tiger = new Tiger();//实现了 Runnable // 1.// ThreadProxy threadProxy = new ThreadProxy(tiger); // 2.// threadProxy.start(); }}class Animal {}class Tiger extends Animal implements Runnable { // 6. @Override public void run() { System.out.println(”老虎尖叫..."); }}///线程代理类 , 极简主义的Threadclasss模拟 ThreadProxy implements Runnable 你可以把Proxy类作为{// ThreadProxy private Runnable target = null;//属性,类型是 Runnable // 5. @Override public void run() { if (target != null) { target.run();///动态绑定(运行类型Tiger) } } public ThreadProxy(Runnable target) { this.target = target; } // 3. public void start() { start0();///这种方法真正实现了多线程方法 } // 4. public void start0() { run(); }}class Dog implements Runnable { //通过实现Runnable接口,开发线程 int count = 0; @Override public void run() { //普通方法 while (true) { System.out.println(”小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName()); //休眠1秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 10) { break; } } }}
线程应用案例-多线程执行
请编写一个程序,创建两个线程,每1秒输出一个线程hello,world输出10次,退出, 每隔1秒输出一个线程”hi输出5次退出。
package com.hspedu.threaduse;/** * main线程启动两个子线程 */public class Thread03 { public static void main(String[] args) { T1 t1 = new T1(); T2 t2 = new T2(); Thread thread1 = new Thread(t1); Thread thread2 = new Thread(t2); thread1.start();//启动第一个线程 thread2.start();//启动第二个线程 }}class T1 implements Runnable { int count = 0; @Override public void run() { while (true) { ///每隔1秒输出一次 “hello,world”,输出10次 System.out.println("hello,world " + (++count)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(count == 60) { break; } } }}class T2 implements Runnable { int count = 0; @Override public void run() { ///每隔1秒输出一次 “hi”,输出5次 while (true) { System.out.println("hi " + (++count)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(count == 50) { break; } } }}
如何理解线程?
继承Thread vs Runnnablele实现 的区别
- 从java的设计来看,通过继承thread或实现Runnable接口来创建线程本质上没有区别。从jdk帮助文档中,我们可以看到thread类本身实现了Runnable接口。
- 实现Runnable接口更适合多线程共享一个资源,避免单继承的限制。建议使用Runnable。
- [售票系统],编程模拟三个售票窗口的100张售票,分别使用继承 Thread和实现 Runnable分析有什么问题? 都会出现超卖的问题。
package com.hspedu.ticket;/** * 使用多线程,模拟三个窗口同时售票100张 */public class SellTicket { public static void main(String[] args) { /////////测试/// Selticket01 selticket01 = new Selticket01();// Selticket01 selticket02 = new Selticket01();// Selticket01 selticket03 = new Selticket01();//// ///我们会在这里超卖..// selticket01.start();/////开始售票线程////// selticket02.start();/////开始售票线程////// selticket03.start();///开始售票线程 System.out.println(=====================)); Selticket02 selticket02 = new Selticket02(); new Thread(selticket02).start();//第一行-窗口 new Thread(sellTicket02).start();//第二行-窗口 new Thread(sellticket02).start();//第三行-窗口 }}///使用Threadclass Selticket01 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)); } }}///实现接口方式classs Selticket02 implements Runnable { private 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));//1 - 0 - -1 - -2 } }}
线程终止的基本说明
- 任务完成后,线程将自动退出。
- 也可以通过使用变量控制run退出来停止线程,即通知。
要求:启动线程t,要求在main线程中停止线程t,请编程实现。
package com.hspedu.exit_;public class ThreadExit_ { public static void main(String[] args) throws InterruptedException { T t1 = new T(); t1.start(); // 如果希望 main 线程去控制 t1 线程终止, 必须修改 loop // 让 t1 退出 run 从而终止方法 t1 线程 -> 通知方式 // 让主线休眠 10 秒,再通知 t1 线程退出 System.out.println(”main线程休眠10s..."); Thread.sleep(10 * 1000); t1.setLoop(false); }}class T extends Thread { private int count = 0; // 设置控制变量 private boolean loop = true; @Override public void run() { while (loop) { try { Thread.sleep(50);// 让当前线程休眠50ms } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T 运行中..." + (++count)); } } public void setLoop(boolean loop) { this.loop = loop; }}
第一组常用的线程方法
- setName A ///设置与参数name相同的线程名称
- getName ////返回该线程的名称
- startM //使线程开始执行;Java虚拟机底层调用该线程的start0方
- run //调用线程对象 run方法;
- setPriority ////改变线程的优先级
- getPriority ////获取线程的优先级
- sleep ////让当前正在执行的线程在指定的毫秒内休眠(暂停执行)
- interrupt ///中断线程
- start底层将创建新的线程,调用run, run 它是一种简单的调用方法,不会启动新线程。
- 范围为线程优先级。
- interrupt,中断线程,但并没有真正结束线程。因此,它通常用于中断正在休眠的线程。
- sleep:线程的静态方法使当前线程休眠。
package com.hspedu.method;public class ThreadMethod010 { public static void main(String[] args) throws InterruptedException { ///测试相关方法 T t = new T(); t.setName("timerring"); t.setPriority(Thread.MIN_PRIORITY);//1 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());//1 t.interrupt();//当执行到这里时,就会中断 休眠t线程. }}class T extends Thread { // 自定义线程类 @Override public void 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);//20秒 } catch (InterruptedException e) { // 当该线程执行到interupt时 方法时,会catch 一个 异常, 您可以添加自己的业务代码 // InterruptedException 捕获中断异常. System.out.println(Thread.currentThread().getName() + "被 interupt); } } }}
第二组常用方法
- yield:线程礼让。让cpu,让其他线程执行,但礼让时间不确定,所以不一定礼让成功
- join:插队线程。一旦插队线程成功,插入线程的所有任务都必须首先执行。
案例:main线程创建子线程,每1s输出hello,输出20次,主线程每1秒, 输出hi,输出20次。要求:同时执行两个线程,当主线程输出5次时,让子线程完成运行,主线程继续运行。
package com.hspedu.method;public class ThreadMethod02 { 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();// 相当于让T2 首先执行线程 Thread.yield();//礼让,不一定成功. System.out.println(()线程(老板) 吃完了 主线程(弟弟) 接着吃.."); } } }}class T2 extends Thread { @Override public void run() { for (int i = 1; i <= 20; i++) { try { Thread.sleep(1000);//休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(子线程(老板) 吃了 " + i + " 包子"); } }}
课堂练习
- 主线程每隔1秒输出hi,共10次
- 当输出到hi5时,启动一个子线程(要求Runnable),每1秒输出hello,等待该线程输出10次 在hello之后,退出
- 主线程继续输出hi,直到主线程退出.
- 如图所示,完成代码实际上是线程插队
package com.hspedu.method;public class ThreadMethodExercise { public static void main(String[] args) throws InterruptedException { Thread t3 = new Thread(new T3());///创建子线程 for (int i = 1; i <= 10; i++) { System.out.println("hi " + i); if(i == 5) {//说明主线程输出5次 hi t3.start();///启动子线程 输出 hello... t3.join();//立即将T3子线程,插入main线程,让T3先执行 } Thread.sleep(1000);///输出一次 hi, 让main线程也休眠1s } }}class T3 implements Runnable { private int count = 0; @Override public void run() { while (true) { System.out.println("hello " + (++count)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 10) { break; } } }}
用户线程和守护线程
- 用户线程: 当线程任务完成或通知结束时,也称为工作线程。
- 守护线程: 一般为工作线程服务,当所有用户线程结束时,保护线程自动结束。
- 常见的守护线程: 垃圾回收机制。
接下来,我们将测试如何将一个线程设置为守护线程。
只需要将 myDaemonThread.setDaemon(true);
设置为 true
即可。
package com.hspedu.method;public class ThreadMethod0 { public static void main(String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread(); //如果我们想在main线程结束后,自动结束子线程,只需将子线程设置为守护线程 myDaemonThread.setDaemon(true); myDaemonThread.start(); for( int i = 1; i <= 10; i++) {//main线程 System.out.println("工作..."); Thread.sleep(1000); } }}class MyDaemonThread extends Thread { public void run() { for (; ; ) {///无限循环 try { Thread.sleep(1000);///休眠1000毫秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(“聊天”); } }}
生命周期线程的几种状态
JDK Thread中用.State 枚举表示线程的几种状态
线程状态转换图!!!有书说有七种状态,其实就是把Runnnable状态中的Ready和Running分开。是否运行取决于核心状态的调度。
package com.hspedu.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(500); } 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; } }}
Synchronized线程同步机制
- 在多线程编程中,不允许多线程同时访问一些敏感数据。此时,使用同步访问技术,以确保数据在任何时候都有一个线程访问,以确保数据的完整性。
- 也可以理解:线程同步,即当一个线程操作内存时,其他线程不能操作内存地址,直到线程完成,其他线程才能操作内存地址。
synchronized (对象){ // 只有获得对象的锁,才能操作同步代码/// 需要同步代码;}
方法二方法声明
synchronized 也可以放在方法声明中,表示整个方法-同步方法
public synchronized void m (String name){//需要同步的代码}
同步原理的分析
互斥锁的基本介绍
- 在Java语言中,引入了对象互斥锁的概念,以确保共享数据操作的完整性。
- 每个对象对应一个可以称为“互斥锁”的标记,以确保在任何时候只能有一个线程访问对象。
- 关键字synchronized联系对象的互斥锁。当一个对象用synchronized修改时,表示对象只能在任何时候访问一个线程。
- 同步限制:降低程序执行效率。
- 同步方法**(非静态)锁可以是this或其他对象(要求是同一对象)。**
- 同步方法(静态)的锁是当前类本身。即类.class
package com.hspedu.syn;/** * 使用多线程,模拟三个窗口同时售票100张 */public class SellTicket { public static void main(String[] args) { /////////测试/// Selticket01 selticket01 = new Selticket01();// Selticket01 selticket02 = new Selticket01();// Selticket01 selticket03 = new Selticket01();//// ///我们会在这里超卖..// selticket01.start();/////启动售票线程//////// selticket02.start();/////启动售票线程//////// selticket03.start();/////启动售票线程//////// System.out.println(=====================));// Selticket02 selticket02 = new Selticket02();//// new Thread(selticket02).start();//第一线程-窗口// new Thread(selticket02).start();//第二线程-窗口// new Thread(selticket02).start();//第三行-窗口 ///测试一个 Selticket03 selticket03 = new Selticket03(); new Thread(selticket03).start();//第一行-窗口 new Thread(selticket03).start();//第二行-窗口 new Thread(selticket03).start();//第三行-窗口 }}//实现接口方式, 使用synchronized实现同步classss Selticket03 implements Runnable { private int ticketNum = 100;//让多个线程共享 ticketNum private boolean loop = true;///控制run方法的变量 Object object = new Object(); ///同步方法(静态)的锁是当前类本身 //1. public synchronized static void m1() {} 锁是加在 Selticket03.class 上 //2. 若在静态方法中,实现同步代码块. /* synchronized (Sellticket03.class) { System.out.println("m2"); } */ public synchronized static void m1() { } public static void m2() { synchronized (Sellticket03.class) { System.out.println("m2"); } } //1. public synchronized void sell() {} 是同步法 //2. 这时锁在 this对象 //3. 也可以写在代码块上 synchronize ,同步代码块, 互斥锁还是this对象? public /*synchronized*/ void sell() { //同步法, 在同一时刻, 执行sell方法只能有一个线程 synchronized (/*this*/ object) { if (ticketNum <= 0) { System.out.println(”售票结束..."); loop = false; return; } //休眠50毫秒, 模拟 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 卖一张票” + " 剩余票数= + (--ticketNum));//1 - 0 - -1 - -2 } } @Override public void run() { while (loop) { sell();///sell方法是一种共同步法 } }}///使用Thread//// new Sellticket01().start()// new Sellticket01().start(); 对象不一样,不锁M1()class Selticket01 extends Thread { private static int ticketNum = 100;//让多个线程共享 ticketNum // 下面的写作方法是无用的,因为每个对象都不一样,锁不住// public void m1() {// synchronized (this) {// System.out.println("hello");// }// } @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)); } }}///实现接口方式classs Selticket02 implements Runnable { private 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));//1 - 0 - -1 - -2 } }}
注意事项和细节
- 如果不使用static修改同步方法,则默认锁对象为thiss
- 如果该方法采用static修改,则默认锁定对象:当前类别.class
- 着陆步骤的实现:
- 首先需要分析锁定的代码
- 选择同步代码块或同步方法(同步范围越小,效率越高)
- 要求多线程的锁对象是相同的!
许多线程占用了对方的锁资源,但拒绝让步,导致死锁,在编程中必须避免死锁。
应用案例
package com.hspedu.syn;/** * 模拟线程死锁 */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();// 保证多线程,共享一个对象,这里使用staticccticc 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"); } } } }}
释放锁下面的操作释放锁
- 执行当前线程的同步方法和同步代码块。
- 当前线程在同步代码块和同步方法中遇到break、return。
- 未处理的Error或Exception出现在当前线程的同步代码块和同步方法中,导致异常结束。
- 线程对象的wait()方法在同步代码块和同步方法中执行,当前线程暂停并释放锁。
- 当线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()暂停执行当前线程的方法不会释放锁
- 当线程执行同步代码块时,其他线程调用该线程的suspend()方法悬挂该线程,该线程不会释放锁。提示:尽量避免使用suspend()和resume()来控制线程,不再推荐使用该方法
- 编程题(1)在main方法中启动两个线程(2)在第一个线程循环中随机打印100以内的整数(3),直到第二个线程从键盘读取“Q”命令。
package com.hspedu.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(); }}///创建A线程classs A extends Thread { private boolean loop = true; @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线程退出..."); } public void setLoop(boolean loop) {//可以修改loop变量 this.loop = loop; }}//直到第二个线程从键盘读取”Q命令class B extends Thread { private A a; private Scanner scanner = new Scanner(System.in); public B(A a) {//构造器,A类对象直接传输 this.a = a; } @Override public void run() { while (true) { ////收到用户的输入 System.out.println(”请输入您的指令(Q)表示退出:"); char key = scanner.next().toUpperCase().charAt(0); if(key == 'Q') { /////以通知的方式结束a线程 a.setLoop(false); System.out.println(b线程退出."); break; } } }}
- 编程题(1)有两个用户分别从同一张卡上取钱(总额:1万)(2)每次取1000,余额不足时不能取钱(3)不能超取=线程同步.
package com.hspedu.homework;public class Homework02 { public static void main(String[] args) { T t = new T(); Thread thread1 = new Thread(t); thread1.setName("t1"); Thread thread2 = new Thread(t); thread2.setName("t2"); thread1.start(); thread2.start(); }}//1.因为它涉及多个线程共享资源,因此,我们使用实现Runnable的方法//2. 每次取出 1000class T implements Runnable { private int money = 10000; @Override public void run() { while (true) { //解读 //1. 这里使用 synchronized 线程同步已经实现 //2. 这里执行多个线程时,就会去争夺 对象锁this //3. 哪个线程争夺到(获取)对象锁this,就执行 synchronized 代码块, 执行完后,会释放对象锁this //4. 争夺不到对象锁this,就blocked ,准备继续竞争 //5. this对象锁是非公平锁. synchronized (this) {// ///判断余额是否足够 if (money < 1000) { System.out.println(“余额不足”); break; } money -= 1000; System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=“” + money); } //休眠1s try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}