Java多线程之线程同步机制

  • 一、概念
    • 1、并发
    • 2、起因
    • 3、缺点
  • 二、三大不安全案例
    • 1、样例一(模拟买票场景)
    • 2、样例二(模拟取钱场景)
    • 3、样例三(模拟集合)
  • 三、同步方法及同步块
    • 1、同步方法
    • 2、同步块
  • 四、JUC安全类型的集合
    • 1、线程安全的集合(结合延时实现)
    • 2、样例
  • 五、死锁
    • 1、概念
    • 2、死锁样例
    • 3、解决方案
    • 4、产生死锁的四个必要条件
  • 六、Lock(锁)
    • 1、概念
    • 2、synchronized 与 Lock的对比
    • 3、样例
  • 七、线程协作
    • 1、管程法
    • 2、信号灯法
    • 3、线程池
      • 3.1 背景
      • 3.2 思路
      • 3.3 好处
      • 3.4 ExecutorService和Executors
      • 3.5 样例

一、概念

1、并发

同一个对象被多个线程同时操作

2、起因

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。但由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,所以在此基础上,增加锁机制。

3、缺点

一个线程持有锁会导致其他所有需要此锁的线程挂起。
(1)在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题。
(2)如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题。

二、三大不安全案例

1、样例一(模拟买票场景)

多个线程并发时,不设计好队列,结果就会出现不安全的情况,出现了-1

package com.example.multithreading.demo12_syn;// 线程不安全,有负数
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{// 票private int ticketNums = 10;// 外部停止方式boolean flag = true;@Overridepublic 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.example.multithreading.demo12_syn;import lombok.Data;public class UnsafeBank {public static void main(String[] args) {// 账户Account account = new Account(100,"基金");Drawing boy = new Drawing(account,50,"自己");Drawing girlFriend = new Drawing(account,100,"女朋友");boy.start();girlFriend.start();}
}// 账户
@Data
class Account {// 余额int money;// 卡名String name;public Account(int money, String name) {this.money = money;this.name = name;}
}// 模拟取钱
@Data
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;this.nowMoney = nowMoney;}@Overridepublic void run() {// 判断是否有钱if (account.money - drawingMoney < 0){System.out.println(Thread.currentThread().getName() + "钱不够,取不了");return;}try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 卡内余额 = 余额 - 取的钱account.money = account.money - drawingMoney;// 你手里的钱nowMoney = nowMoney + drawingMoney;System.out.println(account.name + "余额为:" + account.money);System.out.println(this.getName() + "手里的钱:" + nowMoney);}
}

结果

3、样例三(模拟集合)

package com.example.multithreading.demo12_syn;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 < 1000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());}).start();}System.out.println(list.size());}
}

结果(真正应该为1000)

三、同步方法及同步块

1、同步方法

由于可以通过private关键字来保证数据对象只能被方法访问,所以只需要针对方法提出一套机制,这套机制就是synchronized关键字。两种用法(synchronized方法 和 synchronized块)

public synchronized void method(int args){}

synchronized方法控制对 “对象” 的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。(缺点:如果将一个大的方法申明为synchronized 将会影响效率)

2、同步块

(1)格式

synchronized(obj){}

(2)Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身。
(3)同步监视器的执行过程
第一个线程访问,锁定同步监视器,执行其中代码。
第二个线程访问,发现同步监视器被锁定,无法访问。
第一个线程访问完毕,解锁同步监视器。
第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

四、JUC安全类型的集合

1、线程安全的集合(结合延时实现)

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

2、样例

package com.example.multithreading.demo12_syn;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;// 线程不安全的集合
public class UnsafeList {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();for (int i = 0; i < 1000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());}).start();}try{Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(list.size());}
}

结果

五、死锁

1、概念

多线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有”两个以上对象的锁“时,就可能会发生”死锁“的问题。

2、死锁样例

package com.example.multithreading.demo13_DeadLock;// 死锁:多个线程互相占有需要的资源,然后形成僵持
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.choice = choice;this.girlName = girlName;}@Overridepublic 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 + "获得看口红的锁");}}}}
}

