🌔1、我们首先要理解什么是程序、进程和线程?
🌔 2、线程采用怎样的调度方式?
🌔 3、相较于单线程,多线程程序有哪些优点?
🌔 4、我们常说的并发和并行有什么区别?
🌔1、在Java中,用什么代表线程?
🌔2、我们可以通过继承Thread类来创建线程类
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0*/
public class MyThreadTest {public static void main(String[] args) {//创建线程类的实例MyThread myThread = new MyThread("线程A");//调用start方法执行线程myThread.start();MyThread mt2 = new MyThread("线程B");mt2.start();}
}class MyThread extends Thread{public MyThread(){}public MyThread(String message){super(message);}@Overridepublic void run() {for(int i = 1; i < 10; i++){System.out.println(getName() + "线程正在执行" + i);}}
}
🌔 3、我们可以通过实现Runnable接口创建线程类
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0*/
public class MyThreadTest2{public static void main(String[] args) {//创建线程任务对象MyThread2 m2 = new MyThread2();//创建线程对象[用指定的线程和指定的线程名]Thread thread = new Thread(m2, "长江一号");//启动线程thread.start();}
}class MyThread2 implements Runnable{@Overridepublic void run() {for(int i = 0; i < 10; i++){//继承Thread类可以直接使用getName方法获取线程名,但是采用实现Runnable接口//的方式,必须要通过Thread.currentThread()来调用System.out.println(Thread.currentThread().getName() + " " + i);}}
}
🌔 4、我们还可以通过匿名内部类实现线程的创建和启动
new Thread("新的线程!"){@Overridepublic void run(){for(int i = 0; i < 10; i++){System.out.println(getName() + " 正在执行 " + i);}}
}.start();
new Thread(new Runnable(){@Overridepublic void run(){for(int i = 0; i < 10; i++){System.out.println(Thread.currentThread().getName() + " 正在执行 " + i);}}
}).start();
🌔 5、2和3采用的两种方法有什么异同点呢?
联系:Thread类实际上实现了Runnable接口
public class Thread extends Object implements Runnable
区别:
实现Runnable接口比继承Thread类所具有的优势
🌔 6、练习
创建两个分线程,让其中一个线程输出1-100之间的偶数,另一个线程输出1-100之间的奇数。
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0* 创建两个分线程,让其中一个线程输出1-100之间的偶数,另一个线程输出1-100之间的奇数。*/
public class ThreadTest1 {public static void main(String[] args) {//创建线程1Thread m1 = new Thread(new MyThread3(), "长江一号");m1.start();//创建线程2MyThread4 m2 = new MyThread4("长江二号");m2.start();}}
//实现Runnable接口的效果比继承Thread类的效果要好
class MyThread3 implements Runnable{@Overridepublic void run(){while(true){for(int i = 1; i <= 100; i += 2){System.out.println(Thread.currentThread().getName() + " -奇数- " + i);}break;}}
}
class MyThread4 extends Thread{public MyThread4(String message){super(message);}@Overridepublic void run(){while(true){for(int i = 2; i <= 100; i += 2){System.out.println(getName() + " -偶数- " + i);}break;}}
}
🌔 1、首先我们要了解Thread类的构造器有哪几种形式?
🌔 2、其次我们需要了解与线程相关的常用方法有哪些呢?【为了方便记忆将其分为三组】
(1)常用方法系列一
(2)常用方法系列二
(3)常用方法系列三
每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0* 声明一个匿名内部类继承Thread类,重写run方法,* 实现打印[1,100]之间的偶数,要求每隔1秒打印1个偶数。* 声明一个匿名内部类继承Thread类,重写run方法,实现打印[1,100]之间的奇数,* 当打印到5时,让奇数线程暂停一下,再继续。* 当打印到5时,让奇数线程停下来,让偶数线程执行完再打印。*/
public class ThreadWork1 {public static void main(String[] args) {Thread t = new Thread(){@Overridepublic void run() {for(int i = 2; i <= 100; i += 2){System.out.println(i);try{sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}}};t.start();//创建一个新的线程打印奇数,这两个线程都是采用匿名类继承Thread类实现的Thread t2 = new Thread(){@Overridepublic void run() {for(int i = 1; i <= 100; i += 2){System.out.println(i);if(i == 5){
// yield(); 问题要求的是尝试让奇数线程等一会儿和让奇数线程停止try{join();}catch (InterruptedException e){e.printStackTrace();}}}}};t2.start();}
}
🌔 3、什么是守护线程呢?
有一种线程,它在后台运行,它的任务是为其他线程提供服务的,这种线程被称为守护线程
如果其他非守护线程全部死亡,那么守护线程自动死亡
案例分析:创建一个守护线程、让main线程做其他线程,感受守护线程自动死亡的效果
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0*/
public class MyThreadTest3 {public static void main(String[] args) {MyThread5 m5 = new MyThread5();//将我们创建的线程对象设置为守护线程m5.setDaemon(true);//启动m5.start();//用我们的主线程打印1-100,来体验所有非守护线程死亡后,守护线程自动死亡的效果for(int i = 1; i <= 100; i++){System.out.println(Thread.currentThread().getName() + " " + i);}}
}class MyThread5 extends Thread{@Overridepublic void run(){//让循环条件为true,我们是希望它一直运行下去while(true){System.out.println("我会一直守护你!");try{Thread.sleep(1);}catch (InterruptedException e){e.printStackTrace();}}}
}
Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下一些状态:
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切换,就导致线程状态会多次在运行、阻塞、就绪之间切换
1、新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
此时它和其他Java对象一样,仅仅有JVM为其分配了内存,并初始化了实例变量的值
此时线程对象并没有任何线程的动态特征,程序也不会执行它的线程体
2、就绪
3、运行
如果处于就绪状态的线程获得了CPU资源时,则会开始执行run方法的线程体代码,此时线程处于运行状态
计算机有几个CPU核心,在同一时刻就可以有几个线程在并行执行
对于抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务,当时间用完,系统会剥夺该线程所占用的资源,让其回到就绪状态等待下一次被调度。
此时其他线程将获得执行机会,抢占式策略下,会更具线程的优先级来分配CPU的执行权给线程,如果优先级相同,那么将会采用先来先服务的方式
4、阻塞
当在运行过程中的线程遇到一下情况时,会让出CPU并临时终止自己的执行,进入到阻塞状态:
阻塞线程被唤醒(重新转入到就绪状态)有以下几种情况:
5、死亡
在 java.lang.Thread.State (线程状态) 的枚举类中这样定义:
public enum State{NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}
重点说明,根据Thread.State的定义,阻塞状态分为三种:BLOCKED、WAITING、TIMED_WAITING。
BLOCKED(锁阻塞):一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。
TIMED_WAITING(计时等待):一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。
WAITING(无限等待):一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
说明:当从WAITING或TIMED_WAITING恢复到Runnable状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入BLOCKED状态。
通过案例演示来感受进程在各种状态之间切换
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0*/
public class MyThreadState {public static void main(String[] args) throws InterruptedException{MyThread6 m6 = new MyThread6();System.out.println(m6.getName() + " " + m6.getState());m6.start();//如果线程不处于终止状态,那么我们就每隔0.5s输出一次线程名和线程状态while(m6.getState() != Thread.State.TERMINATED){System.out.println(m6.getName() + " " + m6.getState());Thread.sleep(500);}System.out.println(m6.getName() + " " + m6.getState());}
}class MyThread6 extends Thread{@Overridepublic void run() {//此处添加将循环条件设为true,然后在for循环结束后又跳出循环的设置不知道有什么用while(true){for(int i = 0; i < 10; i++){System.out.println(i);try{sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}break;}}
}
当我们使用多个线程访问同一资源的时候,若多个线程只有读操作,那么不会发生线程安全问题,但如果多个线程中对资源读和写的操作,就容易出现线程安全问题
(1)第一种情况:局部变量不能共享
package com.atguigu.unsafe;class Window extends Thread {public void run() {int ticket = 100;while (ticket > 0) {System.out.println(getName() + "卖出一张票,票号:" + ticket);ticket--;}}
}public class SaleTicketDemo1 {public static void main(String[] args) {Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}
结果:三个窗口操作的局部变量 ticket是独立的,最终卖出300张票
(2)第二种情况:不同对象的实例变量不共享
package com.atguigu.unsafe;class TicketWindow extends Thread {private int ticket = 100;public void run() {while (ticket > 0) {System.out.println(getName() + "卖出一张票,票号:" + ticket);ticket--;}}
}public class SaleTicketDemo2 {public static void main(String[] args) {TicketWindow w1 = new TicketWindow();TicketWindow w2 = new TicketWindow();TicketWindow w3 = new TicketWindow();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}
(3)第三种情况:静态变量是共享的
package com.atguigu.unsafe;class TicketSaleThread extends Thread {private static int ticket = 100;public void run() {while (ticket > 0) {try {Thread.sleep(10);//加入这个,使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "卖出一张票,票号:" + ticket);ticket--;}}
}public class SaleTicketDemo3 {public static void main(String[] args) {TicketSaleThread t1 = new TicketSaleThread();TicketSaleThread t2 = new TicketSaleThread();TicketSaleThread t3 = new TicketSaleThread();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
(4)第四种情况:同一个对象的实例变量共享
package com.atguigu.safe;class TicketSaleRunnable implements Runnable {private int ticket = 100;public void run() {while (ticket > 0) {try {Thread.sleep(10);//加入这个,使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);ticket--;}}
}public class SaleTicketDemo4 {public static void main(String[] args) {TicketSaleRunnable tr = new TicketSaleRunnable();Thread t1 = new Thread(tr, "窗口一");Thread t2 = new Thread(tr, "窗口二");Thread t3 = new Thread(tr, "窗口三");t1.start();t2.start();t3.start();}
}
(5)第五种情况:抽取资源类,共享同一个资源对象
package com.atguigu.unsafe;//1、编写资源类
class Ticket {private int ticket = 100;public void sale() {if (ticket > 0) {try {Thread.sleep(10);//加入这个,使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);ticket--;} else {throw new RuntimeException("没有票了");}}public int getTicket() {return ticket;}
}public class SaleTicketDemo5 {public static void main(String[] args) {//2、创建资源对象Ticket ticket = new Ticket();//3、启动多个线程操作资源类的对象Thread t1 = new Thread("窗口一") {public void run() {while (true) {ticket.sale();}}};Thread t2 = new Thread("窗口二") {public void run() {while (true) {ticket.sale();}}};Thread t3 = new Thread(new Runnable() {public void run() {ticket.sale();}}, "窗口三");t1.start();t2.start();t3.start();}
}
上面给出的几种情况均存在线程安全问题,那么我们应该如何解决呢?
🌔 1、同步机制是如何解决线程安全问题的呢?
其实就相当于给某段代码加“锁”,任何线程想要执行这段代码,都要先获得“锁”,我们称它为同步锁。因为Java对象在堆中的数据分为分为对象头、实例变量、空白的填充。而对象头中包含:
哪个线程获得了“同步锁”对象之后,”同步锁“对象就会记录这个线程的ID,这样其他线程就只能等待了,除非这个线程”释放“了锁对象,其他线程才能重新获得/占用”同步锁“对象。
我们采用 synchronized 关键字定义同步代码块,表示只对这个区域的资源实行互斥访问
synchronized(同步锁){需要同步操作的代码
}
public synchronized void method(){可能会产生线程安全问题的代码
}
🌔 2、同步锁的实现机制是怎样的呢?
对于并发工作,需要防止共享资源竞争。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问这项资源;解锁后,其他任务中的一个就可以访问这个临界资源了。
🌔 3、synchronize 的锁是什么呢?
🌔 4、同步操作的思考顺序?
(1)第一步,如何判断是否存在线程安全问题呢?
(2)第二步,如果存在线程安全问题,那么我们如何解决呢?
(3)最后,要确定好同步的范围
🌔 5、演示如何加锁?
静态方法加锁
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0* 静态方法加锁*/
public class ThreadSafe1 {public static void main(String[] args) {//创建三个线程代表三个售票窗口Thread1 t1 = new Thread1();Thread1 t2 = new Thread1();Thread1 t3 = new Thread1();//因为没有设置带参的构造方法,所以只能用setName设置线程名t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//启动三个线程t1.start();t2.start();t3.start();}
}class Thread1 extends Thread{//将共享变量设置为静态变量[100张票]private static int ticket = 100;@Overridepublic void run() {//票数不为零才能继续买票while(ticket > 0){salOneTicket();}}//提供一个静态方法来实现卖票的动作,我们为这个方法加锁 >> 在同一时刻只能有一个线程调用这个方法public synchronized static void salOneTicket(){if(ticket > 0){System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);ticket--;}}
}
非静态方法加锁
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0* 非静态方法加锁 -- 采用的是实现Runnable接口,利用一个线程target创建三个线程对象*/
public class ThreadSafe2 {public static void main(String[] args) {//创建线程的目标targetThread2 t = new Thread2();//创建三个线程Thread t1 = new Thread(t, "窗口1");Thread t2 = new Thread(t, "窗口2");Thread t3 = new Thread(t, "窗口3");//启动t1.start();t2.start();t3.start();}
}class Thread2 implements Runnable{private int ticket = 100;@Overridepublic void run() {while(ticket > 0){salOneTicket();}}//提供一个非静态加锁的售票方法public synchronized void salOneTicket(){if(ticket > 0){System.out.println(Thread.currentThread().getName() + "正在售票,票号:" + ticket);ticket--;}}
}
同步代码块
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0* 同步代码块 -- 通过创建资源类来共享数据*/
public class ThreadSafe3 {public static void main(String[] args) {//创建资源类的对象Ticket ticket = new Ticket();//通过匿名内部类创建线程对象Thread t1 = new Thread("窗口一"){@Overridepublic void run() {while(true){synchronized (ticket){ticket.sal();}}}};Thread t2 = new Thread("窗口二"){@Overridepublic void run() {while(true){synchronized (ticket){ticket.sal();}}}};Thread t3 = new Thread(new Runnable(){@Overridepublic void run() {while(true){synchronized (ticket){ticket.sal();}}}}, "窗口三");//启动t1.start();t2.start();t3.start();}
}class Ticket{private int ticket = 100;public void sal(){if(ticket > 0){System.out.println(Thread.currentThread().getName() + "正在售票,票号:" + ticket);ticket--;}else{//因为在线程的run方法内,采用了无限循环,所以此处通过抛出异常代替return结束线程throw new RuntimeException("当前无余票!");}}//提供一个方法获取私有变量public int getTicket(){return ticket;}
}
银行有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
【提示】
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0* 银行有一个账户。* 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。* 问题:该程序是否有安全问题,如果有,如何解决?* 答:存在安全问题,需要对操作共享数据的部分添加锁* 【提示】* 1,明确哪些代码是多线程运行代码,须写入run()方法* 2,明确什么是共享数据。* 3,明确多线程运行代码中哪些语句是操作共享数据的。*/
public class ThreadSafeWork {public static void main(String[] args) {//线程targetMyNewThread thread = new MyNewThread();Thread t1 = new Thread(thread, "账户A");Thread t2 = new Thread(thread, "账户B");t1.start();t2.start();}
}
//我选择采用同一个线程对象访问实例变量[目的是共享数据],然后对存款操作的方法加锁
class MyNewThread implements Runnable{private double balance = 0.0;@Overridepublic void run() {for(int i = 0; i < 3; i++){deposit();}}public synchronized void deposit(){balance += 1000;System.out.println(Thread.currentThread().getName() + "存钱一次, 余额: " + balance);}
}
我选择的方法是:给每个存钱操作后添加一个线程睡眠的动作;原因:当前线程睡眠时间足够长,那么一定是另一个线程来完成操作
(1)饿汉式没有线程安全问题
饿汉式的体现形式有两种:
//第一种
public class HungrySigle{private static final HungrySingle INSTANCE = new HungrySingle(); //对象是否定义为final都可以private HungrySingle(){}public static HungrySingle getInstance(){return INSTANCE;}
}
//第二种
public enum HungryOne{INSTANCE;
}
我们需要编写一个类进行测试
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0* 饿汉式线程问题演示*/
public class HungrySingleTest {//创建两个饿汉式线程的对象static HungrySingle hs1 = null;static HungrySingle hs2 = null;public static void main(String[] args) {//通过两个线程来获取INSTANCEThread t1 = new Thread(){@Overridepublic void run() {hs1 = HungrySingle.getInstance();}};Thread t2 = new Thread(){@Overridepublic void run() {hs2 = HungrySingle.getInstance();}};t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(hs1);System.out.println(hs2);//结果为true,说明这两个线程是一个对象System.out.println(hs1 == hs2);}
}class HungrySingle{private static final HungrySingle INSTANCE = new HungrySingle();public HungrySingle(){}//获取我们饿汉式对象的方法public static HungrySingle getInstance(){return INSTANCE;}
}
(2)懒汉式线程安全问题
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0*/
public class LazyOne {private static LazyOne instance;private LazyOne(){}//方式一:public static synchronized LazyOne getInstance1(){if(instance == null){instance = new LazyOne();}return instance;}//方式二:public static LazyOne getInstance2(){synchronized (LazyOne.class){if(instance == null){instance = new LazyOne();}return instance;}}//方式三:public static LazyOne getInstance3(){if(instance == null){synchronized (LazyOne.class){try{Thread.sleep(10);}catch (InterruptedException e){e.printStackTrace();}if(instance == null){instance = new LazyOne();}}}return instance;}}
采用内部类获取懒汉式线程的INSTANCE
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0*/
public class LazySingle {private LazySingle(){}public static LazySingle getInstance(){return Inner.INSTANCE;}public static class Inner{static final LazySingle INSTANCE = new LazySingle();}
}
我们编写代码进行测试:
import org.junit.Test;public class TestLazy{@Testpublic void test01(){LazyOne s1 = LazyOne.getInstance();LazyOne s2 = LazyOne.getInstance();System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}//把s1和s2声明在外面,是想要在线程的匿名内部类中为s1和s2赋值LazyOne s1;LazyOne s2;@Testpublic void test02(){Thread t1 = new Thread(){public void run(){s1 = LazyOne.getInstance();}};Thread t2 = new Thread(){public void run(){s2 = LazyOne.getInstance();}};//启动s1.start();s2.start();//暂停try{t1.join();t2.join();}catch(InterruptedException e){e.printStackTrace();}System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}LazySingle obj1;LazySingle obj2;@Testpublic void test03(){Thread t1 = new Thread(){public void run(){obj1 = LazySingle.getInstance();}};Thread t2 = new Thread(){public void run(){obj2 = LazySingle.getInstance();}};t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(obj1);System.out.println(obj2);System.out.println(obj1 == obj2);}
}
一旦出现死锁,那么整个程序不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续执行。
那么死锁是如何发生的呢?【同时具备以下四个条件 >> 死锁】
那么我们如何解决死锁问题呢?【死锁发生后无法干预,只能提前规避】
互斥条件基本上无法被破坏,因为线程需要通过互斥解决安全问题
可以考虑一次性申请所有所需的资源,这样就不存在等待问题
占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源
可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题
列举几个死锁案例:
案例一:
public class DeadLockTest {public static void main(String[] args) {StringBuilder s1 = new StringBuilder();StringBuilder s2 = new StringBuilder();new Thread() {public void run() {synchronized (s1) {s1.append("a");s2.append("1");try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2) {s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread() {public void run() {synchronized (s2) {s1.append("c");s2.append("3");try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1) {s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}.start();}
}
案例二:
class A {public synchronized void foo(B b) {System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了A实例的foo方法"); // ①try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用B实例的last方法"); // ③b.last();}public synchronized void last() {System.out.println("进入了A类的last方法内部");}
}class B {public synchronized void bar(A a) {System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了B实例的bar方法"); // ②try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用A实例的last方法"); // ④a.last();}public synchronized void last() {System.out.println("进入了B类的last方法内部");}
}public class DeadLock implements Runnable {A a = new A();B b = new B();public void init() {Thread.currentThread().setName("主线程");// 调用a对象的foo方法a.foo(b);System.out.println("进入了主线程之后");}public void run() {Thread.currentThread().setName("副线程");// 调用b对象的bar方法b.bar(a);System.out.println("进入了副线程之后");}public static void main(String[] args) {DeadLock dl = new DeadLock();new Thread(dl).start();dl.init();}
}
案例三:
public class TestDeadLock {public static void main(String[] args) {Object g = new Object();Object m = new Object();Owner s = new Owner(g,m);Customer c = new Customer(g,m);new Thread(s).start();new Thread(c).start();}
}
class Owner implements Runnable{private Object goods;private Object money;public Owner(Object goods, Object money) {super();this.goods = goods;this.money = money;}@Overridepublic void run() {synchronized (goods) {System.out.println("先给钱");synchronized (money) {System.out.println("发货");}}}
}
class Customer implements Runnable{private Object goods;private Object money;public Customer(Object goods, Object money) {super();this.goods = goods;this.money = money;}@Overridepublic void run() {synchronized (money) {System.out.println("先发货");synchronized (goods) {System.out.println("再给钱");}}}
}
🌔 1、我们该如何使用 ReentrantLock 呢?
class Main{//创建Lock的实例,确保多个线程共享同一个Lock的实例private final ReentrantLock lock = new ReentrantLock();public void m(){//调用lock方法,实现共享代码的锁定lock.lock();try{//保证线程安全的代码}finally{//调用unlock(),释放共享代码的锁定,因为同步代码块可能出现异常,所以将unlock放到finally中lock.unlock();}}
}
以两个窗口售票为例,演示如何使用ReentrantLock加锁和释放锁
package com.zwh.shangguigu.thread_;import java.util.concurrent.locks.ReentrantLock;/*** @author Bonbons* @version 1.0*/
public class ReentrantLockTest {public static void main(String[] args) {//创建线程target的实例Windows windows = new Windows();Thread t1 = new Thread(windows);Thread t2 = new Thread(windows);//启动t1.start();t2.start();}}
//线程target
class Windows implements Runnable{//共享实例变量[此处设置为私有不是重点,所以直接设置为公有]int ticket = 100;//创建我们的锁对象private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while(true){try{//共享部分加锁lock.lock();if(ticket > 0){try{Thread.sleep(10);}catch (InterruptedException e){e.printStackTrace();}System.out.println(ticket--);}else{break;}}finally {lock.unlock();}}}
}
🌔 2、那么Lock和ReentrantLock有什么区别呢?
🌔 1、进程之间为什么需要通信?
当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。[等待唤醒机制]
🌔 2、什么是等待唤醒机制?
对这几个方法再展开叙述一下:
我们需要知道,被通知的线程被唤醒后不一定立刻恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
案例:使用两个线程打印 1-100,采用线程1、2交替打印
class Communication implements Runnable {int i = 1;public void run() {while (true) {synchronized (this) {notify();if (i <= 100) {System.out.println(Thread.currentThread().getName() + ":" + i++);} elsebreak;try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}}
}
🌔 3、调用wait和notify需要注意哪些问题?
(1)wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
(2)wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
(3)wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。否则会报java.lang.IllegalMonitorStateException异常。
🌔 4、经典案例——生产者与消费者问题
该问题描述了多个(>=2)共享固定大小缓冲区的线程,在实际运行时会发生的问题
生产者与消费者问题中其实隐含了两个问题:
代码实现生产者与消费者:
package com.zwh.shangguigu.thread_;/*** @author Bonbons* @version 1.0*/
public class ConsumerProducerTest {public static void main(String[] args) {//创建我们资源类、生产者、消费者的实例Clerk clerk = new Clerk();Producer p1 = new Producer(clerk);Consumer c1 = new Consumer(clerk);Consumer c2 = new Consumer(clerk);//设置线程名p1.setName("生产者1");c1.setName("消费者1");c2.setName("消费者2");//启动线程p1.start();c1.start();c2.start();}
}//共享的限量资源
class Clerk{private int productNum = 0;//产品数量private static final int MAX_PRODUCT = 20;private static final int MIN_PRODUCT = 1;//增加产品public synchronized void addProduct(){//不能超过最大容量if(productNum < MAX_PRODUCT){productNum++;System.out.println(Thread.currentThread().getName() +"生产了第" + productNum + "个产品");//有产品就可以唤醒消费者,唤醒全部消费者让他们去竞争this.notifyAll();}else{//如果生产到了最大的限量,就让生产线程进入到阻塞状态,等消费线程消费掉一个之后再唤醒生产线程try{this.wait();}catch (InterruptedException e){e.printStackTrace();}}}//消耗产品public synchronized void minusProduct(){if(productNum >= MIN_PRODUCT){System.out.println(Thread.currentThread().getName() +"消费了第" + productNum + "个产品");productNum--;//唤醒生产者this.notifyAll();}else{//没有产品了消费线程就要被阻塞,等待生产线程提供了产品再唤醒消费线程try{this.wait();}catch (InterruptedException e){e.printStackTrace();}}}
}//生产者
class Producer extends Thread{private Clerk clerk;public Producer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {System.out.println("=========生产者开始生产产品========");while(true){try{Thread.sleep(40);}catch(InterruptedException e){e.printStackTrace();}//调用我们资源类的方法生产产品clerk.addProduct();}}
}//消费者
class Consumer extends Thread{private Clerk clerk;public Consumer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {System.out.println("=========消费者开始消耗产品========");while(true){try{Thread.sleep(90);}catch(InterruptedException e){e.printStackTrace();}//调用我们资源类的方法生产产品clerk.minusProduct();}}
}
🌔 5、经典面试题:sleep() 和 wait()的异同点?
🌔 6、任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?
释放锁的操作
不会释放锁的操作
🌔 1、通过实现Callable接口创建线程
与使用Runnable相比,Callable功能很强大些
Future接口(了解)
缺点:在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。
package com.zwh.shangguigu.thread_;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** @author Bonbons* @version 1.0* 采用实现Callable接口的方式来创建新线程*/
public class CallableTest {public static void main(String[] args) {//创建接口实现类的对象NumThread numThread = new NumThread();//这个接口的对象作为FutureTask实例的参数FutureTask futureTask = new FutureTask(numThread);//将FutureTask的对象作为Thread对象的参数创建线程new Thread(futureTask).start();//这个线程的call方法可以有返回值,我们就接收一下他的这个返回值try{//直接调用我们FutureTask的对象的get方法,就可以获取到call方法的返回值Object sum = futureTask.get();System.out.println("总和为: " + sum);}catch (InterruptedException e){e.printStackTrace();}catch (ExecutionException e){e.printStackTrace();}}
}//创建Callable接口的实现类
class NumThread implements Callable {//这个call相当于之前创建线程的run方法@Overridepublic Object call() throws Exception {int sum = 0;for(int i = 1; i <= 100; i++){if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}
🌔 2、使用线程池创建新线程
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
线程池相关API
代码举例:
package com.zwh.shangguigu.thread_;import java.util.concurrent.*;/*** @author Bonbons* @version 1.0*/
public class ThreadPoolTest {public static void main(String[] args) {//1、提供指定线程数量的线程池//ExecutorService是线程池接口,Executors是线程池的静态工厂,用来创建各种线程池ExecutorService service = Executors.newFixedThreadPool(10);//ThreadPoolExecutor 是一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//设置线程池中线程数量上限service1.setMaximumPoolSize(50);//2、执行指定的线程操作需要提供Runnable/Callable接口的实现类对象service.execute(new NumberThread());service.execute(new NumberThread1());try{Future future = service.submit(new NumberThread2());System.out.println("总和为:" + future.get());}catch (Exception e){e.printStackTrace();}//3、关闭连接池service.shutdown();}
}//接下来采用实现Runnable创建两个线程类,实现Callable创建一个线程类
class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0; i <= 100; i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + " : " + i);}}}
}class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0; i <= 100; i++){if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + " : " + i);}}}
}class NumberThread2 implements Callable {@Overridepublic Object call() throws Exception {int evenSum = 0;//记录偶数的和for(int i = 0; i <= 100; i++){if(i % 2 == 0){evenSum += i;}}return evenSum;}
}
上一篇:卧槽无情什么意思 ,一个男生对女生说卧槽无情是什么意思? 极速百科网 极速百科
下一篇:【Linux】文件目录操作指令(pwd、ls、cd、mkdir、rmdir、touch、cp、rm、mv、cat、echo、tail等)