- 线程同步机制
- 不安全案例
- 同步方法和同步块
- JUC并发安全 CopyOnWriteArrayList
- 死锁
- 死锁避免方法
- Lock(锁)
- synchronized与Lock的对比
- 线程协作:生产者消费者模式
- 线程通信
- 管程法
- 信号灯法
- 线程池
- 总结
https://www.bilibili.com/video/BV1V4411p7EF/
线程同步机制
- 并发:同一个对象被多个线程同时操作。
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。
- 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
不安全案例
package com.qing.sync;/** * 不安全的买票 * 线程不安全,有负数 */public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket, "杨康").start(); new Thread(buyTicket, "郭靖").start(); new Thread(buyTicket, "黄蓉").start(); }}class BuyTicket implements Runnable { //票 private int ticketNums = 10; @Override public void run() { //买票 while (ticketNums > 0) { buy(); } } private void buy() { //判断是否有票 if (ticketNums <= 0) { return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买到第" + ticketNums-- + "票"); }}杨康买到第10票郭靖买到第9票黄蓉买到第8票杨康买到第7票黄蓉买到第6票郭靖买到第5票杨康买到第4票郭靖买到第3票黄蓉买到第2票黄蓉买到第1票杨康买到第0票郭靖买到第-1票package com.qing.sync;import java.util.ArrayList;import java.util.List;/** * 线程不安全的集合 */public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); }}9996同步方法和同步块
- 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:
- synchronized方法。
- synchronized块。
- synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
- synchronized方法的缺陷:若将一个大的方法申明为synchronized将会影响效率。
- 同步块:synchronized(obj){}
- obj称之为同步监视器。
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器。
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class。
- 同步监视器的执行过程:
- 第一个线程访问,锁定同步监视器,执行其中代码。
- 第二个线程访问,发现同步监视器被锁定,无法访问。
- 第一个线程访问完毕,解锁同步监视器。
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
- 同步块锁的对象是变化的量。
private synchronized void buy() { //判断是否有票 if (ticketNums <= 0) { return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买到第" + ticketNums-- + "票"); }package com.qing.sync;import java.util.ArrayList;import java.util.List;/** * 线程不安全的集合 */public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ synchronized (list) { list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); }}10000JUC并发安全 CopyOnWriteArrayList
package com.qing.sync;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 < 30000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); }}30000死锁
- 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
- 某一个同步块同时拥有"两个以上对象的锁"时,就可能发生"死锁"的问题。
package com.qing.sync;/** * 死锁:多个线程互相持有对方需要的资源,然后形成僵持。 */public class DeadLock { public static void main(String[] args) { Makeup t1 = new Makeup(0,"灰姑娘"); Makeup t2 = new Makeup(1,"白雪公主"); t1.start(); t2.start(); }}//口红class Lipstick {}//镜子class Mirror {}class Makeup extends Thread { //需要的资源只有一份,用static来保证只有一份 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice;//选择 String girlName;//用化妆品的人 public Makeup(int choice,String girlName) { this.choice = choice; this.girlName = girlName; } @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(1000); synchronized (lipstick) {//一秒钟后获得口红的锁 System.out.println(this.girlName + "获得口红的锁"); } } } }}灰姑娘获得口红的锁白雪公主获得镜子的锁死锁避免方法
- 产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
- 上面列出了死锁的四个必要条件,只要破除其中的任意一个或多个条件就可以避免死锁发生。
Lock(锁)
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。
//定义Lock锁private final ReentrantLock lock = new ReentrantLock();public void test() { try { lock.lock();//加锁 //保证线程安全的代码 } finally { lock.unlock();//解锁 }}package com.qing.sync;import java.util.concurrent.locks.ReentrantLock;/** * 测试Lock锁 */public class TestLock { public static void main(String[] args) { Lock2 lock2 = new Lock2(); new Thread(lock2).start(); new Thread(lock2).start(); new Thread(lock2).start(); }}class Lock2 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();//解锁 } } }}10987654321synchronized与Lock的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁);synchronized是隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。
- 优先使用顺序:Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)。
线程协作:生产者消费者模式
线程通信
- Java提供了几个方法解决线程之间的通信问题。

