Java靓仔_靓仔,你会synchronized吗?

悲观锁 & 乐观锁

在介绍synchronized之前,需要知道悲观锁&乐观锁。

悲观锁与乐观锁是一种广义上的概念,体现了看待线程同步的不同角度。对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定会有其他的线程来修改数据,因此在获取数据的时候会先加锁,以此确保数据不会被其他线程修改。JAVA中,synchronized关键字和Lock实现类都是悲观锁。

悲观锁分析图:

a53ce3be7050b5e7c0b5931be979c07b.png

乐观锁恰恰与之相反,乐观锁认为自己在使用数据时不会有其他线程修改数据,所以不会加锁,只是在更新数据的时候去判断之前有没有其他线程更新了这个数据。如果这个数据没有被更新,则当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新了,则根据不同实现方式执行不同操作,比如报错或者自动重试。

其实,悲观锁&乐观锁说白了,就是判断线程要不要锁住同步资源,如需要锁住,则是悲观锁,不需要锁住,就是乐观锁。

synchronized

synchronized关键字主要有两种用法,分别是同步方法和同步代码块,被synchronized修饰的方法或者代码块,在同一时间,只能被一个线程访问。

举个栗子:

售票处现在只剩下2张票,但是有10个人来抢票,到底谁可以买到票呢?

传统写法:

public class TicketMgt {

private int ticket = 2; //还剩最后2张票

public void booking() {

if (ticket > 0) {

ticket--;

System.out.print("这张票我买了!");

}

System.out.println(" 还剩:" + ticket + "张。");

}

}

10个壮汉来抢票:

public class Main {

public static void main(String[] args) {

TicketMgt ticketMgt = new TicketMgt();

for (int i = 0; i < 10; i++) {

new Thread(ticketMgt::booking).start();

}

}

}

抢票结果:

这张票我买了! 还剩:0张。

还剩:0张。

这张票我买了! 还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

What? 弄啥嘞,为什么会出现这个结果呢?这不乱套了吗?

嗯哼。是时候体现在出synchronized的重要性了,只需要用synchronized修饰booking()方法就可以啦:

public synchronized void booking() {

......

}

10个壮汉再抢一次,抢票结果:

这张票我买了! 还剩:1张。

这张票我买了! 还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

还剩:0张。

对于这个抢票结果,基本可以达到我们预期的效果了。它的实现原理:synchronized给TicketMgt 对象进行加锁了,同一时间只允许一个线程对ticket进行修改。就好比壮汉去售票处买票,synchronized相当于一把门锁,1#壮汉进去就把门锁上,2#、3#、4#等其他壮汉就在门外等着,1#壮汉买完开锁出来,其他壮汉才能接着进去买,保证每次只能进去一个买票的壮汉。

针对上面的小栗子,稍微讲讲synchronized的两种用法吧:

用在方法上,又分实例方法和类方法:

/**

* ①synchronized修饰实例方法

* 锁的是对象,同一时间只能有一个线程访问方法。

*/

public synchronized void doStd() {

// TODO: 2020/7/28

}

/**

* ②synchronized修饰类方法

* 锁住的是类,同一时间只能有一个线程访问这个类

*/

public synchronized static void doStaticStd() {

// TODO: 2020/7/28

}

用在方法块上,也分实例方法和类方法:

/**

* ①实例方法

* 当在某个线程中执行这段代码块,该线程会获取this对象的锁,从而使得其他线程无法同时访问该代码块

*/

public void doSynchronizedStd() {

synchronized (this) {

// TODO: 2020/7/28

}

/**

* 当一个线程访问对象的一个synchronized(this)同步代码块时

* 另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

*/

// TODO: 2020/7/28 可被其他线程访问到

}

/**

* ②类方法

* 锁住的是类,这个类的所有对象是同一把锁,效果与synchronized修饰类方法一样

*/

public static void doSynchronizedStaticStd() {

synchronized (MultiThreads.class) {

// TODO: 2020/7/28

}

}

两者区别:synchronized代码块的锁粒度要比 synchronized方法小一些,因为 synchronized代码块所在的方法里还可以有其他代码。

synchronized 是非公平锁,也是可重入锁。

公平锁:获取不到锁的时候,会自动加入队列,等待线程释放后,队列的第一个线程获取锁

非公平锁:获取不到锁的时候,会自动加入队列,等待线程释放锁后所有等待的线程同时去竞争

可重入锁:也叫递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞

代码示例:

public synchronized void booking() {

System.out.println("提交订单");

WeChatPay();

}

public synchronized void WeChatPay() {

System.out.println("微信支付");

}

在上面的代码中,类中的两个方法都是被内置锁synchronized修饰的,booking()方法中调用WeChatPay()方法。因为内置锁是可重入的,所以同一个线程在调用booking()时可以直接获得当前对象的锁,进入WeChatPay()进行操作。

看到这里,是不是对synchronized并没有那么陌生了呢。非常感谢你能看到最后,如果能帮助到你,是我的荣幸。后期可能还会写一篇关于synchronized实现原理的文章,还有synchronized是如何保证的原子性、顺序性和可见性。

本文地址:https://blog.csdn.net/qq_36270361/article/details/107393278

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值