跟随狂神学Java
作者:joker2yue 链接:https://github.com/Joker2Yue/Joker2Yue-Blog 来源:Github 著作权归原作者所有。商业转载请联系原作者获得授权,非商业转载请注明出处。
第二十六天:多线程
计算机科学就是有关计算机的,正如天文学就是有关望远镜的。
【狂神说Java】多线程详解_哔哩哔哩_bilibili
学习内容 线程简介
注意 :很多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以有同时执行的错觉
核心概念
线程就是独立的执行路径
在程序运行时,及时没有自己创建线程,后台也会有多个线程,如主线程,gc线程
main()称之为主线程,为系统的入口,用于执行整个程序
在一个进程中,如果开辟了多个线程。线程的运行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序是不能人为干预的
同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,如 CPU调度时间,并发控制开销 。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
实现多线程第一种方法
继承Thread类
继承Thread类
重写run()
方法
直接start()
启动线程
注意: 线程不一定立刻执行,CPU安排调度
不推荐使用,避免OOP单继承局限性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.joker_yue.javalearn.ThreadLearn;public class TestThread extends Thread { @Override public void run () { for (int i = 0 ; i < 20 ; i++) { System.out.println("我在写代码===" +i); } } public static void main (String[] args) { TestThread testThread1 = new TestThread (); testThread1.start(); for (int i = 0 ; i < 200 ; i++) { System.out.println("我在学习===" +i); } } }
网图下载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.joker_yue.javalearn.ThreadLearn;import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.MalformedURLException;import java.net.URL;public class TestThread02 extends Thread { private String url; private String name; public TestThread02 (String url, String name) { this .url = url; this .name = name; } @Override public void run () { WebDownloader downloader = new WebDownloader (); downloader.downloader(url,name); System.out.println("下载完毕,下载的文件名为" +name); } public static void main (String[] args) { TestThread02 testThread01 = new TestThread02 ("https://th.bing.com/th/id/OIP.zDCS-qQYLzpBLvOmPll6ogHaFP?w=267&h=189&c=7&r=0&o=5&dpr=2.5&pid=1.7" ,"idea1.jpg" ); TestThread02 testThread02 = new TestThread02 ("https://th.bing.com/th/id/OIP.zDCS-qQYLzpBLvOmPll6ogHaFP?w=267&h=189&c=7&r=0&o=5&dpr=2.5&pid=1.7" ,"idea2.jpg" ); TestThread02 testThread03 = new TestThread02 ("https://th.bing.com/th/id/OIP.zDCS-qQYLzpBLvOmPll6ogHaFP?w=267&h=189&c=7&r=0&o=5&dpr=2.5&pid=1.7" ,"idea3.jpg" ); testThread01.start(); testThread02.start(); testThread03.start(); } } class WebDownloader { public void downloader (String url, String name) { try { FileUtils.copyURLToFile(new URL (url), new File (name)); } catch (IOException e) { System.out.println("IO异常,下载出错" ); throw new RuntimeException (e); } } }
输出结果为
1 2 3 下载完毕,下载的文件名为idea2.jpg 下载完毕,下载的文件名为idea1.jpg 下载完毕,下载的文件名为idea3.jpg
实现多线程第二种方法
实现Runnable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.joker_yue.javalearn.ThreadLearn;public class TestThread03 implements Runnable { @Override public void run () { for (int i = 0 ; i < 20 ; i++) { System.out.println("我在写代码===" + i); } } public static void main (String[] args) { TestThread03 testThread03 = new TestThread03 (); new Thread (testThread03).start(); for (int i = 0 ; i < 200 ; i++) { System.out.println("我在学习===" + i); } } }
初识并发问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.joker_yue.javalearn.ThreadLearn;public class TestThread04 implements Runnable { private int ticketNums = 10 ; @Override public void run () { while (true ) { if (ticketNums <= 0 ) { break ; } try { Thread.sleep(200 ); } catch (InterruptedException e) { throw new RuntimeException (e); } System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票" ); } } public static void main (String[] args) { TestThread04 ticket = new TestThread04 (); new Thread (ticket,"小明" ).start(); new Thread (ticket,"小红" ).start(); new Thread (ticket,"小方" ).start(); } }
问题: 多个线程操作同一个资源的时候,线程不安全,数据紊乱
案例:龟兔赛跑
首先来个赛道距离,然后离终点越来越近
判断比赛是否结束
打印胜利者
龟兔赛跑开始
故事中乌龟是赢得,兔子需要睡觉,所以需要模拟图兔子睡觉
终于乌龟赢了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.joker_yue.javalearn.ThreadLearn;import org.apache.commons.io.input.TaggedReader;public class Race 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(10 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } if (gameOver(i)) break ; System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步" ); } } private boolean gameOver (int steps) { if (winner != null ) { return true ; } if (steps == 100 ) { winner = Thread.currentThread().getName(); System.out.println("winner is" + winner); } return false ; } public static void main (String[] args) { Race race = new Race (); new Thread (race,"兔子" ).start(); new Thread (race,"乌龟" ).start(); } }
实现Callable接口(了解即可)
实现Callable接口,需要返回值类型
重写call方法,需要抛出异常
创建目标对象
创建执行服务
提交执行
获取结果
关闭服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package com.joker_yue.javalearn.ThreadLearn;import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.MalformedURLException;import java.net.URL;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 () { WebDownloader1 downloader = new WebDownloader1 (); downloader.downloader(url,name); System.out.println("下载完毕,下载的文件名为" +name); return true ; } public static void main (String[] args) throws ExecutionException, InterruptedException { TestCallable testThread01 = new TestCallable ("https://th.bing.com/th/id/OIP.zDCS-qQYLzpBLvOmPll6ogHaFP?w=267&h=189&c=7&r=0&o=5&dpr=2.5&pid=1.7" ,"idea1.jpg" ); TestCallable testThread02 = new TestCallable ("https://th.bing.com/th/id/OIP.zDCS-qQYLzpBLvOmPll6ogHaFP?w=267&h=189&c=7&r=0&o=5&dpr=2.5&pid=1.7" ,"idea2.jpg" ); TestCallable testThread03 = new TestCallable ("https://th.bing.com/th/id/OIP.zDCS-qQYLzpBLvOmPll6ogHaFP?w=267&h=189&c=7&r=0&o=5&dpr=2.5&pid=1.7" ,"idea3.jpg" ); ExecutorService ser = Executors.newFixedThreadPool(3 ); Future<Boolean> result1 = ser.submit(testThread01); Future<Boolean> result2 = ser.submit(testThread02); Future<Boolean> result3 = ser.submit(testThread03); boolean r1 = result1.get(); boolean r2 = result2.get(); boolean r3 = result3.get(); System.out.println(r1); System.out.println(r2); System.out.println(r3); ser.shutdownNow(); } } class WebDownloader1 { public void downloader (String url, String name) { try { FileUtils.copyURLToFile(new URL (url), new File (name)); } catch (IOException e) { System.out.println("IO异常,下载出错" ); throw new RuntimeException (e); } } }
静态代理模式
代理是为了不改变原来代码的基础上增强代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.joker_yue.javalearn.ThreadLearn;public class StaticProxy { public static void main (String[] args) { You you = new You (); WeddingCom weddingCom = new WeddingCom (you); weddingCom.HappyMarry(); } } interface Marry { void HappyMarry () ; } class You implements Marry { @Override public void HappyMarry () { System.out.println("我们结婚啦" ); } } class WeddingCom implements Marry { private Marry target; public WeddingCom (Marry target) { this .target = target; } @Override public void HappyMarry () { before(); this .target.HappyMarry(); after(); } private void after () { System.out.println("结婚之后,收尾款" ); } private void before () { System.out.println("结婚之前,布置" ); } }
线程停止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.joker_yue.javalearn.ThreadLearn;public class TestThreadStop implements Runnable { private boolean flag = true ; @Override public void run () { int i = 0 ; while (flag) { System.out.println("线程正在运行" + i++); } } public void stop () { this .flag = false ; } public static void main (String[] args) { TestThreadStop tts = new TestThreadStop (); new Thread (tts).start(); for (int i = 0 ; i < 1000 ; i++) { System.out.println("main" + i); if (i == 900 ) { tts.stop(); System.out.println("线程停止了" ); } } } }
线程休眠
sleep(时间)指定当前线程阻塞的毫秒数
sleep存在异常InterruptedException
sleep时间达到后线程将进入就绪状态
sleep可以模拟网络延时,倒计时等
每一个对象都有一把锁,sleep不会释放锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.joker_yue.javalearn.ThreadLearn;import java.text.SimpleDateFormat;import java.util.Date;public class TestThreadSleep02 { public static void tenDown () throws InterruptedException { int num = 10 ; while (true ) { Thread.sleep(1000 ); System.out.println(num--); if (num <= 0 ) break ; } } 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) { throw new RuntimeException (e); } } } }
线程礼让
礼让状态,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转换成就绪状态
让CPU重新调度,礼让不一定成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.joker_yue.javalearn.ThreadLearn;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()+"线程停止执行" ); } }
线程强制执行
Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
可以想象成插队
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.joker_yue.javalearn.ThreadLearn;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 < 500 ; i++) { if (i == 200 ) { thread.join(); } System.out.println("main" + i); } } }
线程状态观测
Thread.State
线程状态,线程可以处于以下状态之一
一个线程可以在给定的时间点处于一个状态,这些状态时不反应给任何操作系统线程状态的虚拟机状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.joker_yue.javalearn.ThreadLearn;public class TestState { public static void main (String[] args) throws InterruptedException { Thread thread = new Thread (() -> { for (int i = 0 ; i < 5 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } System.out.println("////////" ); }); Thread.State state = thread.getState(); System.out.println(state); thread.start(); state = thread.getState(); System.out.println(state); while (state != Thread.State.TERMINATED){ Thread.sleep(100 ); state = thread.getState(); System.out.println(state); } } }
线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围从1-10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
使用以下方式查看和改变线程优先级
getPriority().
setPriority(int xx);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.joker_yue.javalearn.ThreadLearn;public class TestPriority extends Thread { 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); 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()); } }
注意:优先级高的不一定先跑,只是大概率先跑
守护(daemon)线程
线程分为用户线程 和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不必等待守护线程执行完毕
如,后台记录操作日志,监控内存,垃圾回收等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.joker_yue.javalearn.ThreadLearn;public class TestDaemon { public static void main (String[] args) { God god = new God (); Us us = new Us (); Thread thread = new Thread (god); thread.setDaemon(true ); thread.start(); new Thread (us).start(); } } class God implements Runnable { @Override public void run () { while (true ) { System.out.println("上帝保佑你" ); } } } class Us implements Runnable { @Override public void run () { for (int i = 0 ; i < 36500 ; i++) { System.out.println("我一生都在开心的活着" ); } System.out.println("=============GoodBye World==========" ); } }
线程同步机制
并发:同一个对象被多个线程同时操作
比如:上万人抢同一张票
形成条件:队列+锁
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制Synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
一个线程持有锁会导致其他所有需要锁的线程挂起
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
如果一个优先级高的进程等待一个优先级低的进程释放锁,会导致优先级倒置,引起性能问题
例子:不安全的买票,因为可能出现负数或者抢到同一张票
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.joker_yue.javalearn.ThreadLearn;public class UnsafeBuyTicket { 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 { int ticketNums = 10 ; boolean flag = true ; @Override public void run () { while (flag) { try { buy(); } catch (InterruptedException e) { throw new RuntimeException (e); } } } private void buy () throws InterruptedException { if (ticketNums <= 0 ) { flag = false ; return ; } Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums--); } }
例子:不安全的取钱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package com.joker_yue.javalearn.ThreadLearn;public class UnsafeBank { public static void main (String[] args) { Account account = new Account (100 ,"结婚基金" ); Drawing you = new Drawing (account,50 ,"你" ); Drawing gf = new Drawing (account,100 ,"girlFriend" ); you.start(); gf.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 drawing, String name) { super (name); this .account = account; this .drawingMoney = drawing; } @Override public void run () { if (account.money - drawingMoney < 0 ) { System.out.println(Thread.currentThread().getName() + "钱不够了" ); return ; } try { Thread.sleep(1000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } account.money = account.money - drawingMoney; nowMoney = nowMoney + drawingMoney; System.out.println(account.name+"余额为" +account.money); System.out.println(this .getName()+"手里的钱" +nowMoney); } }
例子:不安全的集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.joker_yue.javalearn.ThreadLearn;import java.util.ArrayList;import java.util.List;public class UnsafeList { public static void main (String[] args) throws InterruptedException { List<String> list = new ArrayList <>(); for (int i = 0 ; i < 10000 ; i++) { new Thread (() -> { list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(1000 ); System.out.println(list.size()); } }
同步方法
由于我们通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:
synchronized方法和synchronized块
同步方法:public synchronized void method(int args){}
synchronized方法控制“对象”的访问,每个对象对应着一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的锁才能获得这个锁,继续执行。
缺陷:若将一个较大的方法申明为synchronized将会影响效率
同步方法弊端
同步块
同步块:synchronized(Obj){}
Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[见反射]
同步监视器的执行过程
第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,解锁同步监视器
第二个线程访问,发现同步监视器没有锁,然后锁定并访问
将上面的改成线程安全的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.joker_yue.javalearn.ThreadLearn;public class SafeBuyTicket { 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 { int ticketNums = 10 ; boolean flag = true ; @Override public void run () { while (flag) { try { buy(); } catch (InterruptedException e) { throw new RuntimeException (e); } } } private synchronized void buy () throws InterruptedException { if (ticketNums <= 0 ) { flag = false ; return ; } Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums--); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 package com.joker_yue.javalearn.ThreadLearn;public class SafeBank { public static void main (String[] args) { Account account = new Account (1000 , "结婚基金" ); Drawing you = new Drawing (account, 50 , "你" ); Drawing gf = new Drawing (account, 100 , "girlFriend" ); you.start(); gf.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 drawing, String name) { super (name); this .account = account; this .drawingMoney = drawing; } @Override public void run () { synchronized (account) { if (account.money - drawingMoney < 0 ) { System.out.println(Thread.currentThread().getName() + "钱不够,取不了" ); return ; } try { Thread.sleep(1000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } account.money = account.money - drawingMoney; nowMoney = nowMoney + drawingMoney; System.out.println(account.name + "余额为" + account.money); System.out.println(this .getName() + "手里的钱:" + nowMoney); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.joker_yue.javalearn.ThreadLearn;import java.util.ArrayList;import java.util.List;public class SafeList { public static void main (String[] args) throws InterruptedException { List<String> list = new ArrayList <>(); for (int i = 0 ; i < 10000 ; i++) { new Thread (() -> { synchronized (list) { list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(1000 ); System.out.println(list.size()); } }
CopyOnWriteArrayList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.joker_yue.javalearn.ThreadLearn;import java.util.concurrent.CopyOnWriteArrayList;public class TestJUC { public static void main (String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList <String>(); for (int i = 0 ; i < 10000 ; i++) { new Thread (()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } System.out.println(list.size()); } }
在java.util.concurrent包下的都是线程安全的
死锁
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁 ”时,就可能会发生“死锁”的问题
死锁:公司招有工作经验的毕业生,毕业生没有工作哪来的工作经验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package com.joker_yue.javalearn.ThreadLearn;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 Lipstick lipstick = new Lipstick (); static Mirror mirror = new Mirror (); int choice; String girlName; MakeUp(int choice, String girlName) { this .choice = choice; this .girlName = girlName; } @Override public void run () { try { makeup(); } catch (InterruptedException e) { throw new RuntimeException (e); } } 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 + "获得口红的锁" ); } } } } }
解决方法:将获得锁放到外面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package com.joker_yue.javalearn.ThreadLearn;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 Lipstick lipstick = new Lipstick (); static Mirror mirror = new Mirror (); int choice; String girlName; MakeUp(int choice, String girlName) { this .choice = choice; this .girlName = girlName; } @Override public void run () { try { makeup(); } catch (InterruptedException e) { throw new RuntimeException (e); } } 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 + "获得口红的锁" ); } } } }
产生死锁的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放 。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺 。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系 。
注意:只需要破坏其中一个或多个即可避免死锁
Lock锁
从JDK1.5开始,Java提供了更强大的线程同步机制–通过显示定义同步锁队象来实现同步,同步锁使用Lock对象充当
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象
ReentrantLock类(可重 入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
class A{
private final ReentrantLock lock = new ReentrantLock();//确保安全不会被修改
public void m(){
lock.lock();
try{
//确保线程安全的代码
}finally{
lock.unlock();
//如果同步代码有异常,要将unlock写到finally语句块
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 * Lock是显式锁(手动开启和关闭,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放 * Lock只有代码块锁,synchronized有哦代码块锁和方法锁 * 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多子类) * 优先使用顺序: * Lock > 同步代码块 (已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外) #### 线程协作(生产者消费者模式) --- 线程通信 * 应用场景:生产者和消费者问题 * 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费 * 如果仓库中没有产品,则生产者将产品放入残酷,否则停止生产并等待,直到仓库中的产品被消费者取走为止 * 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止 <img src="images/跟随狂神学Java-26/image-20230417211358349.png" alt="image-20230417211358349" style="zoom: 50%;" /> * 这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件 * 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又要马上通知消费者消费 * 对于消费者,在消费之后,要通知生产者已经结束消费,需要产生新的产品以供消费 * 在生产者消费者问题中,仅有synchronized是**不够**的 * synchronized可阻止并发更新同一个共享资源,实现了同步 * synchronized不能用来实现不同线程之间的消息传递(通信) * Java提供了几个方法解决线程之间的通信问题 * | 方法名 | 作用 | | ------------------ | ------------------------------------------------------------ | | wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 | | wait(long timeout) | 指定等待的毫秒数 | | notify() | 唤醒一个正在处于等待状态的线程 | | notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 | * 注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegaIMonitorStateException * 解决方式1 并发协作模型:“生产者/消费者模式”-->管程法 * 生产者:负责生产数据的模块,可能是方法,对象,线程,进程 * 消费者:负责处理数据的模块,可能是方法,对象,线程,进程 * 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区” **生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据** <img src="images/跟随狂神学Java-26/image-20230420183252471.png" alt="image-20230420183252471" style="zoom: 67%;" /> * 解决方式2 并发协作模型:“生产者/消费者模式”-->信号灯法 * 应用场景 <img src="images/跟随狂神学Java-26/image-20230420183519492.png" alt="image-20230420183519492" style="zoom: 33%;" /> --- ##### 管程法 ~~~java package com.joker_yue.javalearn.ThreadLearn; //测试生产者消费者模型--利用缓冲区解决:管程法 //生产者,消费者,产品,缓冲区 public class TestPC { public static void main(String[] args) { SyncContainer container = new SyncContainer(); new Productor(container).start(); new Consumer(container).start(); } } //生产者 class Productor extends Thread { SyncContainer container; public Productor(SyncContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { // 打印是两个线程同时进行的,把打印放到同步方法里就会显示0~9只鸡 System.out.println("生产了" + i + "只鸡"); container.push(new Chicken(i)); } } } //消费者 class Consumer extends Thread { SyncContainer container; public Consumer(SyncContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { container.push(new Chicken(i)); System.out.println("消费了-->" + container.pop().id + "只鸡"); } } } // 产品 class Chicken { int id;//产品编号 public Chicken(int id) { this.id = id; } } //缓冲区 class SyncContainer { Chicken[] chickens = new Chicken[10]; //需要一个容器大小 int count = 0; //容器计数器 //生产者放入产品 public synchronized void push(Chicken chicken) { //如果容器满了,就需要等待消费者消费 if (count == chickens.length) { try { this.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } //如果没有满,就需要丢入产品 chickens[count] = chicken; count++; //可以通知消费者消费了 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop() { //判断能否消费 if (count == 0) { //等待生产者生产,消费者等待 try { this.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } //如果可以消费,消费 count--; Chicken chicken = chickens[count]; //吃完了,通知生产者生产 this.notifyAll(); return chicken; } }
线程池
背景:机场创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大 。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用,类似生活中的公共交通工具。
好处:
提高相应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理(…)
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
JDK5.0开始提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口,常见子类:ThreadPoolExecutor
void executor(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future <T> submit(Callable<T> task)
:执行任务,有返回值,一般用来执行Callable
void shutdown()
: 关闭连接池
Executors : 工具类 、 线程池的工厂类 , 用于创建并返回不同类型的线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.joker_yue.javalearn.ThreadLearn;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestPool01 { public static void main (String[] args) { 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() ); } }
总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.joker_yue.javalearn.ThreadLearn;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class ThreadNew { public static void main (String[] args) throws ExecutionException, InterruptedException { new MyThread1 ().start(); new Thread (new MyThread2 ()).start(); FutureTask<Integer> futureTask = new FutureTask <Integer>(new MyThread3 ()); new Thread (futureTask).start(); Integer integer = futureTask.get(); System.out.println(integer); } } class MyThread1 extends Thread { @Override public void run () { System.out.println("继承Thread,重写run" ); } } class MyThread2 implements Runnable { @Override public void run () { System.out.println("继承Runnable接口,重写run" ); } } class MyThread3 implements Callable { @Override public Integer call () throws Exception { System.out.println("继承Callable,重写call" ); return 100 ; } }