Intro
本篇文章主要关于多线程"同步"以及"锁"的相关内容~
正文
同步(Synchronize)
概念
“同步”是基于“并发”的需求而出现的
所谓并发,就是同一个对象被多个线程同时操作,比如两个人同时从同一个账户取钱,再比如春运抢票。
多个线程同时使用一个资源,必然会造成混乱。想象一下从前的线下购票厅,如果大家都不排队而拥挤着抢票,不仅流程混乱,更可能少票少钱。而排队则可以让整个流程有序顺畅进行。
同理,同步是一种等待机制,多个需要同时访问同一个对象的线程会进入对象的等待池形成队列,等前面的线程使用资源完毕释放后,下一个线程再使用。使用期间怎么做到防止其他线程访问,则是在线程访问时加入锁机制,如此便可独占资源。释放的过程也就是解锁的过程。
因此,同步其实就是排队+锁
但是,鱼和熊掌不可兼得,保证了安全性,必然会牺牲一定的性能,锁存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致较多的上下文切换和调度延时
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置
实现
要实现同步,可以通过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
以及其中锁的细节