线程同步
2022/3/28 23:52:38
本文主要是介绍线程同步,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
线程同步
-
并发
同一个对象被多个线程同时操作
-
形成条件
队列 + 锁
-
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多个线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
三大不安全的案例
//不安全的买票 //线程不安全,有重复或者负数 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 ticketnum = 10; boolean flag = true;//外部停止标志 @Override public void run() { while (flag) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } buy(); } } public void buy() { if(ticketnum <= 0) { flag = false; return; } System.out.println(Thread.currentThread().getName() + "买到了" + ticketnum--); } }
package com.GGp.Demo1; //不安全的取钱 //两个人去银行取钱 public class UnsafeBank { public static void main(String[] args) { Account account = new Account(100, "基金"); Drawing you = new Drawing(account, 50, "小明"); Drawing me = new Drawing(account, 100, "小红"); you.start(); me.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; } //取钱 @Override public void run() { if(account.money-drawingMoney<0) { System.out.println(this.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); } }
/线程不安全的集合 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(); } System.out.println(list.size()); } }
同步方法
- 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
- 同步方法:public synchronized void method(int args){}
- synchronized方法控制对“对象”的访问,每一个对象对应一把锁,每一个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦调用,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
- 缺陷:若将一个大的方法申明为synchronized将会影响效率
同步块:synchronized(Obj){}
- Obj称之为 同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象的本身,或者是class
- 同步监视器执行的过程:
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
使得线程安全的改进
//不安全的买票的改进 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 ticketnum = 10; boolean flag = true;//外部停止标志 @Override public void run() { while (flag) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } buy(); } } public synchronized void buy() { if(ticketnum <= 0) { flag = false; return; } System.out.println(Thread.currentThread().getName() + "买到了" + ticketnum--); } }
//不安全的取钱的改进 public class UnsafeBank { public static void main(String[] args) { Account account = new Account(100, "基金"); Drawing you = new Drawing(account, 50, "小明"); Drawing me = new Drawing(account, 100, "小红"); you.start(); me.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; } //取钱 @Override public void run() { synchronized (account) { if(account.money-drawingMoney<0) { System.out.println(this.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); } } }
//线程不安全的集合的改进 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(); } System.out.println(list.size()); } }
总结
使用同步块时,锁住的时要改变的量,例如案例中的整个账户信息
这篇关于线程同步的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-06-26结对编程到底难不难?答案在这里
- 2024-06-19《2023版Java工程师》课程升级公告
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现
- 2024-06-03为什么以及如何要进行架构设计权衡?
- 2024-05-31全网首发第二弹!软考2024年5月《软件设计师》真题+解析+答案!(11-20题)
- 2024-05-31全网首发!软考2024年5月《软件设计师》真题+解析+答案!(21-30题)