目录
1、进程与线程
-
说起进程,就不得不说一下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
-
而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位~
-
通常在一个进程中可以包含多个线程,当然一个进程至少有一个线程。不然没有存在的意义。线程是CPU调度和执行的单位~
注意:很多多线程都是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉~
核心概念:
-
线程就是独立的执行路径;
-
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
-
main()称之为主线程,为系统的入口,用于执行整个程序;
-
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度=,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预;
-
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
-
每个线程在自己的工作内存交互,内存控制不会造成数据不一致。
2、创建线程的方法
1、继承Thread类
package com.study.demo01; //创建线程方式一:继承thread类,重写run方法,调用start开启线程 public class TestThread extends Thread{ @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码-"+i); } } public static void main(String[] args) { //main线程 主线程 //创建一个线程对象 TestThread testThread = new TestThread(); //调用start()方法开启线程 testThread.start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习多线程-"+i); } } }
运行结果:
我在学习多线程-0 我在学习多线程-1 我在看代码-0 我在学习多线程-2 我在看代码-1 我在看代码-2 我在学习多线程-3 我在学习多线程-4 我在学习多线程-5 我在学习多线程-6 我在学习多线程-7 我在学习多线程-8 我在学习多线程-9 我在学习多线程-10 我在学习多线程-11 我在看代码-3 我在学习多线程-12 我在看代码-4 我在学习多线程-13 我在看代码-5 我在学习多线程-14 我在看代码-6 我在学习多线程-15 我在看代码-7 我在看代码-8 我在看代码-9 我在看代码-10 我在看代码-11 我在看代码-12 我在看代码-13 我在看代码-14 我在学习多线程-16 我在学习多线程-17 我在学习多线程-18 我在学习多线程-19 我在看代码-15 我在看代码-16 我在看代码-17 我在看代码-18 我在看代码-19
总结:线程开启不一定立即执行,由CPU调度执行~
2、实现runnable接口,重写run方法~
package com.study.demo01; //创建线程方法2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类。调用start方法 public class TestThread03 implements Runnable{ @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码~"+i); } } public static void main(String[] args) { //创建runnable接口实现类的对象 TestThread03 testThread03 = new TestThread03(); //创建线程对象,通过线程对象来开启我们的线程 代理 Thread thread = new Thread(testThread03); thread.start(); //new Thread(testThread03).start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习多线程~"+i); } } }
运行结果:
我在学习多线程~0 我在学习多线程~1 我在学习多线程~2 我在学习多线程~3 我在学习多线程~4 我在学习多线程~5 我在学习多线程~6 我在学习多线程~7 我在学习多线程~8 我在学习多线程~9 我在学习多线程~10 我在学习多线程~11 我在学习多线程~12 我在看代码~0 我在看代码~1 我在看代码~2 我在看代码~3 我在看代码~4 我在看代码~5 我在看代码~6 我在看代码~7 我在看代码~8 我在看代码~9 我在看代码~10 我在看代码~11 我在学习多线程~13 我在看代码~12 我在看代码~13 我在看代码~14 我在看代码~15 我在看代码~16 我在看代码~17 我在看代码~18 我在看代码~19 我在学习多线程~14 我在学习多线程~15 我在学习多线程~16 我在学习多线程~17 我在学习多线程~18 我在学习多线程~19
并发问题
package com.study.demo01; //多线程同时操作同一对象 //买火车票的例子 public class TestThread04 implements Runnable{ //票数 private int ticketNum = 10; @Override public void run() { while(true){ if (ticketNum<=0){ break; } //模拟延时~ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNum--+"票~"); } } public static void main(String[] args) { TestThread04 ticket = new TestThread04(); new Thread(ticket,"张三").start(); new Thread(ticket,"李四").start(); new Thread(ticket,"王五").start(); } }
运行结果:
王五-->拿到了第10票~ 张三-->拿到了第9票~ 李四-->拿到了第10票~ 李四-->拿到了第8票~ 张三-->拿到了第7票~ 王五-->拿到了第6票~ 李四-->拿到了第4票~ 王五-->拿到了第3票~ 张三-->拿到了第5票~ 张三-->拿到了第2票~ 李四-->拿到了第0票~ 王五-->拿到了第1票~
发现问题:多线程操作同一资源的情况下,线程不安全,数据紊乱!
两种方法对比:
-
继承Thread类
-
子类继承Thread类具备多线程能力
-
启动线程:子类对象.start()
-
不建议使用:避免OOP单继承局限性
-
-
实现Runnable接口:
-
实现接口Runable就有多线程能力
-
启动线程:传入目标对象+Thread对象.start()
-
推荐使用:避免单继承局限性,灵活方便,方便同一对象被多个线程使用。
-
龟兔赛跑实例:
package com.study.demo01; public class TestRace implements Runnable{ //胜利者 private static String winner; @Override public void run() { for (int i = 0; i <= 100; i++) { if (Thread.currentThread().getName().equals("兔子")){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } //判断比赛是否结束 boolean flag = gameOver(i); //如果比赛结束,就停止程序 if(flag){ break; } System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步~"); } } //判断是否完成比赛 private boolean gameOver(int steps){ for (int i = 0; i < 100; i++) { //判断比赛是否有胜利者 if (winner!=null){//已经存在胜利者 return true; } else{ if (steps>=100){ winner = Thread.currentThread().getName(); System.out.println("winner is "+winner); return true; } } } return false; } public static void main(String[] args) { TestRace race = new TestRace(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); } }
3、实现Callable接口(了解即可)
Callable的好处:
-
可以定义返回值
-
可以抛出异常
package com.study.demo02; import com.study.demo01.TestTheard02; import java.util.concurrent.*; public class TestCallable implements Callable<Boolean> { private String url;//网络图片地址 private String name;//保存的文件名 public TestCallable(String url,String name) { this.url = url; this.name = name; } //下载图片到执行体 @Override public Boolean call() { WebDownloader2 webDownloader = new WebDownloader2(); webDownloader.downloader1(url,name); System.out.println("下载了文件名为:"+name); return true; } public static void main(String[] args) { TestCallable t1 = new TestCallable("https://i2.hdslb.com/bfs/archive/41575b1afec5305fa…a.jpg@320w_200h_1c_!web-space-index-myseries.avif","1.jpg"); TestCallable t2 = new TestCallable("https://i0.hdslb.com/bfs/archive/756f7695bcdd43e45…8.jpg@320w_200h_1c_!web-space-index-myseries.avif","2.jpg"); TestCallable t3 = new TestCallable("https://i2.hdslb.com/bfs/archive/0ae34a0c6347a0fd2…6.jpg@320w_200h_1c_!web-space-index-myseries.avif","3.jpg"); //创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); //获取结果 // boolean rs1 = r1.get(); // boolean rs2 = r2.get(); // boolean rs3 = r3.get(); // //关闭服务 ser.shutdownNow(); } } class WebDownloader2{ //下载方法 public void downloader1(String url,String name){ // } }
静态代理
实例:你结婚,婚庆公司代理~
package com.study.demo02; import java.awt.event.WindowAdapter; public class StaticProxy { public static void main(String[] args) { You you = new You();//你要结婚 new Thread(()->System.out.println("我爱你~")).start(); new WeddingCompany(new You()).HappyMarry(); } } interface Marry{ void HappyMarry(); } //真实角色,你去结婚 class You implements Marry{ @Override public void HappyMarry() { System.out.println("你要结婚了~祝福"); } } //代理角色,帮助结婚 class WeddingCompany implements Marry{ //代理的角色-->真是目标角色 private Marry target; public WeddingCompany(Marry target) { this.target = target; } @Override public void HappyMarry() { BEfore(); this.target.HappyMarry(); After(); } private void BEfore(){ System.out.println("婚前布置工作~"); } private void After(){ System.out.println("婚后收尾工作~"); } }
运行结果:
我爱你~ 婚前布置工作~ 你要结婚了~祝福 婚后收尾工作~
静态代理模式总结:
-
特点
-
真实对象和代理对象都要实现同一接口
-
代理对象要代理真实角色
-
-
好处
-
代理对象可以做很多真实对象做不了的事
-
真实对象专注做自己的事情
-
lambda表达式:
package com.study.demo02; public class Testlambda2 { public static void main(String[] args) { ILove love = (int a)->{ System.out.println("I love you -->"+a); }; love.love(520); } } interface ILove{ void love(int a); }
lambda表达式与其他表达式对比:
package com.study.demo02; public class TestLambda { //3.静态内部类 static class Like2 implements Ilike{ @Override public void lambda() { System.out.println("I like lambda2~"); } } public static void main(String[] args) { Ilike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); //4.局部内部类 class Like3 implements Ilike{ @Override public void lambda() { System.out.println("I like lambda3~"); } } like = new Like3(); like.lambda(); //5.匿名内部类,没有类的名称,必须借助接口或者父类~ like = new Ilike(){ @Override public void lambda() { System.out.println("I like lambda4~"); } }; like.lambda(); //6.用lambda简化 like = ()->{ System.out.println("I like lambda5~"); }; like.lambda(); } } //1.定义一个接口 interface Ilike{ void lambda(); } //2.实现类 class Like implements Ilike{ @Override public void lambda() { System.out.println("I like lambda1~"); } }
运行结果:
I like lambda1~ I like lambda2~ I like lambda3~ I like lambda4~ I like lambda5~
lambda表达式总结:
lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码块包裹,前提是接口为函数式接口;多个参数也可去掉参数类型,要去掉就都去掉,必须加上括号~
3、线程状态
1、停止线程
package com.study.state; public class TestStop implements Runnable{ //1.设置一个标志位 private boolean flag = true; @Override public void run() { int i = 0; while(flag){ System.out.println("run......Thread"+i); } } //2.设置一个公开的方法停止线程,转换标志位 public void stop(){ this.flag = false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 1000; i++) { System.out.println("main"+i); if(i==900){ //调用stop方法切换标志位。让线程停止 testStop.stop(); System.out.println("线程停止了"); } } } }
当i执行到900以后时,标志位变为false,System.out.println("run......Thread"+i);将不再执行!
注意:
-
建议线程正常停止-->利用次数,不建议死循环
-
建议使用标志位-->设置一个标志位
-
不要使用stop或者destroy等过时或者JDK不建议使用的方法
2、线程休眠
//模拟倒计时 package com.study.state; public class TestSleep { public static void main(String[] args) { try { tenDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void tenDown() throws InterruptedException { int num = 10; while(true){ Thread.sleep(1000); System.out.println(num--); if(num<=0){ break; } } } }
动态打印系统时间
package com.study.state; import java.text.SimpleDateFormat; import java.util.Date; public class TestSleep { public static void main(String[] args) { //打印当前系统的时间 Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间 while(true){ try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime = new Date(System.currentTimeMillis());//更新当前时间 } catch (InterruptedException e) { e.printStackTrace(); } } } }
3、线程礼让
package com.study.state; public class TestYield { public static void main(String[] args) { MyYield myYield = new MyYield(); new Thread(myYield,"a").start(); new Thread(myYield,"b").start(); } } class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield();//礼让 System.out.println(Thread.currentThread().getName()+"线程停止执行"); } }
注意:礼让不一定成功,看cpu心情~
4、线程合并
package com.study.state; public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("线程Vip来了"+i); } } public static void main(String[] args) throws InterruptedException { //启动线程 TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); //主线程 for (int i = 0; i < 1000; i++) { if (i==100){ thread.join();//插队 } System.out.println("main"+i); } } }
5、观测线程状态
运行时可能进行休眠进入阻塞状态或者进入死亡状态
package com.study.state; //观察测试线程的状态 public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ for (int i = 0; i < 2; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("///"); }); //观察状态 Thread.State state = thread.getState(); System.out.println(state);//New //观察启动后 thread.start();//启动线程 state = thread.getState(); System.out.println(state);//Run while (state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态 Thread.sleep(100); state = thread.getState();//更新线程状态 System.out.println(state); } } }
运行结果
NEW RUNNABLE TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING TIMED_WAITING /// TERMINATED
6、线程优先级
package com.study.state; public class TestPriority { public static void main(String[] args) { //主线程默认优先级 System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); Thread t6 = new Thread(myPriority); //先设置优先级 t1.start(); t2.setPriority(1); t2.start(); t3.setPriority(4); t3.start(); t4.setPriority(Thread.MAX_PRIORITY);//最大优先级10 t4.start(); t5.setPriority(8); t5.start(); t6.setPriority(7); t6.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); } }
7、守护线程
package com.study.state; //测试守护线程 //上帝守护你 public class TestDaemon { public static void main(String[] args) { God god = new God(); You you = new You(); Thread thread = new Thread(god); thread.setDaemon(true);//默认时false表示用户线程,正常的线程都是用户线程 thread.start(); new Thread(you).start(); } } //上帝 class God implements Runnable{ @Override public void run() { while (true){ System.out.println("上帝保佑你~"); } } } //你 class You implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("快乐每一天~"); } System.out.println("=======Goodbye! world======="); } }
总结:在用户线程(你)执行完成后,守护线程(上帝)也会随之结束,尽管上帝线程执行条件恒为真~
8、线程同步
利用队列和锁实现线程同步,详见同步方法及同步块
4、三大不安全案例
1、买票案例
package com.study.Syn; public class UnsafetyBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"苦逼的我").start(); new Thread(station,"牛逼的你们").start(); new Thread(station,"可恶的黄牛党").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNums = 10; boolean flag = true;//外部停止方式 @Override public void run() { //买票 while(flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void buy() throws InterruptedException { //判断是否有票 if (ticketNums<=0){ flag = false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--); } }
问题:出现不同的人买到同一张票!
2、取钱案例
package com.study.Syn; public class UnsafetyBank { public static void main(String[] args) { //账户 Account account = new Account(100, "结婚基金"); Drawing zhangsan = new Drawing(account, 50, "张三"); Drawing lisi = new Drawing(account, 100, "李四"); zhangsan.start(); lisi.start(); } } class Account{ int money;//余额 String name;//卡名 public Account(int money,String name) { this.money = money; this.name = name; } } class Drawing extends Thread{ Account account;//账户 //取了多少钱 int drawingMoney; //现在手里有多少钱 int nowMoney; public Drawing(Account account,int drawingMoney,String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } //取钱 @Override public void run() { //判断有没有钱 if (account.money-drawingMoney<0){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了~"); return; } //sleep放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额=余额-你取的钱 account.money = account.money - drawingMoney; //你手里的钱 nowMoney = nowMoney + drawingMoney; System.out.println(account.name+"余额为:"+account.money); //Thread.currentThread().getName()=this.getName() System.out.println(this.getName()+"手里的钱:"+nowMoney); } }
问题:余额出现负数
3、ArrayList案例
package com.study.Syn; import java.util.ArrayList; //线程不安全的集合 public class UnsafetyLisr { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
问题:集合大小与预想不一致
5、线程同步方法
1、synchronized方法
买票案例改进
package com.study.Syn; //不安全的买票 //线程不安全,有负数~ public class UnsafetyBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"苦逼的我").start(); new Thread(station,"牛逼的你们").start(); new Thread(station,"可恶的黄牛党").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNums = 10; boolean flag = true;//外部停止方式 @Override public void run() { //买票 while(flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } //synchronized同步方法,锁的是this private synchronized void buy() throws InterruptedException { //判断是否有票 if (ticketNums<=0){ flag = false; return; } //模拟延时 Thread.sleep(1000); //买票 System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--); } }
2、synchronized同步块
取钱案例改进
package com.study.Syn; public class UnsafetyBank { public static void main(String[] args) { //账户 Account account = new Account(100, "结婚基金"); Drawing zhangsan = new Drawing(account, 50, "张三"); Drawing lisi = new Drawing(account, 100, "李四"); zhangsan.start(); lisi.start(); } } class Account{ int money;//余额 String name;//卡名 public Account(int money,String name) { this.money = money; this.name = name; } } class Drawing extends Thread{ Account account;//账户 //取了多少钱 int drawingMoney; //现在手里有多少钱 int nowMoney; public Drawing(Account account,int drawingMoney,String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } //取钱 @Override public void run() { synchronized (account){ //判断有没有钱 if (account.money-drawingMoney<0){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了~"); return; } //sleep放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额=余额-你取的钱 account.money = account.money - drawingMoney; //你手里的钱 nowMoney = nowMoney + drawingMoney; System.out.println(account.name+"余额为:"+account.money); //Thread.currentThread().getName()=this.getName() System.out.println(this.getName()+"手里的钱:"+nowMoney); } } }
ArrayList案例改进
package com.study.Syn; import java.util.ArrayList; //线程不安全的集合 public class UnsafetyLisr { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
6、JUC-CopyOnWriteArrayList
测试集合(不使用synchronized同步代码块方法)
package com.study.Syn; import java.util.concurrent.CopyOnWriteArrayList; //测试JUC安全类型的集合 public class TestJUC { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
7、死锁和锁
1、死锁
package com.study.lock; //死锁:多个线程互相抱着对方需要的资源,然后形成僵持 public class DeadLock { public static void main(String[] args) { MakeUp g1 = new MakeUp(0,"灰姑凉"); MakeUp g2 = new MakeUp(1,"白雪公主"); g1.start(); g2.start(); } } //口红 class LipStick{ } //镜子 class Mirror{ } class MakeUp extends Thread{ //需要的资源只有一份,用static修饰来保证只有一份 static LipStick lipStick = new LipStick(); static Mirror mirror = new Mirror(); int choice;//选择 String girlName;//使用化妆品的人 MakeUp(int choice,String girlName) { this.girlName = girlName; this.choice = choice; } @Override public void run() { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } //化妆,互相持有对方想得到的锁,就是需要拿到对象的资源~ private void makeup() throws InterruptedException { if(choice == 0){ synchronized (lipStick){//获得口红的锁 System.out.println(this.girlName+"获得口红的锁~"); Thread.sleep(1000); synchronized (mirror) {//获得镜子的锁 System.out.println(this.girlName + "获得镜子的锁~"); } } }else { synchronized (mirror) {//获得镜子的锁 System.out.println(this.girlName + "获得镜子的锁~"); Thread.sleep(2000); synchronized (lipStick) {//获得口红的锁 System.out.println(this.girlName + "获得口红的锁~"); } } } } }
运行结果为:
灰姑凉获得口红的锁~ 白雪公主获得镜子的锁~
此时变为了死锁,程序无法正常运行!
将makeup()函数改成如下:
private void makeup() throws InterruptedException { if(choice == 0){ synchronized (lipStick){//获得口红的锁 System.out.println(this.girlName+"获得口红的锁~"); Thread.sleep(1000); } synchronized (mirror) {//获得镜子的锁 System.out.println(this.girlName + "获得镜子的锁~"); } }else { synchronized (mirror) {//获得镜子的锁 System.out.println(this.girlName + "获得镜子的锁~"); Thread.sleep(2000); } synchronized (lipStick) {//获得口红的锁 System.out.println(this.girlName + "获得口红的锁~"); } } }
运行结果为:
灰姑凉获得口红的锁~ 白雪公主获得镜子的锁~ 白雪公主获得口红的锁~ 灰姑凉获得镜子的锁~
此时程序运行是没有问题的
2、锁
package com.study.lock; import java.util.concurrent.locks.ReentrantLock; public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); } } class TestLock2 implements Runnable{ int ticketNums = 10; @Override public void run() { while(true){ if (ticketNums>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); } else{ break; } } } }
此时会出现多个资源操作同一对象,是不安全的!
依据锁的使用方法,修改TestLock2类如下
class TestLock2 implements Runnable{ int ticketNums = 10; //定义lock锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try{ lock.lock();//加锁 if (ticketNums>=0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); } else{ break; } }finally { //解锁 lock.unlock(); } } } }
此时,代码是正常运行的~
总结对比
8、线程通信
对于线程通信,Java提供了几个解决方法:
1、解决方法一:管程法
生产者消费者实例:利用缓冲区解决--管程法
package com.study.HighLevel; public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } } //生产者 class Productor extends Thread{ SynContainer container; public Productor(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("生产了-->"+i+"只鸡"); container.push(new Chicken(i)); } } } //消费者 class Consumer extends Thread{ SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了-->"+container.pop().id+"只鸡"); } } } //产品 class Chicken{ int id;//编号 public Chicken(int id) { this.id = id; } } //缓冲区 class SynContainer{ //需要一个容器大小 Chicken[] chickens =new Chicken[10]; //容量计数器 int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken){ //如果容器满了,就需要等待消费者消费 if (count==chickens.length){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满,我们就丢入产品 chickens[count] = chicken; count++; //通知消费者消费 this.notify(); } //消费者消费产品 public synchronized Chicken pop(){ //判断能否消费 if (count==0){ //等待生产者生产 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Chicken chicken = chickens[count]; //吃完就通知生产者生产 this.notify(); return chicken; } }
2、解决方法二:信号灯法
生产者消费者实例:信号灯法,标志位解决
package com.study.HighLevel; public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new Player(tv).start(); new Watcher(tv).start(); } } class Player extends Thread{ TV tv; public Player(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if(i%2 == 0){ this.tv.play("快乐大本营播放中"); }else{ this.tv.play("抖音:记录美好生活"); } } } } class Watcher extends Thread{ TV tv; public Watcher(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } class TV{ String voice;//表演的节目 boolean flag = true; //表演 public synchronized void play(String voice){ if(!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了:"+voice); //通知观众观看 this.notify(); this.voice = voice; this.flag = !this.flag; } //观看 public synchronized void watch(){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观看了:"+voice); //通知演员表演 this.notify(); this.flag = !this.flag; } }
9、线程池
线程池使用背景:
线程池使用方法:
重点:ExecutorService、Executor
package com.study.HighLevel; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestPool { public static void main(String[] args) { //1.创建服务,创建线程池 //newFixedThreadPool 参数为:线程池大小 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭连接 service.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }