什么是线程
要了解什么是线程,得先知道什么是程序。
程序:为完成特定任务,用某种语言编写的一组指令的集合。
例如,QQ,Steam,亦或者java写的helloword。
这些都是程序
了解了程序,还得清楚什么是进程
进程:就是指运行中的程序,比如双击QQ,那么就会启动了一个进程,操作系统就会为其分配该进程所需的内存空间,当又点开了一个程序,比如谷歌浏览器,那么就会又启动一个进程。
进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身产生,运行中,消亡的过程。双击启动QQ就是启动了一个进程,打开任务管理器,选中结束任务,就是进程消亡了
了解了进程,再来了解下线程
线程:线程是由进程创建的,是进程的一个实体。一个进程可以有多个线程
例如:迅雷就是一个程序,打开迅雷,就是启动一个进程,而你用迅雷下载一个资源,比如下载<流浪地球>这部电影,那么这个下载任务就是一个线程,你还可以同时下载多个电影,创建多个下载任务同时下载,这就是多线程。
单线程和多线程的概念
单线程:同一时刻,只运行执行一个线程。例如有些下载器,只运行同时执行一个下载任务,必须排队下载,(Steam的下载)
多线程:同一时刻,可以执行多个线程,比如微信可以同时打开多个聊天窗口,迅雷可以同时下载多个资源
并发与并行的概念
并发:同一时刻,多个任务交替执行,但是因为切换的速度很快,造成一种是同时执行的错觉。
例如单核CPU实现的多任务就是并发。并发就是一个人操作2个键盘,但是因为切换的速度很快,看起来像是同时操作的
并行:同一时刻,多个任务同时执行,多核CPU可以实现并行
例如:两个人操作两个键盘,他们是同时打字的
但是在电脑中有时候是并发和并行同时存在的,例如两个人在同时打字,但是其中一个人打到一半去操作另一个键盘了,敲了一会又回来了
线程的基本使用
在Java中创建线程有两个办法
1.实现Runnable接口,重写run方法
2.继承Thread类,重写run方法
继承Thread类创建线程
注意Thread类其实也是实现了Runnable接口,Thread重写的run方法也是Runnable接口的
案例演示使用线程
有以下题目,使用线程完成
有一个Cat类,要求让Cat的对象,每过一秒打印输出一个“喵喵”
思路:定义Cat类,继承Thread类,重写run方法,将逻辑写在run方法里。Thread有一个sleep方法可以睡眠
然后使用Thread类继承过来的start方法,会自动触发run方法
public class test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
}
}
class Cat extends Thread{
@Override
public void run() {
while (true) {
System.out.println("喵喵");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
追加要求:当输出8次喵喵后,停止运行
思路,创建一个int变量,每输出一次就+1,当等于8时就break退出
public class test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
}
}
class Cat extends Thread{
@Override
public void run() {
int nums = 0;
while (true) {
System.out.println("喵喵"+(++nums));
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nums == 8){
break;
}
}
}
}
注意,main方法本身也是一个线程,相当于主线程,上面就是在主线程里又开了一个线程,但是并不会造成主线程的堵塞,比如cat.start();下面还有代码的话,不会等cat.start();执行完再回来执行,而是启动了Thread线程后并发或者并行的执行。
举例说明:
public class test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
for (int i = 0; i < 10; i++) {
System.out.println("main"+i);
}
}
}
class Cat extends Thread{
@Override
public void run() {
int nums = 0;
while (true) {
System.out.println("喵喵"+(++nums));
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nums == 8){
break;
}
}
}
}
可以看到是交替执行的,单核的CPU就是并发,多核的就是并行
使用JConsole监控线程执行情况,且画出示意图
在执行的时候,打开Terminal输入JConsole,可以打开监控面板,查看线程执行情况。
注意点:当main主线程执行完毕后,这个进程并不会立刻退出,而是等所有线程都执行完毕后,才会退出。
就像一个仓库管理员让他手下去关东边的灯,他自己关西边的灯,得等两个人都关了后,才离开。而不是一个人关掉后马上离开
start0方法
为什么在上面不在main方法直接调用run方法来完成呢?
这是因为run方法只是一个普通的方法,并不是创建了一个线程。如果直接在main方法中调用run方法的话,可以在JConsole监控中只能看到main线程,没有Thread线程。
而在main线程中就会造创堵塞,调用run方法下面的语句,必须得等run方法执行完毕后,才会执行。这就是没有达到多线程的效果,只是串行,没有并发或者并行。
只有使用start方法才能真正的去创建线程。
start方法底层是调用start0方法(本地方法),由start0方法调用run方法。而start0方法是JVM调动的。
当start0方法调用run方法,并不是马上执行的,而是取决于CPU。CPU会根据内存资源IO资源等等,判断让不让run方法现在执行
实现Runnable接口创建线程
上面说了继承Thread类创建线程,但是由于Java只能单继承的特性,在实际的开发中可能没办法再去继承Thread类。这时旧需要实现Runnable接口来创建线程。
但是因为如果是继承Runnable接口的话,由于Runnable接口中只有一个run方法可以重写,没有start方法来触发run方法,无法创建线程。
为了解决这个问题就需要将创建线程的对象放入倒Thread的实例中,然后直接使用Thread对象调用start方法。因为多态绑定的特性,底层还是跑的实现Runnable接口的类实例。
代码演示:
例如每隔1秒输出一个“你好”
public class test {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{
@Override
public void run() {
int nums = 0;
while (true){
System.out.println("你好"+(++nums)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nums == 10 ){
break;
}
}
}
}
下面简单了解一下,Thread是怎么将dog的run方法调用起来的
当dog传入Thread时,Thread的底层使用了线程代理模式
线程代理模式,简单模拟
class ThreadProxy implements Runnable{
private Runnable target;
public ThreadProxy(Runnable target) {
this.target = target;//将传入的对象赋给target
}
@Override
public void run() {
if (target!=null){//如果传入的对象不为空
target.run();//调用传入对象的run方法
}
}
public void start(){
start0();//调用本类的start方法
}
public void start0(){
run();//调用本类的run方法
}
}
ThreadProxy类可以当成Thread类理解,当然这里只是简单演示而已。
可以看到ThreadProxy里有一个Runnable类型的变量target
构造器会把传入的对象给到target
然后ThreadProxy的run方法会判断target是不是空,如果不是就调用target的run方法,也就是传入对象的run方法
而在main方法创建ThreadProxy实例将要创建线程的类传入时,调用start,而start又会调用start0,start0就会调用ThreadProxy的run方法,最终调用传入对象的run方法
多个子线程案例
要求从main线程创建两个子线程
第一个子线程:每秒输出“你好”,输出10次退出
第二个子线程:每秒输出“hello”,输出20次退出
public class test {
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{
@Override
public void run() {
int nums = 0;
while (true){
System.out.println("你好"+(++nums)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nums == 10 ){
break;
}
}
}
}
class T2 implements Runnable{
@Override
public void run() {
int nums = 0;
while (true){
System.out.println("hello"+(++nums)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nums == 20 ){
break;
}
}
}
}
继承Thread和实现Runnable的区别
- 从java的设计上来看,继承Thread或者实现Runnable接口来创建线程本质上没有区别
- 但是由于java只能单继承的特性,实现Runnable接口方式更加适合多个线程共享一个资源的情况,避免了单继承的限制。
线程控制
多线程售票问题
有这么一道题目,要求三个线程同时售卖票,当票<=0时就停止售卖,分别用继承Thread和实现Runnable接口两种方式实现
继承Thread
public class test3 {
public static void main(String[] args) {
ticket t1 = new ticket();
ticket t2 = new ticket();
ticket t3 = new ticket();
t1.start();
t2.start();
t3.start();
}
}
class ticket extends Thread{
public static int tickets = 100;//初始剩余票数
@Override
public void run() {
while (true) {
if (tickets <= 0) {//当剩余票少于等于0时就停止售票
System.out.println("停止售票");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
}
}
}
运行结果可以看到超卖了2张。
实现Runnable接口
public class test3 {
public static void main(String[] args) {
ticket t1 = new ticket();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t1);
Thread thread3 = new Thread(t1);
thread1.start();
thread2.start();
thread3.start();
}
}
class ticket implements Runnable{
public int tickets = 100;//初始剩余票数
@Override
public void run() {
while (true) {
if (tickets <= 0) {//当剩余票少于等于0时就停止售票
System.out.println("停止售票");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
}
}
}
可以看到上面两种方法也超卖了2张,这是因为3个子线程同时进入while循环,当还剩余1张票的时候,3个子线程都能通过if语句,但是当第一个子线程再售出1张票之后,第2个和第3个子线程由于已经通过了if语句,所以也会进行售票,从而导致超卖2张。
下面随着继续深入学习来解决超卖问题
线程终止
假设有一个子线程一直在不停的跑,我们需要在main线程让这个子线程停下来。
从上面的案例可以分析,假设这个子线程是通过whlie循环在不停的跑,而while能不停的跑是因为放进去的boolean值一直是true。
所以如果想让子线程停下来,就得把true改成false。如果要改子线程的布尔值,就得提供一个方法让main线程去操作。
代码实现
public class test4 {
public static void main(String[] args) throws InterruptedException {
RR rr = new RR();
rr.start();
Thread.sleep(1000);
rr.setLoop(false);
}
}
class RR extends Thread{
boolean loop = true;
int nums = 0;
@Override
public void run() {
while(loop){
System.out.println("滴滴"+(++nums));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
控制线程的常用方法
控制线程的常用方法一般都是Thread的方法
setName:设置线程的名称,使之与参数name相同
getName:返回该线程的名称
start:让线程开始执行,Java虚拟机底层调用该线程的start0方法
run:调用线程对象的run方法
setPriority:设置线程的优先级
getPriority:获取线程的优先级
sleep:在指定毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt:中断线程(一般用来中断休眠,唤醒线程继续执行)
常用方法注意点
- start方法的底层会创建新的线程,调用run,但是run方法只是一个普通的方法,不会启动新线程
- 线程优先级的范围:MAX_Priority 10 MIN_Priority 1 NORM_Priority 5
- sleep方法是线程的静态方法,让当前线程休眠
使用演示:
public class test {
public static void main(String[] args) throws Exception{
Cat cat = new Cat();
cat.start();
for (int i = 10; i > 0; i--) {
Thread.sleep(1000);
System.out.println("唤醒倒计时"+i);
}
cat.interrupt();//当输出10次i后,发出interrupted中断信号
}
}
class Cat extends Thread{
@Override
public void run() {
int nums = 0;
Thread.currentThread().setName("CatThread");//设置线程名字
Thread.currentThread().setPriority(MIN_PRIORITY);//设置线程优先级
while (true) {
System.out.println("喵喵"+(++nums));
try {
sleep(50000);
} catch (InterruptedException e) {
System.out.println("中断了睡眠");//当接收到interrupted要求中断时,就catch这个语句,然后重新进入循环
System.out.println(Thread.currentThread().getName());//输出线程的名字
System.out.println(Thread.currentThread().getPriority());//输出线程优先级
}
if (nums == 50){
break;
}
}
}
}
线程插队
除了上面的方法还有两个方法比较重要
yield:线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功
join:线程的插队,插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务,再回来执行被插队的线程任务
案例演示:
在main主线程执行数包子1~50,同时创建子线程数包子1 ~ 70.
public class test2 {
public static void main(String[] args) throws InterruptedException {
steamed s = new steamed();
s.start();
int num = 0;
while (true){
if (num == 10){
s.join();//当主线程数到10个包子的时候,就让子线程插队先执行掉
}
System.out.println("主线程数包子"+(++num));
if (num == 50){
break;
}
}
}
}
class steamed extends Thread{
int nums = 0;
@Override
public void run() {
while (true){
System.out.println("子线程数包子"+(++nums));
if (nums == 70){
break;
}
}
}
}
线程插队练习
要求:
主线程每隔1秒输出 hi ,输出10次
当输出到hi 5时,启动一个子线程(实现Runnable),每隔1秒输出hello,输出10次hello后退出。
子线程推出后,主线程再继续执行输出
public class test2 {
public static void main(String[] args) throws InterruptedException {
steamed s = new steamed();
Thread thread = new Thread(s);
int num = 0;
while (true){
if (num == 5) {
thread.start();
thread.join();
}
if (num == 10){
break;
}
System.out.println("hi"+(++num));
Thread.sleep(1000);
}
}
}
class steamed implements Runnable{
int nums = 0;
@Override
public void run() {
while (true){
System.out.println("hello"+(++nums));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nums == 10){
System.out.println("子线程结束");
break;
}
}
}
}
用户线程和守护线程
用户线程:也叫工作线程,结束方式是当线程的任务执行完或者通知结束
守护线程:一般是为用户线程服务的,结束方式是当所有的用户线程结束后,守护线程就会自动结束
常见的守护线程:垃圾回收机制
例如在main方法每秒输出一个hi,共输出10次。然后创建一个子线程每秒输出一个hello,共输出50次
按照正常的情况,肯定是main线程先结束,子线程线程继续运行。但是只要将子线程设置成一个守护线程,那么只要main线程结束,子线程就会结束
设置守护线程。Thread.setDaemon(true);
案例演示
public class test5 {
public static void main(String[] args) throws InterruptedException {
guard guard = new guard();
guard.setDaemon(true);
guard.start();
for (int i = 1; i < 10; i++) {
Thread.sleep(1000);
System.out.println("hi"+(i));
}
}
}
class guard extends Thread{
int num = 0;
@Override
public void run() {
while (true){
if (num == 50){
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello"+(++num));
}
}
}
线程的生命周期
一个线程在创建-启动-消亡,会有好几种状态
JDK中用Thread.State枚举,表示了线程的几种状态
NEW:尚未启动的线程
RUNNABLE:在Java虚拟机中执行的线程,此状态还细分为两种Ready,准备执行但还没执行,Running正在执行
BLOCKED:被阻塞等待监视器锁定的线程
WATING:正在等待另一个线程执行特定动作的线程
TIMED_WATING:等待另一个线程执行动作达到指定时间的线程
TERMINATED:已退出的线程
线程的状态会因为各种方法对线程的操作,改变线程的状态
图解
代码演示查看线程状态
Thread.getState(),可以获取线程状态
public class test5 {
public static void main(String[] args) throws InterruptedException {
guard guard = new guard();
System.out.println("状态="+guard.getState());
guard.start();
while (guard.getState() != Thread.State.TERMINATED){
Thread.sleep(1000);
System.out.println("状态="+guard.getState());
}
}
}
class guard extends Thread{
int num = 0;
@Override
public void run() {
while (true){
if (num == 10){
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello"+(++num));
}
}
}
状态=NEW
状态=TIMED_WAITING
hello1
状态=TIMED_WAITING
hello2
状态=TIMED_WAITING
hello3
状态=RUNNABLE
hello4
hello5
状态=RUNNABLE
状态=RUNNABLE
hello6
hello7
状态=RUNNABLE
状态=TIMED_WAITING
hello8
hello9
状态=RUNNABLE
状态=RUNNABLE
hello10
状态=TERMINATED
线程同步
线程同步机制:
在多线程开发中,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多只能有一个线程访问,以保证数据的完整性
简单的说:线程同步就是一个线程在对一个内存进行操作时,其他线程都无法对这个内存地址进行操作,直到该线程完成操作,其他线程才可以对该线程操作。
就像只有一个马桶的卫生间,有很多人都要上厕所,但是必须一个个排队,进去一个就锁门。别人进不去
synchronized
synchronized关键字就相当于这个锁,synchronized可以修饰代码块,也可以放在方法声明中:
synchronized(对象){
//需同步代码
}
public synchronized 返回类型 方法名 (){
//需同步代码
}
使用synchronized修饰的方法,在同一时间只运行一个线程进入,将其他线程堵塞在后面,一个线程操作完出来了,另一个才能进去。因此这个特性可以用来解决上面的多线程售票超卖的问题
synchronized解决多线程售票超卖问题
继承Thread类
public class test3 {
public static void main(String[] args) {
ticket t1 = new ticket();
ticket t2 = new ticket();
ticket t3 = new ticket();
t1.start();
t2.start();
t3.start();
}
}
class ticket extends Thread{
public static int tickets = 100;//初始剩余票数
public synchronized static void seep(){
if (tickets <= 0) {//当剩余票少于等于0时就停止售票
System.out.println("停止售票");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
}
@Override
public void run() {
while (tickets >0) {
seep();
}
}
}
需要将synchronized修饰的方法写成静态的不然就是同时打开3个seep方法在售票,依旧会超卖。因为继承Thread类创建了3个对象
如果是实现Runnable接口就只需要创建一个对象就能创建3个线程,就不用写成静态的
实现Runnable接口的
public class test3 {
public static void main(String[] args) {
ticket t1 = new ticket();
new Thread(t1).start();
new Thread(t1).start();
new Thread(t1).start();
}
}
class ticket implements Runnable{
public static int tickets = 100;//初始剩余票数
public synchronized void seep(){
if (tickets <= 0) {//当剩余票少于等于0时就停止售票
System.out.println("停止售票");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出一张票" + "剩余票:" + (--tickets));
}
@Override
public void run() {
while (tickets >0) {
seep();
}
}
}
互斥锁
上面用synchronized解决了多线程售票超卖的问题。通俗的说就是在不能多线程一起操作的地方给他加一把锁,只有拿到这个锁,才能进去操作,当一个线程拿到锁进去操作,剩下的线程就没有锁可以拿了,自然就无法进去。
如果是一个循环,那么一个线程拿到锁之后,进去操作完,就会返回来继续和其他线程抢锁
基本介绍
- Java语言中,引入了互斥锁的概念,用于保证共享数据操作的完整性
- 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任何同一时刻,只能有一个线程访问该对象。
- 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任何时刻只能有一个线程访问
- 同步的局限性:会导致程序的效率降低
- 同步方法(非静态)的锁可以是this,也可以是其他对象,但是必须是同一个对象,不然就相当于有很多把锁,那就会有很多线程一起进入
- 同步方法(静态的)锁,为当前类本身,用类名.class表示,因为静态比对象创建更前面,无法找到对象加锁
注意点:这个锁是加在对象或者类身上的,相当于一个管理员带着这把锁。只有找到管理员拿到他身上的锁,才能进去。当锁被一个线程拿走了,其他线程就没锁了。
如果是继承Thread类创建的线程,就得将同步方法变成静态的,因为在启动的时候是创建一个对象启动一次,如果不变成静态的,就相当于有多个管理员多把锁。相当于只有一个马桶但是有很多门。当然静态同步方法就得将锁放在类本身上
如果是实现Runnable接口创建的线程,同步方法不用变成静态,因为启动的时候只创建一个对象,所以同步方法是多个线程共享的,相当于只有一个管理员一个锁,锁可以放在对象身上。
细节:
同步方法如果没有使用static修饰,默认锁对象为this
如果方法使用static修饰,默认锁对象是当前类本身,类.class
注意上面说的锁是一种非公平锁,不会比较谁等待的时间久下一个就是谁拿到锁,也有可能一个线程拿到锁执行完回来后,又是它拿到锁
线程死锁
上面了解到了互斥锁,下面了解一下一种风险,死锁。
死锁:多个线程同时占用了对象的锁资源,但不肯想让,导致一直堵塞BLOCKED,形成死锁。
通俗的说就是
A线程手里有一把锁,但是想要继续执行,需要B线程手里的锁。
B线程手里也有一把锁,但是想要继续执行,需要A线程手里的锁。
代码演示:
public class test6 {
public static void main(String[] args) {
deadlock A = new deadlock(true);
deadlock B = new deadlock(false);
A.start();
B.start();
}
}
class deadlock extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean loop;
public deadlock(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
if (loop){
synchronized (o1){
System.out.println("A线程拿到o1,进入1");
synchronized (o2){
System.out.println("A线程拿到o2,进入2");
}
}
}else {
synchronized (o2){
System.out.println("B线程拿到o2,进入3");
synchronized (o1){
System.out.println("B线程拿到o1,进入4");
}
}
}
}
}
从上面代码可以分析,
当传入的是true时会先拿到o1锁,再去抢o2锁,才能执行完毕
反之当传入的是false时会先拿到o2锁,再去抢o1锁,才能执行完毕
只要两个线程执行run方法的时间是同时的就会出现死锁,o1锁被A拿着,B执行不了下面的,o2锁被B拿着,A执行不了下面的。就会一直卡着堵塞BLOCKED,形成死锁。在开发者要避免此种情况
释放锁
当一个线程抢到锁进入同步代码块操作时,此时锁是被该线程拿到的,别人拿不到,正常情况下,只有当该线程将语句执行完,才会释放锁,但是也有有其他情况会导致提前释放锁
以下情况会导致释放锁
- 线程将同步代码块执行完毕
- 当前线程在同步代码块中遇到break或者return
- 在执行同步代码块时遇到未处理的Error或者Exception,导致异常结束
- 在执行同步代码块时执行了线程对象的wait()方法,当前线程暂停,且释放锁
以下情况不会导致提前释放锁
- 同步代码块调用Thread.sleep()和Thread.yield()方法,不会释放锁
- 同步代码块时,其他线程调用了该线程的suspend()方法,将线程挂起
多线程练习
一:按照要求编程
1)在main线程中启动两个线程
2)第一个子线程循环随机打印100以内的整数
3)直到第二个子线程从键盘读取了"Q"命令,第一个子线程就停止
思路: random方法随机生成整数,Scanner键盘按Q就让第一个子线程停止
public class test7 {
public static void main(String[] args) {
A a = new A();
new Thread(a).start();
B b = new B();
new Thread(b).start();
}
}
class A implements Runnable{
static boolean loop = true;
public void setLoop(Boolean loop){
this.loop = loop;
}
@Override
public void run() {
while (loop){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Random().nextInt(100));//随机打印0~100的整数
}
}
}
class B implements Runnable {
@Override
public void run() {
while (new A().loop){
System.out.println("请输入字符:");
char i = new Scanner(System.in).next().toUpperCase().charAt(0);
if (i == 'Q'){
new A().setLoop(false);
break;
}
}
}
}
二:按照要求编程
1)有2个用户分别从同一个卡上取钱(总余额10000)
2)每次都取1000,当余额不足时,就不能取款了
3)不能出现超取现象
public class test8 {
public static void main(String[] args) {
ATM atm = new ATM();
new Thread(atm).start();
new Thread(atm).start();
new Thread(atm).start();
}
}
class ATM implements Runnable{
static int wallet = 10000;
public void withdrawMoney() throws InterruptedException {
synchronized (this){
if (wallet <= 0){
System.out.println("余额不足");
return;
}
Thread.sleep(500);
wallet-=1000;
System.out.println("线程"+Thread.currentThread().getName()+"取了1000,余额剩余"+wallet);
}
}
@Override
public void run() {
while (wallet > 0){
try {
withdrawMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}