java线程

一、线程的相关概念

  • 程序(program)

    是为了完成特定任务,用某种语言进行编写的一组指令的集合。简单的说:就是我们写的代码

  • 进程

    1、进程是指运行中的程序,比如我们使用QQ,就启动一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间

    2、进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

  • 线程

    1、线程是由进程创建的,是进程的一个实体

    2、一个进程可以拥有多个线程

  • 其他相关概念

    1、单线程:同一个时刻,只允许执行一个线程

    2、多线程:同一时刻,可以执行多个线程,比如:一个qq进程,可以打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件

    3、并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多个任务就是并发

    4、并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。并发金和并行可以同时存在

二、线程的创建

1、继承Thread类,重写run()方法

类图关系

代码案例:

copy
public class Thread01 { public static void main(String[] args) throws InterruptedException { /** * 1、请编写一个程序,开启一个线程,该线程每隔一秒,在控制台输出“ hello,world!” * 2、对上题改进:当输出80次 ”hello,world“,结束该线程 * 3、使用JConsole 监控线程执行情况,并画出程序示意图 */ //创建一个Cat对象,当做线程使用 Cat cat = new Cat(); cat.start(); //说明:当main线程启动一个子线程时,也就是Thread-0子线程,主线程不会阻塞,会继续执行下面的方法 for (int i = 0; i < 10; i++) { System.out.println("主线程:" + Thread.currentThread().getName() + " 正在打印: 我是主线程"); Thread.sleep(1000); } } } //继承 Thread 类 //1、当一个类继承了Thread,该类就可以当做线程使用 //2、一般我们会重写run方法,写上自己的逻辑 //3、run Thread类 实现了 Runnable 接口的run方法 class Cat extends Thread{ int time = 0; //重写run方法 @Override public void run() { while (time < 10){ //线程名称 System.out.println("线程名称:=====>"+Thread.currentThread().getName() + " 正在打印:====> hello world!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } time++; } } }

1、当运行该程序时,就相当于启动了一个进程,

2、启动进程过后,就会马上进入这个主方法,也就是main方法,进入main方法,就是相当于开启了一个主线程,也就是进程里面开启了一个主线程(main线程)

3、在这个main主线程里面,new了一个Cat,因为这个Cat继承了Thread,所以是一个线程类,Cat调用 start() 方法,就是相当于在main主线程中又开启了一个新的线程,Cat线程

4、当main线程启动一个子线程时,也就是Thread-0子线程,主线程不会阻塞,会继续执行下面的方法,这时的主线程和子线程都是交互执行的

5、当主线程执行完并退出时,子线程还在运行,这样并不会造成这个进程的结束(这个进程也就是应用程序)

6、主线程可以开启多个子线程,子线程也可以开启多个子线程,当最后一个子线程结束后,整个进程才会退出

2、实现Runnable接口

说明:

1、java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread方法来创建线程显然是不可能了

2、java设计者们提供了另一个方式创建线程,就是通过实现Runnable接口来创建线程

代码案例:

三、start() 和 run()

run() 方法只是一个普通的方法,在main主方法中调用线程的 run() 方法,只会是等待被调用的线程的 run() 方法执行完后才会再执行下一行代码,没有真正的启动一个线程,线程类调用的 run() 方法所在的线程是主线程,也就是main线程

start() 才会真正的启动线程

源码:

copy
//首先调用start方法 public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); //这个方法才是真正的启动线程 started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }

真正实现多线程的是 start0() 这个方法

copy
//start0()是一个 本地方法,是JVM进行调用,底层是c/c++ //真正实现多线程的是 start0() 这个方法,而不是 run() 方法 private native void start0();

原理图:

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

静态代理测试代码:

copy
public class Thread02 { public static void main(String[] args) { //编写程序,该程序每隔一秒,在控制台输出 “hi” ,当输出10次后,自动退出,请使用实现Runnable接口方式实现,这里是静态代理 //Dog dog = new Dog(); //创建一个 Thread 对象,把dog(实现了Runnable接口),放入new Thread(dog) //这里使用了一个设计模式,代理模式,静态代理 //Thread t = new Thread(dog); //t.start(); Tiger tiger = new Tiger(); ThreadProxy threadProxy = new ThreadProxy(tiger); threadProxy.start(); } } //测试 class Animal {} class Tiger extends Animal implements Runnable { int time = 0; @Override public void run() { while (time < 10){ System.out.println("老虎叫了" + (++time) + "声"); } } } //模拟代理模式 //可以将ThreadProxy看做一个代理类 //模拟了一个最简单的Thread类 class ThreadProxy implements Runnable { //属性,类型是 Runnable private Runnable target = null; @Override public void run() { if (target != null){ target.run(); //动态绑定(运行类型是 Tiger) } } //创建一个构造器,将实现了Runnable对象传进来 public ThreadProxy(Runnable target) { this.target = target; } //实现start方法 public void start(){ //这个方法是真正实现了多线程的方法 start0(); } public void start0(){ run(); } } //通过实现 Runnable 接口实现线程类 class Dog implements Runnable{ int time = 0; //重写run方法 @Override public void run() { while (true) { System.out.println("hi" + (++time) + " 线程名称:" + Thread.currentThread().getName()); try { //休眠1秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (time == 10){ break; } } } }

四、多线程执行

要求:

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

copy
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(); } } //每隔一秒输出 “hello,world”,输出10次退出 class T1 implements Runnable{ int time = 0; @Override public void run() { while (time < 10){ System.out.println("hello,world " + (++time)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } //每隔一秒输出 “hi”,输出5次退出 class T2 implements Runnable{ int time = 0; @Override public void run() { while (time < 5){ System.out.println("hi " + (++time)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }

五、Thread 和 Runnable 的区别

1、从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口

底层都是通过 start()方法 去调用 start0()方法

2、实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制

理解:当创建了一个线程类,继承Runnable接口,那么可以开启两个线程进行去同时执行这个线程类,这样两个线程就可以共享一个资源

copy
T1 t1 = new T1(); Thread thread1 = new Thread(t1); Thread thread2 = new Thread(t1); thread1.start(); thread2.start();

六、模拟售票

copy
public class SellTicket { public static void main(String[] args) { //Thread 测试 // SellTicket01 s1 = new SellTicket01(); // SellTicket01 s2 = new SellTicket01(); // SellTicket01 s3 = new SellTicket01(); //启动 // s1.start(); // s2.start(); // s3.start(); //Runnable 测试 SellTicket02 sellTicket02 = new SellTicket02(); //启动 操作的是同一个对象 new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); } } //使用 Thread 方式 互斥同步的问题 class SellTicket01 extends Thread{ //票数,让多个线程共享 private static int ticketNum = 100; @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() + " 卖出了1张票,剩余票数 ===> " + (--ticketNum)); } } } //通过 Runnable 接口实现 class SellTicket02 implements Runnable{ //票数,让多个线程共享 private int ticketNum = 100; @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() + " 卖出了1张票,剩余票数 ===> " + (--ticketNum)); } } }

出现的共同问题:

当出现三个线程共享一个资源(票数)
当票数只剩2张,当t1先进行判断时,发现还有2张票,但是在t1没有来得及进行售票操作时,t2进来,此时的票数在t1没有减去1张前,还有2张,就在t2和t1正在往下执行售票操作时,t3进来,判断发现票数还没有减去,还有2张,也就执行售票操作,最终导致的结果是t1和t2已经完成了售票操作,分别卖出1张,那么t3就没有票,但是也执行了售票操作,导致票数为负数,超过额定的票数

七、线程退出和中断

1、线程退出

基本说明:

1、当线程完成任务后,会自动退出

2、还可以通过使用变量来控制 run 方法退出的方式停止线程,即通知方式

示例代码:

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

copy
public class ThreadExit_ { public static void main(String[] args) throws InterruptedException { T t1 = new T(); t1.start(); //如果希望main线程去终止t1这个线程,必须修改loop,让t1退出run方法,从而终止 t1 -> 通知方法 //让main线程休眠5秒,再去通知t1子线程 Thread.sleep(5000); t1.setLoop(false); } } class T extends Thread{ int count = 0; //设置一个变量 private boolean loop = true; //通过set方式修改 loop 的值,从而达到将 run 方法停止的目的 public void setLoop(boolean loop) { this.loop = loop; } @Override public void run() { while (loop){ System.out.println("我是子线程 " + Thread.currentThread().getName() + (++count)); try { //休眠50毫秒 Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }

2、线程中断

线程中断就是将正在休眠的线程提前中断,打断线程休眠,将线程唤醒,打断的线程将会重新进入 while 循环,再次执行

copy
public class ThreadInterrupt_ { public static void main(String[] args) throws InterruptedException { A a = new A(); //开启子线程 a.start(); //主线程打印5次 “hi”,中断子线程的休眠 for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println("hi " + i); } //中断子线程的休眠 a.interrupt(); } } //自定义线程类 class A 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(200000); } catch (InterruptedException e) { // InterruptedException 中断异常 //当线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码 System.out.println(Thread.currentThread().getName() + " 被interrupt了~~~ "); } } } }

八、线程的常用方法

1、基本方法

1、setName //设置线程名称,使之与参数name相同

2、getName //返回该线程的名称

3、start //使该线程开始执行;java虚拟机底层调用该线程的 start0() 方法

4、run //调用线程对象 run 方法

5、setPriority //更改线程优先级

6、getPriority //获取线程优先级

7、sleep //在执行的毫秒数内让当前正在执行的线程休眠(暂停执行)

8、interrupt //中断线程

需要注意的细节:

1、start 底层会创建新的线程,调用 run,run就是一个简单的方法调用,不会启动新线程

2、线程优先级的范围

3、interrupt,中断线程,但是并没有真正的结束线程。所以一般用于中断正在休眠的线程

4、sleep:线程的静态方法,使当前线程休眠

源码,优先级常量:

copy
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; //优先级最低 /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; //正常的优先级 /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10; //优先级最高

示例代码:

copy
public class ThreadMethod01 { public static void main(String[] args) throws InterruptedException { //测试相关方法 T t = new T(); //设置线程名称 setName t.setName("猪八戒"); //设置优先级 t.setPriority(Thread.MIN_PRIORITY); //开启线程 t.start(); //输出线程名称 System.out.println("线程名称:" + Thread.currentThread().getName()); //主线程打印5次 “hi”,然后中断子线程的休眠 for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println("hi " + i); } //中断子线程的休眠 t.interrupt(); } } //自定义线程类 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(200000); } catch (InterruptedException e) { // InterruptedException 中断异常 //当线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码 System.out.println(Thread.currentThread().getName() + " 被interrupt了~~~ "); } } } }

2、线程礼让和线程插队

1、yield:线程礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功

copy
public class ThreadMethod02 { public static void main(String[] args) throws InterruptedException { B b = new B(); b.start(); //让主线程输出 20次 hi,每隔1秒 for (int i = 0; i <= 20; i++) { Thread.sleep(1000); System.out.println("主线程输出 hi" + i); //判断主线程是否到了第5次,让子线程先执行,执行完成后,主线程再执行 if (i == 4){ //线程礼让,不一定成功,这是一个静态方法,让主线程先执行 Thread.yield(); } } } } //创建子线程 class B extends Thread{ int time = 0; @Override public void run() { //让子线程循环输出20次 hello,每隔1秒 while (true){ if (time == 20){ break; } try { System.out.println("子线程输出 hello " + (++time)); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }

2、join:线程插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务

案例:

创建一个子线程,每隔1s输出 hello ,输出20次,主线程每隔1s ,输出 hi,输出 20 次

要求:

两个线程同时执行,当主线程输出 5 次后,就让子线程运行完毕,主线程再继续

join代码示例:

copy
public class ThreadMethod02 { public static void main(String[] args) throws InterruptedException { B b = new B(); b.start(); //让主线程输出 20次 hi,每隔1秒 for (int i = 0; i <= 20; i++) { Thread.sleep(1000); System.out.println("主线程输出 hi" + i); //判断主线程是否到了第5次,让子线程先执行,执行完成后,主线程再执行 if (i == 4){ //让子线程先执行 //线程插队 b.join(); } } } } //创建子线程 class B extends Thread{ int time = 0; @Override public void run() { //让子线程循环输出20次 hello,每隔1秒 while (true){ if (time == 20){ break; } try { System.out.println("子线程输出 hello " + (++time)); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }

3、用户线程和守护线程

基本介绍:

1、用户线程:也叫做工作线程,当线程的任务执行完或者通知方式结束

2、守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

3、常见的守护线程:垃圾回收机制

制作一个守护线程:

copy
public class ThreadMethod03 { public static void main(String[] args) throws InterruptedException { //声明守护线程 MyDeamonThread mt = new MyDeamonThread(); //将子线程设置为守护线程 //一旦所有的线程结束工作,那么守护线程也将会退出工作 mt.setDaemon(true); //启动守护线程 mt.start(); //主线程循环 for (int i = 0; i <= 10; i++) { Thread.sleep(1000); System.out.println("我是主线程,我正在工作..."); } } } //线程类 class MyDeamonThread extends Thread{ @Override public void run() { //等价于while循环,无限循环 for (; ;){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是守护线程,我正在守护....."); } } }

注意点:

需要将 mt.setDaemon(true) 在线程启动之前设置,否则就会抛出异常

九、线程的生命周期

官方文档的生命周期:

