多线程学习
【狂神说Java】多线程详解_哔哩哔哩_bilibili
这个只是简单的学了一下怎么用,几个小demo,没有深入的讲。
1. 线程简介 线程,进程,多线程
并发:同时发生,在一个时间段内执行,不一定是同一时间点
并行:同时执行,在一个时间点上有多个线程执行
Process 和 Thread 三个概念,程序、进程、线程:
程序:静态代码
进程:程序的一次执行过程
线程:一个进程可以包含多个线程,线程是CPU调度和执行的单位 。
很多多线程是模拟出来的,真正的多线程是指有多个CPU。模拟的线程只是一个CPU切换的很快产生了多线程的错觉。其实就是并发和并行的区别。
一些要点
main()
函数就是主线程;
默认的话有主线程、GC线程等;
线程因为调度,会代来开销
资源抢夺问题,需要并发控制
2. 线程实现(重点) 2.1 三种实现方式
继承Thread类
实现Runnable接口
实现Callable接口
2.1.1继承Thread类 Demo
自定义线程类,继承Thread
重写 run()
方法
调用 start()
方法启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class TestThread1 extends Thread { @Override public void run () { for (int i = 0 ; i < 2000 ; i++) { System.out.println("run方法线程" +i); } } public static void main (String[] args) { TestThread1 testThread1 = new TestThread1(); testThread1.start(); for (int i = 0 ; i < 2000 ; i++) { System.out.println("MMMMMMMMMMMMMM" +i); } } }
PS: 线程开启不一定立即执行,由CPU调度执行
实例:图片下载 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 public class TestThread2 extends Thread { private String url; private String name; public TestThread2 (String url, String name) { this .name = name; this .url = url; } public static void main (String[] args) { TestThread2 testThread1 = new TestThread2("https://commons.apache.org/proper/commons-io/images/commons-logo.png" , "1.png" ); TestThread2 testThread2 = new TestThread2("https://commons.apache.org/proper/commons-io/images/io-logo-white.png" , "2.png" ); TestThread2 testThread3 = new TestThread2("http://www.apache.org/events/current-event-125x125.png" , "3.png" ); testThread1.start(); testThread2.start(); testThread3.start(); } @Override public void run () { WebDownload webDownload = new WebDownload(); webDownload.downloader(url, name); System.out.println("下载了文件,名为" + name); } class WebDownload { public void downloader (String url, String name) { String path = "F:/AWork/Project/2021/JavaDataStructure/src/com/songx64/baselearn/threadlearn/kuangThread/" ; try { FileUtils.copyURLToFile(new URL(url), new File(path+name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,下载出错" ); } } } }
需要Apache的一个包 commons-io
:Commons IO – Commons IO Overview (apache.org) ,用于文件下载
下载后Add to Library。
2.2 Runnable实现线程
创建类实现 Runnable
接口
创建 类的对象
将对象传入 Thread()
构造函数中
调用 Thread 实例的 start()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class TestThread3 implements Runnable { public static void main (String[] args) { TestThread3 testThread3 = new TestThread3(); new Thread(testThread3).start(); for (int i = 0 ; i < 2000 ; i++) { System.out.println("主线程" + i); } } @Override public void run () { for (int i = 0 ; i < 2000 ; i++) { System.out.println("RRRRRRRRRRRRRRRRRRRR" + i); } } }
Thread 与 Runnable 对比
继承 Thread 类:
启动线程:子类对象.start()
不推荐使用:避免单继承局限性
实现 Runnable 接口:
启动线程:new Thread(对象) + thread.start()
推荐使用:没有单继承局限性,方便同一个对象被多个线程使用
Thread:基于继承;Runnable:基于组合;创建Thread比一个Runnable成本要昂贵一点。/
实例:初识线程并发问题 卖票问题
Thread.currentThread.getName()
:得到当前正在执行的线程方法
Thread.sleep(200)
:此线程暂停200ms
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 public class TestThread4 implements Runnable { private int ticketNums = 10 ; public static void main (String[] args) { TestThread4 t = new TestThread4(); new Thread(t, "小明" ).start(); new Thread(t, "Bob" ).start(); new Thread(t, "牛牛" ).start(); } @Override public void run () { while (true ) { if (ticketNums <= 0 ) { break ; } System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums-- + "张票" ); try { Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }
这里还没讲怎么解决,看之后的。
实例:龟兔赛跑
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 public class Race implements Runnable { private String winner; @Override public void run () { for (int i = 0 ; i < 100 ; i++) { if (Thread.currentThread().getName().equals("兔子" ) && i%10 ==0 ){ try { Thread.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"跑了-->" +i+"步" ); if (gameOver(i)){ System.out.println("Winner is " + winner); break ; } } } public static void main (String[] args) { Race race = new Race(); new Thread(race,"兔子" ).start(); new Thread(race,"乌龟" ).start(); } public boolean gameOver (int steps) { if (winner == null ){ if (steps >= 99 ){ winner = Thread.currentThread().getName(); return true ; } return false ; } return true ; } }
2.3 Callable接口实现 Callable其实相当于一个增强的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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class TestCallable implements Callable <Boolean > { private String url; private String name; public TestCallable (String url, String name) { this .name = name; this .url = url; } public static void main (String[] args) throws ExecutionException, InterruptedException { TestCallable tc1 = new TestCallable("https://commons.apache.org/proper/commons-io/images/commons-logo.png" , "1.png" ); TestCallable tc2 = new TestCallable("https://commons.apache.org/proper/commons-io/images/io-logo-white.png" , "2.png" ); TestCallable tc3 = new TestCallable("http://www.apache.org/events/current-event-125x125.png" , "3.png" ); ExecutorService service = Executors.newFixedThreadPool(1 ); Future<Boolean> result1 = service.submit(tc1); Future<Boolean> result2 = service.submit(tc2); Future<Boolean> result3 = service.submit(tc3); System.out.println("result1 == " +result1.get()); System.out.println("result2 == " +result2.get()); System.out.println("result3 == " +result3.get()); service.shutdownNow(); } @Override public Boolean call () { WebDownload webDownload = new WebDownload(); webDownload.downloader(url, name); System.out.println("下载了文件,名为" + name); if (name == null ){ return false ; } return true ; } class WebDownload { public void downloader (String url, String name) { String path = "F:/AWork/Project/2021/JavaDataStructure/src/com/songx64/baselearn/threadlearn/kuangThread/" ; try { FileUtils.copyURLToFile(new URL(url), new File(path+name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,下载出错" ); } } } }
继承Callable的时候会有个泛型,指定返回值的类型。
然后实现的是 call()
方法,带有返回值。
2.4 静态代理模式 多线程的 Thread 和 Runnable 就是静态代理模式。
代理模式 | 菜鸟教程 (runoob.com)
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能 。这种类型的设计模式属于结构型模式。
主要解决: 在直接访问对象时带来的问题
何时使用: 想在访问一个类时做一些控制。
如何解决: 增加中间层。
关键代码: 实现与被代理类组合。
这里举个例子:
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 public class StaticProxy { public static void main (String[] args) { WeddingCompany weddingCompany = new WeddingCompany(new You()); weddingCompany.HappyMarry(); new WeddingCompany(new You()).HappyMarry(); new Thread(new TestRunnable()).start(); } } interface Marry { void HappyMarry () ; } class You implements Marry { @Override public void HappyMarry () { System.out.println("结婚,Happy" ); } } 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("结婚之后,收拾残局" ); } }
3. 线程状态
3.1 停止线程 建议标志位
不推荐使用JDK提供的stop()、destroy()等方法。【已废弃】
推荐线程自己停止下来
建议使用一个标志位进行终止变量当flag=false,则终止线程运行。
标志位的方式:
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 public class TestStop implements Runnable { private boolean flag = true ; @Override public void run () { int i = 0 ; while (flag){ System.out.println("Thread is Running --" + i++); } } public void myStop () { this .flag = false ; } public static void main (String[] args) { TestStop t = new TestStop(); new Thread(t).start(); for (int i = 0 ; i < Integer.MAX_VALUE/2 ; i++) { System.out.println("main--" +i); if (i == Integer.MAX_VALUE/400000 ){ t.myStop(); System.out.println("Stop At " +i); break ; } } } }
3.2 线程休眠 sleep(int ms) sleep()
sleep(1000)
,休眠1000毫秒,也就是1s
会抛出 InterruptedException
调用 sleep,线程进入阻塞状态;sleep 时间到达后,进入就绪状态
sleep 不会释放对象的锁,wait 会释放对象锁
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 public class TestSleep { public void timeCount (int second) throws InterruptedException { while (second > 0 ){ System.out.println(second--); Thread.sleep(1000 ); } } public void timePrinter () throws InterruptedException { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss" ); while (true ){ System.out.println(dateFormat.format(new Date(System.currentTimeMillis()))); Thread.sleep(1000 ); } } public static void main (String[] args) throws InterruptedException { TestSleep testSleep = new TestSleep(); testSleep.timeCount(10 ); testSleep.timePrinter(); } }
3.3 线程礼让 yield()
礼让线程,让当前正在执行的线程暂停,但不阻塞将线程
从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功!看CPU心情
1 2 3 4 5 6 7 8 9 10 11 12 public class TestYield { public static void main (String[] args) { Runnable runnable = ()->{ System.out.println(Thread.currentThread().getName() + " 线程开始执行" ); Thread.yield(); System.out.println(Thread.currentThread().getName() + " 线程停止" ); }; new Thread(runnable,"线程A" ).start(); new Thread(runnable,"线程B" ).start(); } }
PS:我礼让就没成功过。。电脑的原因吗
3.4 线程强制执行 join()
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 public class TestJoin implements Runnable { @Override public void run () { for (int i = 0 ; i < 5000 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Join线程-------------" + 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 < 5000 ; i++) { if (i == 1000 ){ thread.join(); } System.out.println("Main线程 + " + i); } } }
3.5 线程状态观测 getState()
JDK1.8文档:
public static enum Thread.State extends Enum<Thread.State> 线程状态。线程可以处于以下状态之一:
NEW尚未启动 的线程处于此状态。
RUNNABLE 在Java虚拟机中执行 的线程处于此状态。
BLOCKED 被阻塞 等待监视器锁定的线程处于此状态。
WAITING 正在等待另一个线程 执行特定动作的线程处于此状态。
TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间 的线程处于此状态。
TERMINATED已退出 的线程处于此状态。
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
thread1.getState()
:得到 thread1 的状态,上面的那几个值
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 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) { e.printStackTrace(); } System.out.println("///////" ); } }); Thread.State state = thread.getState(); System.out.println("start之前: " + state); thread.start(); state = thread.getState(); System.out.println("start之后:" + state); while (state != Thread.State.TERMINATED){ state = thread.getState(); System.out.println(state); Thread.yield(); Thread.sleep(1000 ); } state = thread.getState(); System.out.println("线程死了!--" + state); thread.start(); } }
3.6 线程优先级 getPriority()
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10.Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
使用以下方式改变或获取优先级
getPriority()
:获取优先级
setPriority(int xxx)
: 设置优先级
线程执不执行还是得看CPU,优先级高的不一定先执行。但是优先级高的权重大,更可能先执行。
性能倒置:优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度
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 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,"t1" ); Thread t2 = new Thread(myPriority,"t2" ); Thread t3 = new Thread(myPriority,"t3" ); Thread t4 = new Thread(myPriority,"t4" ); Thread t5 = new Thread(myPriority,"t5" ); t1.setPriority(1 ); t2.setPriority(2 ); t3.setPriority(3 ); t4.setPriority(4 ); t5.setPriority(Thread.MAX_PRIORITY); t4.start(); t2.start(); t1.start(); t3.start(); t5.start(); } } class MyPriority implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName() + " --> " + Thread.currentThread().getPriority() ); } }
3.7 守护线程 setDaemon(boolean)
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕:如 main
线程
虚拟机不用等待守护线程执行完毕:如后台记录操作日志、监控内存、垃圾回收GC线程等..
thread1.setDaemon(true)
:将线程 thread1 设为守护线程
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 public class TestDaemon { public static void main (String[] args) { Bless bless = new Bless(); NormalT normalT = new NormalT(); Thread thread = new Thread(bless); thread.setDaemon(true ); Thread thread1 = new Thread(normalT); thread1.start(); thread.start(); } } class Bless implements Runnable { @Override public void run () { while (true ){ System.out.println("God Bless You" ); } } } class NormalT implements Runnable { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) { System.out.println("此线程存活" ); } System.out.println("此线程死亡" ); } }
4. 线程同步(重点) 并发:同一个对象被多个线程同时操作
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。
线程同步其实就是一种等待机制 ,多个需要同时访问此对象的线程进入这个对象的等待池形成队列 ,等待前面线程使用完毕,下一个线程再使用。
synchronized,排他锁独占资源。
使用锁可能会引起问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起 ;
在多线程竞争下,加锁﹐释放锁会导致比较多的上下文切换和调度延时,引起性能问题 ;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置 ﹐引起性能问题.
死锁 产生死锁的四个必要条件: 1.互斥条件:一个资源每次只能被一个进程使用。 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生
写个死锁案例:
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 public class DeadLock { public static void main (String[] args) { Integer i1 = 10 ; Integer i2 = 20 ; Thread thread = new Thread(new MyThread(i1, i2, 0 ), "线程1" ); Thread thread2 = new Thread(new MyThread(i1, i2, 1 ), "线程2" ); thread.start(); thread2.start(); } } class MyThread implements Runnable { private Integer i1; private Integer i2; private Integer choice; public MyThread (Integer i1, Integer i2, Integer choice) { this .i1 = i1; this .i2 = i2; this .choice = choice; } @Override public void run () { if (choice == 0 ) { synchronized (i1) { System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1); synchronized (i2) { System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2); } } } else { synchronized (i2) { System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2); synchronized (i1) { System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1); } } } } }
将最后一段的代码改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public void run () { if (choice == 0 ) { synchronized (i1) { System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1); } synchronized (i2) { System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2); } } else { synchronized (i2) { System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2); } synchronized (i1) { System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1); } } }
这样就能避免死锁了。为啥呢?我自己理解的,是锁升级到重量级锁了,阻塞了其中的一个线程。
Lock锁 JUC,就是 import 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 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 { private final ReentrantLock reentrantLock = new ReentrantLock(); int tirckNums = 10 ; @Override public void run () { while (true ){ reentrantLock.lock(); try { if (tirckNums > 0 ){ try { Thread.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->" +tirckNums--); }else { break ; } }finally { reentrantLock.unlock(); } } } }
手动锁,自己调用 lock
和 unlock
,锁代码块。
Lock是显式锁 (手动开启和关闭锁,别忘记关闭锁) ;synchronized是隐式锁 ,出了作用域自动释放
Lock只有代码块锁 ,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能 更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
5. 线程通信问题 5.1 生产者消费者问题
可以观看:一篇文章,让你彻底弄懂生产者–消费者问题 - 简书 (jianshu.com) ,使用了3种不同的方法。
并发协作模型“生产者Ⅰ消费者模式”—>管程法
生产者:负责生产数据的模块(可能是方法﹐对象﹐线程﹐进程);
消费者:负责处理数据的模块(可能是方法﹐对象﹐线程,进程);
缓冲区∶消费者不能直接使用生产者的数据﹐他们之间有个“缓冲区
生产者将生产好的数据放入缓冲区 ,消费者从缓冲区 拿出数据
并发协作模型“生产者/消费者模式”—>信号灯法
就是一个标志位
5.2 管程法 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 public class TestPC { public static void main (String[] args) { SynContainer synContainer = new SynContainer(); Producer producer = new Producer(synContainer); Consumer consumer =new Consumer(synContainer); producer.start(); consumer.start(); } } class Producer extends Thread { SynContainer synContainer; public Producer (SynContainer synContainer) { this .synContainer = synContainer; } @Override public void run () { for (int i = 0 ; i < 100 ; i++) { synContainer.push(new Production(i)); System.out.println("生产者生产了--> " +i); } } } class Consumer extends Thread { SynContainer synContainer; public Consumer (SynContainer synContainer) { this .synContainer = synContainer; } @Override public void run () { for (int i = 0 ; i < 100 ; i++) { int id = synContainer.pop().id; System.out.println("消费者消费了--> " + id); } } } class Production { int id; public Production (int id) { this .id = id; } } class SynContainer { Production[] productions = new Production[10 ]; int count = 0 ; public synchronized void push (Production production) { if (count == productions.length) { try { this .wait(); }catch (Exception e){ e.printStackTrace(); } } productions[count] = production; count++; this .notifyAll(); } public synchronized Production pop () { if (count == 0 ){ try { this .wait(); }catch (InterruptedException e){ e.printStackTrace(); } } count--; Production production = productions[count]; this .notifyAll(); return production; } }
Java中的管程 - 被罚站的树 - 博客园 (cnblogs.com)
Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。而管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程 。但是管程利用OOP的封装特性解决了信号量在工程实践上的复杂性问题 ,因此java采用管理机制。
所谓管程,指的是管理共享变量以及对其操作过程,让它们支持并发访问 。翻译为 Java 领域的语言,就是管理类的成员变量和成员方法,让这个类是线程安全的。
有一点需要再次提醒,对于 MESA 管程来说,有一个编程范式,就是==需要在一个 while 循环里面调用 wait()==。这个是 MESA 管程特有的 。
5.3 信号灯法
JAVA并发框架之Semaphore实现生产者与消费者模型 - 陈峰 - 博客园 (cnblogs.com)
锁和 信号量(Semaphore) 是实现多线程同步的两种常用的手段。
信号量需要初始化一个许可值,许可值可以大于0,也可以小于0,也可以等于0.
如果大于0,表示,还有许可证可以发放,线程不会被阻塞;
如果小于或者等于0,表示,没有许可证可以发放了,线程被阻塞住了。
它有两个常用的操作:
acquire()
申请许可证,如果有,就可以获得,如果没有就等待了。相当于减法。
release()
归还许可证,保证循环使用。相当于加法。
看一个例子,就会明白了,还是实现上次的那个生产者和消费者的例子。
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 package com.songx64.baselearn.threadlearn.kuangThread.gaoji;import java.util.LinkedList;import java.util.concurrent.Semaphore;public class TestPC2 { public static void main (String[] args) { SynContainer2 synContainer2 = new SynContainer2(); Thread p1 = new Producer2(synContainer2); Thread c1 = new Consumer2(synContainer2); Thread c2 = new Consumer2(synContainer2); p1.start(); c1.start(); c2.start(); } } class Production2 { public int id; public Production2 (int id) { this .id = id; } } class Producer2 extends Thread { SynContainer2 synContainer2; public Producer2 (SynContainer2 synContainer2) { this .synContainer2 = synContainer2; } @Override public void run () { for (int i = 0 ; i < 20 ; i++) { synContainer2.push(new Production2(i)); System.out.println("生产了-->" + i); } } } class Consumer2 extends Thread { SynContainer2 synContainer2; public Consumer2 (SynContainer2 synContainer2) { this .synContainer2 = synContainer2; } @Override public void run () { for (int i = 0 ; i < 20 ; i++) { int id = synContainer2.pop().id; System.out.println(Thread.currentThread().getName()+"消费了<--" + id); } } } class SynContainer2 { LinkedList<Production2> production2s = new LinkedList<>(); Semaphore mutex = new Semaphore(1 ); Semaphore isFull = new Semaphore(10 ); Semaphore isEmpty = new Semaphore(0 ); public void push (Production2 production2) { try { isFull.acquire(); mutex.acquire(); int i = isFull.availablePermits(); production2s.add(production2); } catch (InterruptedException e) { e.printStackTrace(); } finally { mutex.release(); isEmpty.release(); } } public Production2 pop () { Production2 temp = null ; try { isEmpty.acquire(); mutex.acquire(); temp = production2s.removeLast(); } catch (InterruptedException e) { e.printStackTrace(); } finally { mutex.release(); isFull.release(); } return temp; } }
写的有点乱。。。但是知道Semaphore的用法就行了
6.高级主题 6.1线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能 影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理(….)
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
简单使用:
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 public class TestPool { public static void main (String[] args) { ThreadPoolExecutor executorService = new ThreadPoolExecutor( 10 , 20 , 100 , TimeUnit.SECONDS, new LinkedBlockingDeque<>()); executorService.execute(new MyThread()); executorService.execute(new MyThread()); executorService.execute(new MyThread()); executorService.execute(new MyThread()); } } class MyThread implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName()); } }