一、线程介绍
- 程序:是为完成特定任务,用某种语言编写的一组指令的集合。简单地说,就是我们写的代码
- 进程:
- 进程是指运行中的程序,比如我们使用qq,就启动了一个进程。操作系统会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
- 进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身的产生、存在和消亡的过程
- 线程:
- 线程是由进程创建(比如我们启动了迅雷,它就是一个进程。我们又想通过迅雷下载文件,那么这个下载任务就是一个线程),是进程的一个实体。一个线程还可以创建另一个线程。不仅main线程(主线程)可以开启一个子线程,子线程也可以开启一个子线程
- 一个进程可以拥有多个线程(比如迅雷在同时下载多个文件,每个下载任务就是一个线程)
- 单线程:同一时刻,只允许执行一个线程
- 多线程:同一时刻,可以执行多个线程。比如:一个qq进程,可以同时打开多个聊天窗口。一个迅雷进程,可以同时下载多个文件
- 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核CPU实现的多任务就是并发(比如,一个人在同时做两件事,但是他只有一个大脑,并不能真的同时做两件事。他只能在两个任务之间来回切换,一会儿做做这个,一会儿做做那个。由于切换的速度极快,于是造成了一种在同时做两件事的错觉)
- 并行:同一时刻,多个任务同时执行。多核CPU可以实现并行(比如,有多个人,在同一时间,每个人都在做自己的事)。并发和并行是可以同时出现的,比如同一时间有很多人,每个人都在做自己的事情,而每个人又在来回切换地做多个事
- 获取当前电脑CPU个数地代码:
package com.study;public class CpuNum {public static void main(String[] args) {Runtime runtime = Runtime.getRuntime();//底层是单例模式//获取当前电脑地CPU数量int cpuNums = runtime.availableProcessors();System.out.println("当前有CPU个数="+cpuNums);} }
二、线程使用
- 创建线程的两种方式:
- 继承Thread类,重写run方法(Thread类里的run方法实际上是实现了Runnable接口的run方法)
- 实现Runnable接口,重写run方法
- 继承Thread创建线程:
- 线程应用案例1
public class Thread01 {public static void main(String[] args) {//创建一个Cat对象,可以当作线程使用了Cat cat = new Cat();cat.start();//调用了run方法} } //当一个类继承了Thread类,该类的对象就可以当作一个线程使用 class Cat extends Thread{@Overridepublic void run() {//重写run方法,写上自己的业务逻辑int times = 0;while (true) {if (times == 80){break;//当次数等于80时,就退出,这时线程也退出}//让线程每隔1秒。在控制台输出"喵喵,我是小猫咪"System.out.println("喵喵,我是小猫咪"+(++times));//让该线程休眠一秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}} }
- 当主线程(main线程)开启了一个子线程后,主线程不会阻塞,会继续往下执行。接下来,主线程和子线程会交替执行(单核CPU,并发)
//测试代码 package com.study;public class Thread01 {public static void main(String[] args) throws InterruptedException{//创建一个Cat对象,可以当作线程使用了Cat cat = new Cat();cat.start();//调用了run方法//说明:当main线程启动一个子线程后,主线程不会阻塞,会继续执行//这时,主线程和子线程会交替执行System.out.println("主线程继续执行"+Thread.currentThread().getName());for (int i = 0; i < 10; i++) {System.out.println("主线程 i=" + i);Thread.sleep(1000);}} } //当一个类继承了Thread类,该类的对象就可以当作一个线程使用 class Cat extends Thread{@Overridepublic void run() {//重写run方法,写上自己的业务逻辑int times = 0;while (true) {if (times == 80){break;//当次数等于80时,就退出,这时线程也退出}//让线程每隔1秒。在控制台输出"喵喵,我是小猫咪"System.out.println("喵喵,我是小猫咪"+(++times)+" 线程名="+Thread.currentThread().getName());//让该线程休眠一秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}} }
- 主线程的名字就叫main,主线程开启的第一个子线程的名字叫Thread-0,其它以此类推......获取当前线程名的方法Thread.currentThread().getName()
- 可以使用JConsole(在idea下面的终端输入jconsole)监控线程的执行情况(如图,这就是一个进程,左下是它拥有的线程,当main线程里的程序执行完,main就从左下角消失。但是Thread-0还没执行完,所以它还在继续输出“喵喵”)
- 当我们RunJava源程序的时候,就是开启了一个进程。然后它马上开启了main线程,这个主线程里又开启了Thread-0线程
- 只有当进程中所有线程都执行完毕,进程才会结束!!!为什么之前RunJava源程序,当main里的程序执行完毕,进程就退出了呢,因为进程中只有main这一个线程
- 为什么是通过start方法来开启子线程???(1)start方法最终会执行线程重写的run方法(2)run方法就是一个普通的方法,没有真正启动一个线程(3)start方法里的start0()方法才是真正开启线程的。start0()方法是一个本地方法(由JVM调用),native
- 如果我们想让两个程序同时(交替)执行,就让main线程开启一个子线程,然后一个程序写在main线程里,还有一个程序写在子线程的run方法里
- start()方法调用start0()方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度
- 线程应用案例1
- 使用Runnable接口创建线程说明:
- Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类的方法来创建线程显然不可能了
- java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
- 使用Runnable接口创建线程的应用案例:
package com.study;public class Thread02 {public static void main(String[] args) {T2 t2 = new T2();Thread thread = new Thread(t2);thread.start();//因为start方法是Thread类里定义的//所以只能由Thread类的对象,或它的子类的对象来调用//而此时T2没有继承Thread类} } class T2 implements Runnable{int times = 0;@Overridepublic void run() {while(true){//休眠一秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hi!" + (++times));if(times == 10){break;}}} }
- 使用Runnable接口创建线程,因为它没有继承Thread类,所以就不能用它来调用start方法。而start方法又是Thread类里的,所以我们创建一个Thread对象,并在构造器里,把自己编写的线程类的对象传进去,然后通过这个Thread类的对象来调用start方法。这个设计模式叫作静态代理
- 代码模拟静态代理:
//线程代理类,模拟了一个极简的Thread //它的一个有参构造器的参数是实现了Runnable接口的实现类的对象 //ThreadProxy类的对象去调用start:start→start0→run(里面调用的是实现类对象重写的run方法) class ThreadProxy implements Runnable{private Runnable target = null;public ThreadProxy(Runnable target){this.target = target;}@Overridepublic void run() {if (target!=null){target.run();}}public void start(){start0();//这个方法真正实现类开启线程}public void start0(){run();} }
- 多个子线程案例(请编写一个程序,创建两个线程,一个线程每隔一秒输出“Hello World”,输出10次退出,一个线程每隔1秒输出“hi”,输出5次退出):
package com.study;public class Thread03 {public static void main(String[] args) {T3 t3 = new T3();T4 t4 = new T4();Thread thread1 = new Thread(t3);Thread thread2 = new Thread(t4);thread1.start();thread2.start();System.out.println("线程继续");} } class T3 implements Runnable{@Overridepublic void run() {int times = 0;while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello,world"+(++times)+" 子线程名="+Thread.currentThread().getName());if (times == 10){break;}}} } class T4 implements Runnable{@Overridepublic void run() {int times = 0;while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hi" + (++times)+" 子线程名="+Thread.currentThread().getName());if (times == 5){break;}}} }
- 继承Thread和实现Runnable的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有任何区别。Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更适合多个线程共享一个资源的情况,并且避免了单继承的限制。什么是多个线程共享一个资源呢?看下图
- 使用多线程模拟三个窗口同时售票(继承Thread类,会出现问题,当剩余一张票时,被三个线程访问,但是它们都还没来得及做--操作):
package com.study;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();} } class SellTicket01 extends Thread{private static int ticketNum = 100;//三个线程同时售票,所以要共享它,用static@Overridepublic 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));}} }
- 使用多线程模拟三个窗口同时售票(实现Runnable接口,还是会出现超卖现象,跟上面一样):
package com.study;public class SellTicket_ {public static void main(String[] args) {SellTicket02 sellTicket02 = new SellTicket02();Thread thread1 = new Thread(sellTicket02);Thread thread2 = new Thread(sellTicket02);Thread thread3 = new Thread(sellTicket02);thread1.start();thread2.start();thread3.start();} } class SellTicket02 implements Runnable{private static int ticketNum = 100;//三个线程同时售票,所以要共享它,用static@Overridepublic 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));}} }
- 线程终止的基本说明:
- 当线程完成任务后,会自动退出
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知线程退出
- 线程终止的应用案例(启动一个线程t,要求在main线程中去停止线程,请编程实现):
package com.study;public class ThreadExit {public static void main(String[] args) {AThread st = new AThread();new Thread(st).start();//启动线程for (int i = 1; i <= 60; i++) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main线程运行中" + i);if (i==30){st.setLoop(false);}}} } class AThread implements Runnable{boolean loop = true;//步骤一:定义标记变量,默认为true@Overridepublic void run() {while(loop){//步骤二:将loop作为循环条件try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("AThread运行中...");}}//步骤三:提供公共的set方法,用于更新looppublic void setLoop(boolean loop) {this.loop = loop;} }
- 通知线程终止的步骤:(1)在自己定义的线程类里设置一个可以终止run方法的变量(2)并为该变量提供公共的set方法,让它在main线程里可以被使用(3)main线程里调用set方法来控制变量
三、线程方法
-
线程常用方法第一组:
setName 设置线程名称 getName 返回该线程的名称 start 使该线程开始执行,Java虚拟机底层调用该线程的start0方法 run 调用线程对象的run方法 setPriority 更改线程的优先级 getPriority 获取线程的优先级 sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) interrupt 中断线程 -
注意事项和细节:
-
线程优先级的范围(MAX_PRIORITY:10,MIN_PRIORITY:1,NORM_PRIORITY:5)
-
interrupt:中断线程,但并没有结束线程,所以一般用于中断正在休眠的线程(醒醒别睡了)
-
sleep:Thread类的静态方法,是当前线程休眠
-
-
常用方法的测试代码:
package com.study;public class ThreadMethod {public static void main(String[] args) {//测试相关的方法T t = new T();t.setName("jack");t.setPriority(Thread.MIN_PRIORITY);t.start();//启动子线程//主线程打印5个hi,然后就中断子线程的休眠for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hi "+i);}System.out.println("该线程的优先级是="+t.getPriority());t.interrupt();} } //我们自定义的线程类 class T extends Thread{@Overridepublic void run() {//吃完100个包子,本来想休息20秒再继续吃,但是它被打断了,于是又只能继续吃while (true) {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + i);}try {System.out.println(Thread.currentThread().getName() + " 休眠中~~~");Thread.sleep(20000);//1秒=1000毫秒} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + "被 interrupt了");}}} }
-
线程常用方法第二组:(1)yield:线程的礼让。让出CPU,让其它线程执行,但礼让的时间不确定,所以也不一定礼让成功(如果CPU或内存资源不是很紧张)(2)join:线程的插队。插队的线程一旦成功,则肯定先执行完插入线程的所有任务(可以这么理解:如果CPU在并发执行两个线程t1和t2,t1可以礼让或者强制让,比如到了某个时刻,轮到t1执行,但是t1想把执行机会让给t2,如果礼让,不一定让成功;如果强制让,一定能让成功。可以看成t2插队了)
-
例如,如果在main线程中有t1.join()。那么,会让t1线程先执行完毕,main线程才继续执行
-
线程插队的案例:main线程创建一个子线程,每隔一秒输出hello,输出20次,主线程每隔一秒输出hi,输出20次。要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续(main方法可以浅浅理解成线程里的run方法)
package com.study;public class ThreadMethod02 {public static void main(String[] args) throws InterruptedException {T1 t = new T1();Thread thread = new Thread(t);thread.start();for (int i = 0; i < 20; i++) {if (i==5){thread.join();}Thread.sleep(1000);System.out.println("hi");}} } class T1 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 20; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello");}} }
-
例如,(1)如果想让main线程礼让给t1线程,main里调用Thread.yield(),该方法是Thread类的静态方法(2)如果想让main线程强制让给t1线程,也就是t1插队,main里调用t1.join()
-
多线程插队练习:(1)主线程每隔一秒输出hi,一共输出10次(2)当输出到hi5时,启动一个子线程(要求实现Runnable),每隔一秒输出hello,等该线程输出10次hello后,退出(3)主线程继续输出hi,直到主线程退出
package com.study;public class ThreadMethodExercise {public static void main(String[] args) throws InterruptedException {for (int i = 1; i <= 10; i++) {System.out.println("hi" + i);Thread.sleep(1000);if (i==5){T5 t5 = new T5();Thread thread = new Thread(t5);thread.start();//如果不插队,会两个线程交替执行thread.join();}}System.out.println("主线程退出...");} } class T5 implements Runnable{@Overridepublic void run() {for (int i = 1; i <= 10; i++) {System.out.println("hello" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("子线程退出...");} }
- 用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程(工作线程)结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
- 我们如何将一个线程设置成守护线程呢?(如果我们希望main线程结束后,子线程就自动结束,只需将子线程设置为守护线程即可)
package com.study;public class ThreadMethod03 {public static void main(String[] args) throws InterruptedException{MyDaemonThread dt = new MyDaemonThread();//将dt设置成守护线程,当所有线程结束后(包括main线程),dt也就自动结束dt.setDaemon(true);dt.start();for (int i = 1; i <= 100; i++) {Thread.sleep(50);System.out.println("宝强辛苦工作----------------------"+i);}} } class MyDaemonThread extends Thread{@Overridepublic void run() {for (;;) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("宋哲与马蓉快乐聊天,哈哈哈~~~");}} }
四、线程生命周期
- JDK中,用Thread.State枚举表示了线程的几种状态(可以看源码)
- 线程状态:线程可以处于以下状态之一:
- NEW:尚未启动的线程处于此状态
- RUNNABLE:在Java虚拟机中执行的线程处于此状态
- BLOCKED:线程在Runnable状态下,如果在等待进入同步代码块的锁,就会BLOCKED。如果获取到了这把锁,它就会进入Runnable状态
-
WAITING:线程在Runnable状态下,如果执行了o.wait(),t.join(),LockSupport.park()方法,就会进入WAITING状态
-
TIMED_WAITING(超时等待):线程在Runnable状态下,如果执行了Thread.sleep(time),o.wait(time),t.join(time),LockSupport.park(time)等方法,它就会进入TIMED_WAITING状态
-
TERMINATED:终止状态,线程运行结束
//测试代码 package com.study.state_;public class ThreadState_ {public static void main(String[] args) throws InterruptedException{T t = new T();System.out.println(t.getName() + " 状态 " + t.getState());//Thread-0 状态 NEWt.start();while(t.getState()!=Thread.State.TERMINATED){System.out.println(t.getName() + " 状态 " + t.getState());Thread.sleep(500);}System.out.println(t.getName() + " 状态 " + t.getState());} } class T extends Thread{@Overridepublic 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;}} }
- 一旦线程调用了start方法,它就进入可运行状态Runnable。细分可以将Runnable分为两类:Ready(就绪)、Running(运行)。
- 如果线程在Running状态调用Thread.yield()方法,它就进入Ready状态。如果Running状态下的线程被挂起,它也会进入Ready状态。而线程在Ready状态下时,如果它被调度器选中执行,它会进入Running状态
五、Synchronized
- 线程同步机制:
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性
- 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其它线程都不能对这个内存进行操作,直到该线程操作完成,其他线程才能对该内存进行操作
- 同步的具体方法——Synchronized
- 同步代码块:
synchronized (对象){ //得到对象的锁,才能操作同步代码//同一时刻只能被一个对象访问的代码 }
- 同步方法(synchronized还可以放在方法声明中,表示整个方法为同步方法):
public synchronized void m(){//同一时刻只能被一个线程访问的代码 }
- 同步代码块:
- 用同步来解决多窗口售票问题(如果通过实现Runnable接口的方法来创建线程,那么这三个线程就会叫同一个名字,不太合理。有点类似于,一个人找了三个代购帮他买东西,但本质上买东西的还是这个人。):
package com.study.syn;public class SellTicket {public static void main(String[] args) {SellTicket03 sellTicket03 = new SellTicket03();Thread thread1 = new Thread(sellTicket03);Thread thread2 = new Thread(sellTicket03);Thread thread3 = new Thread(sellTicket03);thread1.start();thread2.start();thread3.start();} } class SellTicket03 implements Runnable{private int ticketNum = 100;//三个线程同时售票,所以要共享它private boolean loop = true;public synchronized void sell(){ //同步方法,在同一时刻,只能有一个线程来执行run方法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));}@Overridepublic void run() {while(loop){sell();}} }
- 当我试图通过,继承Thread类创建线程,还用了synchronized修饰sell方法。但是却没有解决超卖问题。原因是,继承Thread类创建线程,实际上创建了三个线程对象,而sell方法又不是共享的(没有被static修饰),也就是说这三个线程各自在访问自己的sell方法。而同步:是为了解决同一资源不能同时被多个线程访问,也就是说它是一个共享的资源,如果我们要用继承Thread类来创建线程,那我们要在sell方法前加static
六、互斥锁
- 分析同步原理:假如某一时刻有三个线程(t1、t2、t3)想访问一个同步代码块(同步方法),那么谁先抢到这把锁,谁就能访问这个资源。例如,现在是t1抢到了这把锁,它进去访问资源,在t1访问该资源的时候,其它线程就不能访问这个资源了。等t1访问完这个资源,就把锁还回去。然后这三个线程又同时抢这把锁......也就是说,谁抢到这把锁,谁就能执行(注意:这个锁是对象锁)
- 线程在Runnable状态下,如果要等待进入同步代码块的锁,那么它就会被阻塞(状态变成BLOCKED)。如果该线程获得锁,那它就会变成Runnable状态
- 互斥锁的基本介绍:
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其它对象(要求是同一个对象)
- 同步方法(静态的)的锁为当前类本身
- 锁就是一个对象。要求多个线程的锁对象是同一个(即多个线程必须抢同一把锁)即可!!!也就是说
- 如果我们通过实现Runnable接口的方式来创建线程,那么这个锁可以是this对象,也可以是自定义线程类中任意一个对象(这三个thread只是一个代理,本质上运作的还是sellTicket03,如果这三个thread线程去抢同一把锁,也就是在抢这个sellTicket03)
package com.study.syn;public class SellTicket {public static void main(String[] args) {SellTicket03 sellTicket03 = new SellTicket03();Thread thread1 = new Thread(sellTicket03);Thread thread2 = new Thread(sellTicket03);Thread thread3 = new Thread(sellTicket03);thread1.start();thread2.start();thread3.start();} } class SellTicket03 implements Runnable{private int ticketNum = 100;private boolean loop = true;public void sell(){ synchronized (this){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));}}@Overridepublic void run() {while(loop){sell();}} }
- 如果我们通过继承Thread类的方式来创建线程,那么这个锁就是类名.class。(实际上也就是三个SellTicket对象,其实是这三个sellTicket在抢,那么锁必须是这三个对象都能访问到的,也就是说,这个锁也可以是自定义线程类里的静态对象)锁一般显式地写在同步代码块的()里
package com.study.syn;public class SellTicket {public static void main(String[] args) {SellTicket03 s1 = new SellTicket03();SellTicket03 s2 = new SellTicket03();SellTicket03 s3 = new SellTicket03();s1.start();s2.start();s3.start();} } class SellTicket03 extends Thread{private static int ticketNum = 100;//三个线程同时售票,所以要共享它private static boolean loop = true;private static Object obj = new Object();public static void sell(){ //同步方法,在同一时刻,只能有一个线程来执行run方法synchronized (obj){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));}}@Overridepublic void run() {while(loop){sell();}} }
- 如果我们通过实现Runnable接口的方式来创建线程,那么这个锁可以是this对象,也可以是自定义线程类中任意一个对象(这三个thread只是一个代理,本质上运作的还是sellTicket03,如果这三个thread线程去抢同一把锁,也就是在抢这个sellTicket03)
- 个人理解:当我们用实现Runnable接口的方式来创建线程,其实操作的是同一个sellTicket,哪个thread抢到sellTicket,哪个thread就执行。当我们用继承Thread类的方式来创建线程,其实是创建了多个sellTicket,多个对象抢同一个资源,那么这个资源必须是SellTicket里静态的
七、死锁
- 线程的死锁基本介绍:多个线程都占用了对方的资源,但不肯相让,导致了死锁
- 死锁的代码演示:
package com.study.syn;public class DeadLock_ {public static void main(String[] args) {//模拟死锁现象DeadLockDemo A = new DeadLockDemo(true);DeadLockDemo B = new DeadLockDemo(false);A.setName("A线程");B.setName("B线程");A.start();B.start();} } class DeadLockDemo extends Thread{static Object o1 = new Object();//保证多个线程共享同一个对象static Object o2 = new Object();boolean flag;public DeadLockDemo(boolean flag){this.flag = flag;}@Overridepublic void run() {//1.如果flag为True,线程A就会先得到/持有o1对象锁,然后尝试去获取o2对象锁//2.如果线程A得不到o2对象锁,就会BLOCKED//3.如果flag为False,线程B就会先得到/持有o2对象锁,然后尝试去获取o1对象锁//4.如果线程B得不到o1对象锁。就会BLOCKEDif (flag){synchronized (o1){System.out.println(Thread.currentThread().getName() + "进入1");synchronized (o2){System.out.println(Thread.currentThread().getName() + "进入2");}}}else{synchronized (o2){System.out.println(Thread.currentThread().getName() + "进入3");synchronized (o1){System.out.println(Thread.currentThread().getName() + "进入4");}}}} }
- 下面的操作会释放锁:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步1代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停、并释放锁
- 下面的操作不会释放锁:
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法,暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其它线程调用了该线程的suspend()方法,将该线程挂起,该线程不会释放锁(线程从Runnable状态出来,才叫释放锁,而Runnable状态又细分为两种:Ready和Running。但如果是从Runnable状态进入TIMED_WAITING状态,不会释放锁)
八、线程作业
- 编程题1(1)在main方法中启动两个线程(2)第一个线程循环随机打印100以内的的整数(3)直到第二个线程从键盘读取了"Q"命令
package HomeWork;import java.util.Scanner;public class Homework01 {public static void main(String[] args) {T1 t1 = new T1();T2 t2 = new T2(t1);t1.start();t2.start();} } class T1 extends Thread{private boolean loop = true;@Overridepublic void run() {//循环打印100以内的整数while(loop){//(int)(Math.random() * 100)生成了[0,99]之间的整数System.out.println((int)(Math.random() * 100) + 1);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public void setLoop(boolean loop) {this.loop = loop;} } class T2 extends Thread{private T1 t;public T2(T1 t){this.t = t;}@Overridepublic void run() {//从键盘读取Scanner scanner = new Scanner(System.in);String next = scanner.next();if (next.equals("Q")){t.setLoop(false);}} }
- 编程题2(1)有2个用户分别从同一个卡上取钱(总额:10000)(2)每次都取1000,当余额不足时,就不能取款了(3)不能出现超取现象(线程同步问题)
package HomeWork;public class Homework02 {public static void main(String[] args) {Account account = new Account();Thread thread1 = new Thread(account);Thread thread2 = new Thread(account);thread1.setName("第一个用户");thread2.setName("第二个用户");thread1.start();thread2.start();} } class Account implements Runnable{private int money = 10000;//余额为10000元public void withdraw(){//哪个用户抢到这把锁,就先判断卡里钱够不够,够的话就取钱,取完钱释放锁//如果钱不够,就退出这个循环while(true){synchronized (this){if (money < 1000){System.out.println("卡里余额不够,无法取钱...");break;}System.out.println(Thread.currentThread().getName() + "从卡中取走1000元,还剩" + (money -= 1000) + "元");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}public void run() {withdraw();} }
本笔记根据韩顺平零基础学Java课程学习整理而来