  • NEW

    尚未启动的线程处于此状态

  • RUNNABLE

    在java虚拟机中执行的线程处于此状态

  • BLOCKED

    被阻塞等待监视器锁定的线程处于此状态

  • WAITING

    在等待另一个线程执行特定动作的线程处于此状态

  • TIMED_WAITING

    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

  • TERMINATED

    已经退出的线程处于此状态

线程生命周期转换图:

说明:

1、当创建一个线程,也就是new一个线程出来过后,首先进入一个NEW状态,一旦调用了 start() 方法,就进入 Runnable状态(可运行状态),Runnable状态细化的话可分为两个状态,Ready状态(就绪状态)和 Running(运行状态)

2、线程是否被执行,还要取决于线程是否被调度器选中执行,一旦被调度器选中,那么就从Ready状态(就绪状态),那么线程就进入Running状态(运行状态),如果线程运行结束,那么就会进入Teminated状态(终止状态),相当于线程挂了

3、如果线程在 Runnable状态(可运行状态)调用了 Tread.sleep(time)、o.wait(time)、t.join(time)、LockSupport.parkNanos() 这些方法,就会进入TimeWaiting状态(超时等待状态),等待时间结束后在进入 Runnable状态(可运行状态)

4、如果线程在 Runnable状态(可运行状态)调用了 o.wait()、t.join()、LockSupport.park() 这些方法,那么就会进入 Waiting状态(等待状态),通过线程执行 o.notify()、o.notifyAll()、LockSupport.unpark() 这些方法重新进入 Runnable状态(可运行状态)

5、当线程在Runnable状态(可运行状态)要去获取一把锁或者进入一个同步代码块时,会先进入 Blocked 状态(阻塞状态),获取锁后才会重新进入Runnable状态(可运行状态),等待 Running

在官方文档中有6种状态,但是在Runnable状态(可运行状态)线程是否在运行,要取决于调度器,由内核的调度器来执行,因此还可再细分为 Ready状态(就绪状态)和 Running状态(运行状态)

实例代码:

copy
public class ThreadState_ { public static void main(String[] args) throws InterruptedException { Td td = new Td(); //线程启动前状态 System.out.println(td.getName() + " 线程启动前的状态 " + td.getState()); td.start(); //循环线程启动后的状态 //Thread.State.TERMINATED != td.getState() 判断当前线程状态是否为终止状态 while (Thread.State.TERMINATED != td.getState()){ //当线程状态不为终止状态,就打印线程的实时状态 System.out.println(td.getName() + " 线程正在运行的状态 " + td.getState()); Thread.sleep(500); //让主线程休眠再打印 } //线程停止的状态 System.out.println(td.getName() + " 线程停止的状态 " + td.getState()); } } class Td extends Thread{ @Override public void run() { while (true){ for (int i = 0; i < 10; i++) { System.out.println("hi~ " + i); try { //这里的休眠会让线程进入 超时等待状态 TIMED_WAITING Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } //for循环结束后直接退出子线程 break; } } }

十、线程同步机制

说明:

1、在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性

2、也可以这里理解:线程同步,即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

同步实现具体方法 - synchronized

1、同步代码块,对象锁

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

2、synchronized( 还可以放在方法声明中,表示整个方法为同步方法

copy
public synchronized void method(String name){ //需要被同步的代码 }

3、理解:当一个人去上厕所前,进入厕所要先关门,上完厕所出来后,把门打开才能让下一个人进去上厕所

4、使用 synchronized 解决售票问题

copy
public class SellTicket { public static void main(String[] args) { SellTicket01 sk = new SellTicket01(); new Thread(sk).start(); new Thread(sk).start(); new Thread(sk).start(); } } //通过 Runnable 接口实现 class SellTicket01 implements Runnable{ //票数,让多个线程共享 private int ticketNum = 100; //控制while循环 private boolean loop = true; //public synchronized void sell(){} 通过synchronized加上一把锁,那么这个方法就是一个同步方法,这时,这个锁是在this对象上(SellTicket01) public synchronized void sell(){ // 同步方法,在同一时刻,只能有一个线程来操作这个卖票方法 //如果卖完了,直接退出 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() + " 卖出了1张票,剩余票数 ===> " + (--ticketNum)); } @Override public void run() { while (loop){ sell(); //这个sell方法是一个同步方法 } } }

同步原理:

比如有三个线程 t1、t2、t3,刚开始会去争夺锁,锁在对象上,当 t1 抢到这把锁,进去操作相关的代码,当操作完后, t1 就会把这把锁放回去,然后再去与 t2、t3 争夺这把锁,synchronized 是一把非公平锁,每个线程都可以去争夺

十一、互斥锁

说明:

1、java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性

2、每个对象都对应于一个可称为 互斥锁 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

3、关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问

4、同步的局限性:导致程序的执行效率要降低

5、同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)

6、同步方法(静态的)的锁为当前类本身

copy
//同步方法(静态的)的锁为当前类本身 //1、public synchronized static void method(){} 锁是加在 SellTicket01.class上的,因为这个method01是一个静态方法 public synchronized static void method01(){} //2、如果是静态方法中,实现一个同步代码块,那么synchronized中就是这个类的 .class public static void method02(){ synchronized (SellTicket01.class){} }

将锁加在代码块上:

copy
public class SellTicket { public static void main(String[] args) { SellTicket01 sk = new SellTicket01(); new Thread(sk).start(); new Thread(sk).start(); new Thread(sk).start(); } } //通过 Runnable 接口实现 class SellTicket01 implements Runnable{ //票数,让多个线程共享 private int ticketNum = 100; //控制while循环 private boolean loop = true; //可以用这个object替代当前synchronized中的this,因为这三个线程操作的都是同一个类 //synchronized (object) Object object = new Object(); //同步方法(静态的)的锁为当前类本身 //1、public synchronized static void method(){} 锁是加在 SellTicket01.class上的,因为这个method01是一个静态方法 public synchronized static void method01(){} //2、如果是静态方法中,实现一个同步代码块,那么synchronized中就是这个类的 .class public static void method02(){ synchronized (SellTicket01.class){} } public void sell(){ // 同步方法,在同一时刻,只能有一个线程来操作这个卖票方法 //同步代码块 synchronized 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() + " 卖出了1张票,剩余票数 ===> " + (--ticketNum)); } } @Override public void run() { while (loop){ sell(); //这个sell方法是一个同步方法 } } }

注意事项和细节:

1、同步方法如果没有使用 statis 修饰,默认锁的对象为 this

2、如果方法使用statis修饰,默认锁对象为 当前类.class

3、实现的落地步骤:

  • 需要分析上锁的代码
  • 选择同步代码块或同步方法
  • 要求多个线程的锁对象为同一个即可
copy
/** * 当调用该线程时,肯定是如下场景 * new SellTicket02().start() * new SellTicket02().start() * 这样这个同步所就无法实现,原因是每次都是 new 一个新的 SellTicket02 * 而不是操作同一个SellTicket02对象,所以 this 的指向都是新的对象,无法锁住 * 最后失效 */ class SellTicket02 extends Thread{ private static int ticketNum = 100; //多个线程共享 public void method(){ synchronized (this){ System.out.println("hello-"); } } @Override public void run() { super.run(); } }

十二、线程死锁

基本介绍:

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生

发生死锁实例代码:

copy
public class DeadLock_ { public static void main(String[] args) { //线程A启动 DeadLockDemo A = new DeadLockDemo(true); A.setName("A线程 "); //线程B启动 DeadLockDemo B = new DeadLockDemo(false); B.setName("B线程 "); A.start(); B.start(); } } class DeadLockDemo extends Thread{ //为保证多线程情况下,共享一个 object对象,这里使用static修饰 static Object o1 = new Object(); static Object o2 = new Object(); boolean flag; public DeadLockDemo(boolean flag) { this.flag = flag; } @Override public void run() { /** * 业务逻辑分析 * 1、如果flag为true,线程A 就会先持有 o1 这个对象锁,然后尝试去获取 o2 对象锁 * 2、如果 线程A 得不到 o2 对象锁,就会进入 Blocked 状态(阻塞状态) * 3、如果flag为false,线程B 就会先得到 o2 这个对象锁,然后尝试获取 o1 对象锁 * 4、如果 线程B 拿不到 o1 对象锁,就会进入 Blocked 状态(阻塞状态) */ if (flag){ synchronized (o1){ //synchronized加上对象互斥锁,下面就是同步对象 System.out.println(Thread.currentThread().getName() + " 进入o1 "); synchronized (o2){ System.out.println(Thread.currentThread().getName() + " 进入o2 "); } } }else{ synchronized (o2){ System.out.println(Thread.currentThread().getName() + " 进入o3 "); synchronized (o1){ System.out.println(Thread.currentThread().getName() + " 进入o4 "); } } } } }

十三、释放锁

基本介绍:

1、当线程的同步方法、同步代码块执行结束

2、当线程在同步代码块、同步方法中遇到 break、return

3、当线程在同步代码块、同步方法中出现未处理的 Error 或 Exception,导致异常结束

4、当线程在同步代码块、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁

下面的操作不会释放锁:

1、线程执行同步代码块或者同步方法时,程序调用 Thread.sleep()、Thread.yield() 方法暂停当前线程的执行,不会释放锁

2、线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放锁,提示:应尽量避免使用 suspend() 和 resume() 来控制线程,方法不再推荐使用

posted @   花椒蛋炒饭  阅读(467)  评论(0编辑  收藏  举报
相关博文:
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起