结果(会卡死在这里)

3、解决方案

package com.example.multithreading.demo13_DeadLock;// 死锁:多个线程互相占有需要的资源,然后形成僵持
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.choice = choice;this.girlName = girlName;}@Overridepublic 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 + "获得看口红的锁");}}}
}

结果

4、产生死锁的四个必要条件

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

六、Lock(锁)

1、概念

从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
锁提供了对共享资源的独占访问,每次只能有一-个线程对L ock对象加锁,线程开始访问共享资源之前应先获得L ock对象。
ReentrantLock 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

2、synchronized 与 Lock的对比

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

3、样例

package com.example.multithreading.demo14_Lock;import java.util.concurrent.locks.ReentrantLock;public class LockTest {public static void main(String[] args) {TicketLock threadTest1 = new TicketLock();new Thread(threadTest1).start();new Thread(threadTest1).start();new Thread(threadTest1).start();}
}class TicketLock implements Runnable{int ticketNums = 10;// 定义lock锁private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try{// 加锁lock.lock();if(ticketNums > 0){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(ticketNums--);}else {break;}}finally {// 解锁lock.unlock();}}}
}

结果

七、线程协作

1、管程法

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

package com.example.multithreading.demo15_PC;import java.beans.Customizer;// 管程法
// 生产者,消费者,产品,缓冲区
public class PcTest {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;}// 生产@Overridepublic void run(){for(int i = 0; i < 100; i++){container.push(new Chicken(i));System.out.println("生产了->第" + i + "只鸡");}}
}// 消费者
class Consumer extends Thread{SynContainer container;public Consumer(SynContainer container){this.container = container;}// 消费@Overridepublic 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.notifyAll();}// 消费者消费产品public synchronized Chicken pop(){// 判断能否消费if (count == 0){// 等待生产者生产,消费者等待try{this.wait();}catch (InterruptedException e){e.printStackTrace();}}// 如果可以消费count--;Chicken chicken = chickens[count];// 吃完了,通知生产者生产this.notifyAll();return chicken;}}

结果

2、信号灯法

通过标志位控制

package com.example.multithreading.demo15_PC;// 信号灯法,标志位解决
public class PcTest2 {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;}@Overridepublic 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;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {tv.watch();}}
}// 产品 --> 节目
class TV{// 演员表演,观众等待 T// 观众观看,演员等待 F// 表演的节目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.notifyAll();this.voice = voice;this.flag = false;}// 观看public synchronized void watch(){if(flag){try{this.wait();}catch (InterruptedException e){e.printStackTrace();}}System.out.println("观看了:" + voice);// 通知演员表演this.notifyAll();this.flag = !this.flag;}
}

结果

3、线程池

3.1 背景

经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

3.2 思路

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。

3.3 好处

(1)提高响应速度(减少了创建新线程的时间)
(2)降低资源消耗(重复利用线程池中线程,不需要每次都创建)
(3)便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止

3.4 ExecutorService和Executors

ExecutorService:真正的线程池接口(常见子类ThreadPoolExecutor)
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable。
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable。
void shutdown() :关闭连接池。
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

3.5 样例

package com.example.multithreading.demo16_Pool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;// 测试线程池
public class PoolTest {public static void main(String[] args) throws Exception {// 1、创建服务,创建线程池// newFixedThreadPool 参数为:线程池大小ExecutorService service = Executors.newFixedThreadPool(10);// 执行service.execute(new MyThread());service.execute(new MyThread());service.execute(new MyThread());service.execute(new MyThread());// 2、关闭链接service.shutdown();}
}class MyThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}

结果