- 几个方法均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException。
管程法
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)。
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)。
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个"缓冲区"。
- 生产者将生产的数据放入缓冲区,消费者从缓冲区拿出数据。

package com.qing.sync;/** * 测试管程法:生产者消费者模型-->利用缓冲区解决 * 生产者,消费者,产品,缓冲区 */public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Producer(container).start(); new Consumer(container).start(); }}//生产者class Producer extends Thread { SynContainer container; public Producer(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 1; i < 20; i++) { container.push(new Chicken(i)); System.out.println("生产了" + i + "号鸡"); } }}//消费者class Consumer extends Thread { SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 1; i < 20; i++) { System.out.println("消费了" + container.pop().id + "号鸡"); } }}//产品class Chicken { int id;//产品编号 public Chicken(int id) { this.id = id; }}//缓冲区class SynContainer { //需要一个容器大小 Chicken[] chickens = new Chicken[5]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken) { //如果容器满了,就需要等待消费者消费 if (count == chickens.length) { //生产者等待 try { System.out.println("push-->wait"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果容器没有满,就放入产品 chickens[count] = chicken; count++; //通知消费者消费 System.out.println("push-->notifyAll"); this.notifyAll(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } //消费者消费产品 public synchronized Chicken pop() { //如果容器空了,就需要等待生产者放入产品 if (count == 0) { //消费者等待 try { System.out.println("pop-->wait"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果容器没有空,就消费产品 count--; Chicken chicken = chickens[count]; //通知生产者生产 System.out.println("pop-->notifyAll"); this.notifyAll(); return chicken; }}pop-->waitpush-->notifyAll生产了1号鸡push-->notifyAllpop-->notifyAll生产了2号鸡push-->notifyAll消费了2号鸡pop-->notifyAll生产了3号鸡消费了3号鸡push-->notifyAllpop-->notifyAll生产了4号鸡消费了4号鸡push-->notifyAll生产了5号鸡pop-->notifyAll消费了5号鸡push-->notifyAllpop-->notifyAll生产了6号鸡消费了6号鸡push-->notifyAllpop-->notifyAll消费了7号鸡pop-->notifyAll生产了7号鸡消费了1号鸡push-->notifyAllpop-->notifyAll消费了8号鸡pop-->wait生产了8号鸡push-->notifyAllpop-->notifyAll生产了9号鸡消费了9号鸡push-->notifyAllpop-->notifyAll生产了10号鸡push-->notifyAll消费了10号鸡pop-->notifyAll消费了11号鸡pop-->wait生产了11号鸡push-->notifyAllpop-->notifyAll生产了12号鸡消费了12号鸡push-->notifyAllpop-->notifyAll生产了13号鸡消费了13号鸡push-->notifyAllpop-->notifyAll生产了14号鸡push-->notifyAll消费了14号鸡pop-->notifyAll消费了15号鸡pop-->wait生产了15号鸡push-->notifyAllpop-->notifyAll生产了16号鸡消费了16号鸡push-->notifyAllpop-->notifyAll消费了17号鸡pop-->wait生产了17号鸡push-->notifyAllpop-->notifyAll生产了18号鸡push-->notifyAll消费了18号鸡生产了19号鸡pop-->notifyAll消费了19号鸡信号灯法
package com.qing.sync;/** * 测试信号灯法:生产者消费者模型--》标志位解决 */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 < 10; 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 < 10; i++) { this.tv.watch(); } }}//产品-->节目class TV { //演员表演,观众等待 T //观众观看,演员等待 F String voice;//表演的节目 boolean flag = true; //表演 public synchronized void play(String voice) { if (!flag) { try { System.out.println("play-->wait"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演:" + voice); //通知观众观看 System.out.println("play-->notifyAll"); this.notifyAll(); this.voice = voice; this.flag = !this.flag; } //观看 public synchronized void watch() { if (flag) { try { System.out.println("watch-->wait"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观众观看:" + voice); //通知演员表演 System.out.println("watch-->notifyAll"); this.notifyAll(); this.voice = voice; this.flag = !this.flag; }}演员表演:天上play-->notifyAllplay-->wait观众观看:天上watch-->notifyAllwatch-->wait演员表演:人间play-->notifyAllplay-->wait观众观看:人间watch-->notifyAllwatch-->wait演员表演:天上play-->notifyAllplay-->wait观众观看:天上watch-->notifyAllwatch-->wait演员表演:人间play-->notifyAllplay-->wait观众观看:人间watch-->notifyAllwatch-->wait演员表演:天上play-->notifyAllplay-->wait观众观看:天上watch-->notifyAllwatch-->wait演员表演:人间play-->notifyAllplay-->wait观众观看:人间watch-->notifyAllwatch-->wait演员表演:天上play-->notifyAllplay-->wait观众观看:天上watch-->notifyAllwatch-->wait演员表演:人间play-->notifyAllplay-->wait观众观看:人间watch-->notifyAllwatch-->wait演员表演:天上play-->notifyAllplay-->wait观众观看:天上watch-->notifyAllwatch-->wait演员表演:人间play-->notifyAll观众观看:人间watch-->notifyAll线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁的创建销毁,实现重复利用。
- 好处:
- 提供响应速度(减少了创建新线程的时间)。
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)。
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- JDK5.0提供了线程池相关API:ExecutorService和Executors。
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable.
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable。 - void shutdown:关闭连接池。
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
package com.qing.sync;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 测试线程池 */public class TestPool { public static void main(String[] args) { //1.创建服务,创建线程池 //参数:线程池大小 ExecutorService service = Executors.newFixedThreadPool(5); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //2.关闭连接 service.shutdown(); }}class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); }}pool-1-thread-1pool-1-thread-2pool-1-thread-4pool-1-thread-3pool-1-thread-3pool-1-thread-3pool-1-thread-3pool-1-thread-5总结
package com.qing.sync;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** * 回顾总结线程的创建 */public class ThreadNew { public static void main(String[] args) { new MyThread1().start(); new Thread(new MyThread2()).start(); FutureTask<Integer> futureTask = new FutureTask<>(new MyThread3()); new Thread(futureTask).start(); try { Integer integer = futureTask.get(); System.out.println(integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }}//1.继承Thread类class MyThread1 extends Thread { @Override public void run() { System.out.println("MyThread1"); }}//2.实现Runnable接口class MyThread2 implements Runnable { @Override public void run() { System.out.println("MyThread2"); }}//3.实现Callable接口class MyThread3 implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("MyThread3"); return 10; }}MyThread1MyThread2MyThread310原文转载:http://www.shaoqun.com/a/494882.html
刘小东:https://www.ikjzd.com/w/1853
primc:https://www.ikjzd.com/w/129
周宁:https://www.ikjzd.com/w/1647
目录线程同步机制不安全案例同步方法和同步块JUC并发安全CopyOnWriteArrayList死锁死锁避免方法Lock(锁)synchronized与Lock的对比线程协作:生产者消费者模式线程通信管程法信号灯法线程池总结https://www.bilibili.com/video/BV1V4411p7EF/线程同步机制并发:同一个对象被多个线程同时操作。处理多线程问题时,多个线程访问同一个对象
转口贸易:转口贸易
铭宣:铭宣
加拿大旅游去哪个地方玩-夏洛瓦:加拿大旅游去哪个地方玩-夏洛瓦
金沙洲万达摩天轮门票2020-1-2月佛山金沙洲摩天轮门票:金沙洲万达摩天轮门票2020-1-2月佛山金沙洲摩天轮门票
香港海洋公园怎么样?好玩吗?:香港海洋公园怎么样?好玩吗?
No comments:
Post a Comment