2020-12-04

091_多线程(二)

目录
  • 线程同步机制
    • 不安全案例
  • 同步方法和同步块
  • JUC并发安全 CopyOnWriteArrayList
  • 死锁
    • 死锁避免方法
  • Lock(锁)
    • synchronized与Lock的对比
  • 线程协作:生产者消费者模式
    • 线程通信
  • 管程法
  • 信号灯法
  • 线程池
  • 总结


https://www.bilibili.com/video/BV1V4411p7EF/

线程同步机制

  1. 并发:同一个对象被多个线程同时操作。
  2. 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。
  3. 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。
  4. 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
    1. 一个线程持有锁会导致其他所有需要此锁的线程挂起。
    2. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
    3. 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

不安全案例

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

同步方法和同步块

  1. 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:
    1. synchronized方法。
    2. synchronized块。
  2. synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
  3. synchronized方法的缺陷:若将一个大的方法申明为synchronized将会影响效率。
  4. 同步块:synchronized(obj){}
  5. obj称之为同步监视器。
    1. obj可以是任何对象,但是推荐使用共享资源作为同步监视器。
    2. 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class。
  6. 同步监视器的执行过程:
    1. 第一个线程访问,锁定同步监视器,执行其中代码。
    2. 第二个线程访问,发现同步监视器被锁定,无法访问。
    3. 第一个线程访问完毕,解锁同步监视器。
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
  7. 同步块锁的对象是变化的量。
 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()); }}
10000

JUC并发安全 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

死锁

  1. 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
  2. 某一个同步块同时拥有"两个以上对象的锁"时,就可能发生"死锁"的问题。
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 + "获得口红的锁");    }   }  } }}
灰姑娘获得口红的锁白雪公主获得镜子的锁

死锁避免方法

  1. 产生死锁的四个必要条件:
    1. 互斥条件:一个资源每次只能被一个线程使用。
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
  2. 上面列出了死锁的四个必要条件,只要破除其中的任意一个或多个条件就可以避免死锁发生。

Lock(锁)

  1. 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  2. java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  3. 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();//解锁   }  } }}
10987654321

synchronized与Lock的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁);synchronized是隐式锁,出了作用域自动释放。
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁。
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。
  4. 优先使用顺序:Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)。

线程协作:生产者消费者模式

线程通信

  1. Java提供了几个方法解决线程之间的通信问题。

image.png

  1. 几个方法均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException。

管程法

  1. 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)。
  2. 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)。
  3. 缓冲区:消费者不能直接使用生产者的数据,他们之间有个"缓冲区"。
  4. 生产者将生产的数据放入缓冲区,消费者从缓冲区拿出数据。

image.png

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

线程池

  1. 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  2. 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁的创建销毁,实现重复利用。
  3. 好处:
    1. 提供响应速度(减少了创建新线程的时间)。
    2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)。
    3. 便于线程管理
      1. corePoolSize:核心池的大小
      2. maximumPoolSize:最大线程数
      3. keepAliveTime:线程没有任务时最多保持多长时间后会终止
  4. JDK5.0提供了线程池相关API:ExecutorService和Executors。
  5. ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
    1. void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable.
    2. Future submit(Callable task):执行任务,有返回值,一般用来执行Callable。
    3. void shutdown:关闭连接池。
  6. 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