[Java]Java初学之多线程03--同步与锁

Intro

本篇文章主要关于多线程"同步"以及"锁"的相关内容~

正文

同步(Synchronize)

概念

“同步”是基于“并发”的需求而出现的
所谓并发,就是同一个对象被多个线程同时操作,比如两个人同时从同一个账户取钱,再比如春运抢票。

多个线程同时使用一个资源,必然会造成混乱。想象一下从前的线下购票厅,如果大家都不排队而拥挤着抢票,不仅流程混乱,更可能少票少钱。而排队则可以让整个流程有序顺畅进行。

同理,同步是一种等待机制,多个需要同时访问同一个对象的线程会进入对象的等待池形成队列,等前面的线程使用资源完毕释放后,下一个线程再使用。使用期间怎么做到防止其他线程访问,则是在线程访问时加入锁机制,如此便可独占资源。释放的过程也就是解锁的过程。
因此,同步其实就是排队+锁

但是,鱼和熊掌不可兼得,保证了安全性,必然会牺牲一定的性能,锁存在以下问题:

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

实现

要实现同步,可以通过Synchronized关键字实现,它包括了两种用法:synchronized方法synchronized块

接下来用两个不同示例分别示范Synchronized用法~
Synchronized方法:在方法前加synchronized前缀

//假设我们在抢演唱会的票
package com.multiThread;
public class DemoTicket {
	public static void main(String[] args) {
		BuyTicket concertHins = new BuyTicket(); //买张敬轩演唱会的票w
		//假设有三个人在抢票
		new Thread(concertHins, "Fans-1").start();
		new Thread(concertHins, "Fans-2").start();
		new Thread(concertHins, "Fans-3").start();
	}
}
//线程BuyTicket
class BuyTicket implements Runnable {
	private int ticketNums = 100; //我们假设有100张票
	boolean flag = true //设定一个标志
	@Override
	public void run() {
		while (flag) {
			try {
				buy(); //调用买票方法
			}catch (InterruptedException e) {
				throw new RuntimeException(e)
			}
		}
	}
}
//在涉及多线程增删改的方法前增加前缀synchronized,使其变成同步方法
private synchronized void buy() throws InterruptedException {
	if (ticketNums <= 0) {
		flag = false;
		return; //没票就设置标志为false 结束循环
	}
	System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--)
}

结果会根据每个人电脑配置不同而不同,就不展示了,但一定是顺序并且不会有小于零的情况。

不妨思考一下,synchronized前缀是给谁上了锁呢?
是这个方法吗本身?是增删改的属性吗?

点击查看思考~
其实是**锁上了拥有这个方法的对象**,在这个示例中则是**BuyTicket**

可以再想想,如果不加synchronized前缀,会怎样?
如果没有synchronized前缀,输出就不会是顺序的,甚至可能几个Fans拿到同一张票,也可能拿到-1张票

Synchronized块:对代码片段用synchronized块包裹起来

//假设你和你朋友想从同一个银行账户取钱
package com.multiThread;
public class DemoMoney {
	public static void main(String args[]) {
		Account account = new Account(100, "基金");
		Drawing you = new Drawing(50, "你");
		Drawing friend = new Drawing(100, "朋友");
		you.start();
		friend.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 currentMoney;
	public Drawing(Account account, int drawingMoney, String name) {
		super(name); //从Thread执行中继承名字
		this.account = account;
		this.drawingMoney = drawingMoney;
		}
	
	@Override
	public void run() {
		//在对属性增删改的代码片段中用synchronized块包裹,参数是同步对象
		//我们实际是对account操作,因此同步对象是account
		synchronized (account) {
			if (account.money - drwaingMoney < 0) {
			System.out.println(Thread.cuurentThread().getName() + "金额不足")
			return;
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedExpection e) {
				throw new RuntimeException(e);
			}
			account.money = account.money - drawingMoney;
			currentMoney = currentMoney + drawingMoney;
			
			System.out.println(account.name + "余额为" + account.money);
			//this.getName就是Thread.currentThread().getName();
			System.out.println(this.getName() + "手里的钱" + currentMoney)
		}
	}
}

从代码可以看出来,you线程先开始,因此结果是你可以取到钱,之后账户余额不足,你朋友就取不到了

进一步思考一下,这个可不可以用方法级别的synchronized实现呢?
答案当然是可以,但,不是在run方法前写synchronized前缀,因为这样锁的就不是account了

点击查看思考~
答案当然是可以,但,不是在run方法前写synchronized前缀,因为这样锁的就不是account了
可以在account里写同步方法然后在run中调用

Ending

我不想把每一篇文章写得太长,因此这篇文章到这里就结束吧~
总结一下:本文主要介绍了同步的概念,从what why how 三个点来展开同步这个概念
用两个示例分别展示了方法级别的synchronized块级别的synchronized
以及其中锁的细节

posted @   ZoeXxx  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示