2021-09-29
线程同步机制
- 并发:同一个对象被多个线程同时操作。
存在问题
同一进程的多个线程共享同一块储存空间,为了保证数据在方法中呗访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。但是存在以下问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起。
在多线程竞争下,加锁,释放锁会导致比较多的上下切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
三个案例:
package com.jia.syn;//不安全的买票
//线程不安全,有重复的票数
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;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--);}
}
package com.jia.syn;//不安全的取钱
//连个人去银行取钱,账户
public class UnsafeBank {public static void main(String[] args) {//账户Account account = new Account(100,"结婚基金");Drawing you = new Drawing(account,50,"你");Drawing girlFriend = new Drawing(account,100,"girlFriend");you.start();girlFriend.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 drawingMoney,String name){super(name);this.account = account;this.drawingMoney = drawingMoney;}//取钱@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);//Thread.currentThread().getName() = this.getName()System.out.println(this.getName()+"手里的钱:"+nowMoney);}
}
package com.jia.syn;import java.util.ArrayList;
import java.util.List;//线程不安全的集合
public class UnsafeList {public static void main(String[] args) {List<String> list = new ArrayList<String>();for (int i = 0; i < 10000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());}).start();}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(list.size());}
}
解决方法:同步方法和同步块。
同步方法及同步块
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized方法和synchronized块。
- 同步方法:public synchronized void method(int args){}
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才会释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
- 缺陷:若将一个大的方法申明synchronized将会影响效率。
- 方法里面需要修改的内容才需要锁,锁的太多,浪费资源。
同步块
同步块:synchronized(Obj){}
Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器。
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class。
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码。
- 第二个线程访问,发现同步监视器被锁定,无法访问。
- 第一个线程访问完毕,解锁同步监视器。
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
代码
package com.jia.syn;//不安全的买票
//线程不安全,有重复的票数
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 = 100;boolean flag = true;//外部停止方式@Overridepublic void run() {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//买票while(flag){try {buy();} catch (InterruptedException e) {e.printStackTrace();}}}//synchronized 同步方法,锁的是thisprivate synchronized void buy() throws InterruptedException {//判断是否有票if (ticketNums<=0){flag = false;return;}//模拟延时Thread.sleep(100);//买票System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);}
}
package com.jia.syn;//不安全的取钱
//连个人去银行取钱,账户
public class UnsafeBank {public static void main(String[] args) {//账户Account account = new Account(1000,"结婚基金");Drawing you = new Drawing(account,50,"你");Drawing girlFriend = new Drawing(account,100,"girlFriend");you.start();girlFriend.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 drawingMoney,String name){super(name);this.account = account;this.drawingMoney = drawingMoney;}//取钱//synchronized ,默认锁的是this.@Overridepublic void run() {//锁的对象就是变化的量,需要增删改的对象synchronized (account){//判断有没有钱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);//Thread.currentThread().getName() = this.getName()System.out.println(this.getName()+"手里的钱:"+nowMoney);}}
}
package com.jia.syn;import java.util.ArrayList;
import java.util.List;//线程不安全的集合
public class UnsafeList {public static void main(String[] args) {List<String> list = new ArrayList<String>();for (int i = 0; i < 10000; i++) {new Thread(()->{synchronized (list){list.add(Thread.currentThread().getName());}}).start();}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(list.size());}
}
死锁
什么是死锁
- 多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
死锁的避免方法
- 产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放。
- 不剥夺条件:进程以获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系。
只要想办法破了上面四个必要条件中任意一个或多个条件就可以避免死锁。
代码
package com.jia.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) {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+"获得镜子的锁");
//
// }}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+"获得镜子的锁");
//
// }}synchronized (lipstick){//一秒钟后想获得镜子System.out.println(this.girlName+"获得镜子的锁");}}}}
Lock(锁)
Lock
- 从JDK5.0开始,Java提供了更强大的线程同步机制------通过显示定义同步锁对象来实现同步,同步锁使用Lock充当对象。
- java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock(可重入锁)类实现了Lock,它拥有synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁。
package com.jia.gaoji;
import java.util.concurrent.locks.ReentrantLock;
//测试Lock锁
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{int tickerNums = 10;//d定义Lock锁private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while(true){lock.lock();//加锁try{lock.lock();//加锁if (tickerNums>0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(tickerNums--);}else{break;}}finally {//解锁lock.unlock();}}}
}
synchronized与Lock的不同
- Lock锁是显式锁(手动开启和关闭锁,不要忘记关闭锁)synchronized是隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类).
- 优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外).
2021-09-29相关推荐
- 橘子CPS联盟操作手册2021.09
橘子CPS联盟操作手册2021.09 目录 橘子CPS联盟操作手册2021.09 橘子CPS联盟是干嘛的 橘子CPS基本操作流程 PC端操作 1.注册 2.登陆 3.渠道管理 4.分享网站 5.分享网 ...
- 史上最详细微信小程序授权登录与后端SprIngBoot交互操作说明,附源代码,有疑惑大家可以直接留言,蟹蟹 2021.11.29完善更新小程序代码,
2021.11.29 更新文章 你好,我是博主宁在春,一起学习吧!!! 写这篇文章的原因,主要是因为最近在写毕业设计,用到了小程序,这中间曲曲折折,一言难尽啊.毕业设计真的让人麻脑阔
- A. [2021.1.29多校省选模拟11]最大公约数(杜教筛/数论)
A. [2021.1.29多校省选模拟11]最大公约数 这是一个杜教筛的经典题目,最后我们只需要筛一下1∗xμ(x)1*x\mu(x)1∗xμ(x)这个函数的前缀和即可,然后看到有111这个函数,我们 ...
- 2021.09.27 MySQL笔记
2021.09.27 MySQL笔记 文章目录 2021.09.27 MySQL笔记 一.展示当前存在的所有数据库 二.使用(选中)一个数据库 三.创建一个数据表 四.查询并展示该数据库内的所有数据表 ...
- 实习日志 (2021.09.13)
2021.09.13星期一 今天把之前的算法题终于给弄明白了,并能够按照自己的思路去把他给完成,总结这个题目并不是很难,最重要的是要把链表给弄懂,一开始由于我对链表不是很熟悉,导致我在写该题目的时候花 ...
- 2021.09青少年软件编程(Python)等级考试试卷(三级)
2021.09青少年软件编程(Python)等级考试试卷(三级) 一.单选题(共25题,每题2分,共50分) 1.使用map函数可以实现列表数据元素类型的转换,而无需通过循环.则将列表L=['1',' ...
- 2021.04.29删点成林
2021.04.29删点成林 (题目来源:https://leetcode-cn.com/problems/delete-nodes-and-return-forest/) 题目描述 给出二叉树的根节 ...
- 2021.12.29国内第一家量产蓝牙AOA高精度定位基站设备原厂深圳核芯物联荣获第二十三届高交会双项大奖
2021.12.29国内第一家量产蓝牙AOA高精度定位基站设备原厂深圳核芯物联荣获第二十三届高交会双项大奖 2021年12月29日,核芯物联参展第二十三届中国国际高新技术成果交易会.凭借在蓝牙AOA基 ...
- 2021.09.24—皮皮与帅帅的第二篇情话
2021.09.24我们小情书的第二天 每天晚上,小兔子都会一个人来到溪水边,坐在地上数着星星.而且他个人也非常喜欢一闪一闪的东西.对于小兔子来说,每颗星星都是特别的,于是她就给每颗星星都起了一个可爱 ...
- 美团笔试题2021.8.29(第四题求大佬解答)
美团笔试题2021.8.29 又再帮同学写,推了这周的周赛,侥幸都有点思路 丁香树 题目描述 思路 因为芳香值最大为30,所以用一个数组存储已走过的芳香值,然后走到第i个点,找比当前芳香值小的有多少个 ...
最新文章
- [vb+mo] visual baisc 6.0 基于mapobjects 2.4 开发的数字化校园电子地图
- springboot 请求路径有后缀_springboot指定访问url接口后缀:*.do或*.action
- PHP高级教程——Zend Framework核心开发人员力作
- 2016.9.9《Oracle查询优化改写技巧与案例》电子工业出版社一书中的技巧
- ClickHouse之集群搭建以及数据复制
- 全国数据中心分布图上线 轻轻松松找机房
- codeforces 118A-C语言解题报告
- day14(xml 编写及解析)
- android+nutz后台如何上传和下载图片
- 【五级流水线CPU】—— 4. 移动操作指令(6条)
- CHD4B1(hadoop-0.23)实现NameNode HA安装配置
- 教你一招解决Git时提交到多个远程仓库
- 四轴飞行器Bootloader和固件的更新
- IDEA插件系列(45):UUID Generator插件——UUID生成器
- 对C语言程序设计老师的评价,C语言程序设计课程教学评价研究
- 计算机桌面体验,如何安装win10的桌面体验功能?
- iPhone、iPod和iPad离线固件升级的方法
- 常见的HTML标签,让你的网页更加精彩
- 2020起重机械指挥证考试及起重机械指挥模拟考试题库
- vue拦截器设置请求头失败,laravel设置前端请求头跨域