Java多线程之线程同步机制(锁,线程池等等)相关推荐

  1. 【小白学java】D36》》》线程入门学习,线程同步机制 和 线程等待与唤醒机制

  2. 对Java多线程编程的初步了解,实现多线程的三种方式以及多线程并发安全的线程同步机制

    什么叫进程?什么叫线程? 进程相当于一个应用程序,线程就是进程中的一个应用场景或者说是一个执行单元,一个进程可以启动多个线程,每个线程执行不同的任务,一个线程不能单独存在,他必须是进程的一部分,当进程 ...

  3. Java 多线程和并发编程:(二)线程同步 Lock 锁

    线程同步 Lock 锁 1.Lock 锁 2.步骤 3.Lock 与 synchronized 的区别 1.Lock 锁 Lock 锁:对需要上锁的地方上锁 JDK1.5 后新增的功能 与 Synch ...

  4. Java高级-线程同步机制实现

    2019独角兽企业重金招聘Python工程师标准>>> 前言 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Threa ...

  5. Java核心(三)并发中的线程同步与锁

    2019独角兽企业重金招聘Python工程师标准>>> 乐观锁.悲观锁.公平锁.自旋锁.偏向锁.轻量级锁.重量级锁.锁膨胀...难理解?不存的!来,话不多说,带你飙车. 上一篇介绍了 ...

  6. 学习java的第四十天,线程的优先级、守护线程、线程同步机制、死锁

    一.线程的优先级(priority) Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行. 线程的优先级用数字表示,范围1~10 Thr ...

  7. 线程同步机制synchronized中锁的判断以及锁的作用范围

    当我们使用多个线程访问同一资源(可以是同一个变量.同一个文件.同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题. 要 ...

  8. c++ linux 线程等待与唤醒_C++ Linux线程同步机制:POSIX信号量,互斥锁,条件变量...

    线程同步机制:POSIX 信号量,互斥量,条件变量 POSIX 信号量 常用的POSIX 信号量函数为如下5个: sem_init sem_destroy sem_wait sem_trywait s ...

  9. java线程 同步与异步 线程池

    1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有时为了解 决此问题,优先考虑 ...

最新文章

  1. SAP MM 明明已经扩展供应商到采购组织下,采购订单里还是报错?
  2. JavaMail邮件别名和主题乱码解决[转]
  3. js把word转html在线预览,js实现word转换为html
  4. QT实现不同内置主题的外观
  5. 我的世界基岩版json_我的世界基岩版1.16
  6. Java继承概述以及Java继承案例和继承的好处
  7. linux 如何在命令行下改系统时间
  8. 作者:窦勇(1966-),男,博士,国防科学技术大学并行与分布处理重点实验室常务副主任、研究员、博士生导师...
  9. Err.number错误号和错误说明(一)
  10. CTP: 平昨仓与平今仓,log轻轻告诉你.......
  11. Java学习资料(一)——Java书籍
  12. 可视化神经网络实验报告,可视化神经网络工具
  13. 查找计算机网络方面文献正确检索,文献检索第二次计算机检索实习题目(2016.4.10)...
  14. django form关于clean及cleaned_data的说明 以及4种初始化
  15. C++17之std::any
  16. mysql 两表关联 分组查询
  17. python求阶乘怎么做_python如何求阶乘
  18. 手机游戏运行时分析工具
  19. 高新企业申报是什么?需要怎么申请?
  20. FE内容付费系统源码

热门文章

  1. PCL中GreedyProjection三角化算法简介与示例
  2. 【Stream流】基础用法—求和、筛选、排序
  3. idea 2019.1 版本破解
  4. python发微信提醒天气冷了注意保暖_天气变冷的短信问候语_天凉了注意保暖的微信问候语...
  5. 微信壁纸小程序源码 自动采集小米壁纸
  6. blender翻转面法线
  7. 中国民航大学计算机科学与技术学院,中国民航大学计算机科学与技术学院研究生导师简介-杨宏宇...
  8. ap模式和sta模式共存_【经验】解密Wi-Fi模块如何实现AP模式和STA模式的切换
  9. Ubuntu18.04——编译报错解决:file format not recognized; treating as linker script
  10. 孩子数学成绩不好怎么办_孩子上初中数学成绩不好,上补习班考试还是不及格,怎么办?...