synchronized和ReentrantLock锁住了谁?
一、synchronized
案例1:
public class LockDemo{
public static void main(String[] args) throws Exception {
Human human = new Human();
new Thread(() -> {
try {
human.drink();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);//确保A线程先启动
new Thread(() -> {
Human.sleep();
},"B").start();
}
}
class Human{
public void eat() {
System.out.println(Thread.currentThread().getName()+ ": *****eat*****");
}
public synchronized void drink() throws Exception {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+": *****drink*****");
}
public synchronized static void sleep() {
System.out.println(Thread.currentThread().getName()+": *****sleep*****");
}
}
由于输出结果是动态的不好截图,是能口述输出结果:先输出B:******sleep*****,2.9秒后输出A:******drink*****
在main方法中,使用Thread.sleep(100)秒让主线程睡眠,确保A线程先于B线程拿到资源。首先,我们知道sleep方法并不会是释放锁对象,按理说输出结果应该是三秒后同时输出A:******drink*****和B:******sleep*****,怎么会出现上面的结果呢?原因很简单,dink方法上的synchronized和sleep方法上的synchronized锁的不是同一个资源!
当在非静态方法上加锁,锁的是类的实例对象。当在静态方法上加锁,所得就是类的对象。也就是说当线程A调用加锁方法drink后,其他线程不能再调用此方法的加锁资源,但是线程B之所以可以调用sleep方法,是因为线程B拿到的是类对象的锁,两者并不冲突,就好像两个人进两扇门,谁也不碍着谁。
案例2:
public class LockDemo{
public static void main(String[] args) throws Exception {
Human human = new Human();
new Thread(() -> {
try {
human.drink();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);//确保A线程先启动
new Thread(() -> {
human.eat();
},"B").start();
}
}
class Human{
public void eat() {
System.out.println(Thread.currentThread().getName()+ ": *****eat*****");
}
public synchronized void drink() throws Exception {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+": *****drink*****");
}
public synchronized static void sleep() {
System.out.println(Thread.currentThread().getName()+": *****sleep*****");
}
}
输出结果是:先输出B:******eat*****,2.9秒后输出A:******drink*****
分析:首先不用多虑,A线程拿到资源后,锁住了贡献资源human对象,但是B线程访问的并不是加了锁的方法,而是普通方法,这就好像两个人去上厕所,一个人要蹲大号,一个人是小号,小号完成后,蹲大的才刚刚开始【如果你在吃饭,请你原谅我,实在想不出什么形象的案例】
过多的案例不再多举,只需要搞明白一点:锁是对象的一部分,而不是线程的一部分。线程只是暂时的持有锁,在线程持有锁的这段时间里,其他线程不能访问此对象的同步资源,可以访问此对象的费同步资源。
二、线程通信以及while
上面的案例中并未涉及到线程通信,然而现实的业务纷繁复杂,通常都是线程之间的协作完成业务的处理,最典型的就是———生产者消费者模式
实现复杂的业务需要更加灵活的锁——Lock,Lock接口有多个实现类,提供了更加灵活的结构,可以为同一把锁“配多把钥匙”。
案例1:
public class LockDemo{
public static void main(String[] args) throws Exception {
Shop shop = new Shop();
new Thread(() -> {
try {
shop.produce();
} catch (Exception e) {
e.printStackTrace();
}
},"P_A").start();
new Thread(() -> {
try {
shop.produce();
} catch (Exception e) {
e.printStackTrace();
}
},"P_B").start();
new Thread(() -> {
try {
shop.consume();
} catch (Exception e) {
e.printStackTrace();
}
},"C_C").start();
new Thread(() -> {
try {
shop.consume();
} catch (Exception e) {
e.printStackTrace();
}
},"C_D").start();
}
}
class Shop{
int number = 1;
public synchronized void produce() throws Exception {
if(number != 0) {
wait();
}
number++;
System.out.println(Thread.currentThread().getName()+": "+number);
notifyAll();
}
public synchronized void consume() throws Exception {
if(number == 0) {
wait();
}
number--;
System.out.println(Thread.currentThread().getName()+": "+number);
notifyAll();
}
}
输出:
C_C: 0
P_B: 1
P_A: 2
C_D: 1
灵魂质问:为什么会输出 2 ?
情况可以这样发生:当线程P_A抢到资源后,发现初始库存为1,于是进入wait状态,释放锁资源,此时线程C_C抢到资源,执行number--,输出C_C:0,然后唤醒所有线程,此时P_B抢到资源,发现number=0,于是执行number++,然后输出P_B:1,然后释放锁,唤醒其他线程,此时CPU转给了P_A,线程P_A先加载上下文,不会再去进行if判断,因为之前判断过了,于是执行number++,输出了P_A:2。问题就出在这个if,所以在同步方法的flag判断是否执行时,杜绝使用if,一律使用while。当使用了while后,当线程P_A加载完上下文继续执行时,会再执行一遍判断,只有当while循环条件不成立时,才会执行后续代码。
在这里再提一下notify和notifyAll的区别:当有多个线程时,notify会随机唤醒一个线程,被唤醒的线程百分百获得资源,但是具体唤醒哪一个是不确定的。而是用notifyAll时,会唤醒其实所有线程,所有线程再次抢占同步资源,谁抢到谁执行。
案例2:
上面的案例只是简答演示了,可以通过定义flag的方式,控制线程的执行。但是涉及到更加复杂情况时,还可以使用更加优秀的解决办法:组合使用Lock和Condition
public class LockDemo{
public static void main(String[] args) throws Exception {
Shop shop = new Shop();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
shop.superproduce();
}
} catch (Exception e) {
e.printStackTrace();
}
},"P_A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
shop.produce();
}
} catch (Exception e) {
e.printStackTrace();
}
},"P_B").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
shop.consume();
}
} catch (Exception e) {
e.printStackTrace();
}
},"C_C").start();
}
}
class Shop{
int number = 0;
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
public void superproduce() throws InterruptedException{
lock.lock();
try {
while(number != 0) {
c1.await();
}
number+=2;
System.out.println(Thread.currentThread().getName()+": "+number);
c2.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void produce() throws InterruptedException{
lock.lock();
try {
while(number != 2) {
c2.await();
}
number++;
System.out.println(Thread.currentThread().getName()+": "+number);
c3.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void consume() throws InterruptedException{
lock.lock();
try {
while(number != 3) {
c3.await();
}
number-=3;
System.out.println(Thread.currentThread().getName()+": "+number);
c1.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
本案例大概意思为:当库存为0时,加快生产,当库存为2时,普通生产,当库存为3时,提供给消费者消费,轮番十次。由于初始初始状态为0,就算是P_B和C_C线程先抢到了资源,由于条件不符合,也只能将执行权交给P_A,当P_A执行完成后,标记线程P_B的执行。
以此类推
synchronized和ReentrantLock锁住了谁?的更多相关文章
- java synchronized究竟锁住的是什么
刚学java的时候,仅仅知道synchronized一个线程锁.能够锁住代码,可是它真的能像我想的那样,能够锁住代码吗? 在讨论之前先看一下项目中常见关于synchronized的使用方法: publ ...
- synchronized到底锁住的是谁?
本文代码仓库:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sync 先来一道校招级并发编程笔试题 题 ...
- synchronized锁住的是代码还是对象,以及synchronized底层实现原理
synchronized (this)原理:涉及两条指令:monitorenter,monitorexit:再说同步方法,从同步方法反编译的结果来看,方法的同步并没有通过指令monitorenter和 ...
- 线程同步synchronized和ReentrantLock
一.线程同步问题的产生及解决方案 问题的产生: Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突. 如下例:假设有一个卖票 ...
- 关于synchronized和ReentrantLock之多线程同步详解
一.线程同步问题的产生及解决方案 问题的产生: Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突. 如下例:假设有一个卖票 ...
- synchronized是对象锁还是全局锁
昆昆欧粑粑 2019-02-20 15:09:59 1148 收藏 1分类专栏: java学习 文章标签: synchronized 全局锁 对象锁 同步版权都可以锁!synchronized(thi ...
- Java中的ReentrantLock和synchronized两种锁机制的对比
原文:http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html 多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之 ...
- synchronized锁住的是代码还是对象
不同的对象 public class Sync { public synchronized void test() { System.out.println("test start" ...
- Java synchronized(this)锁住的是什么
synchronized锁住的是括号里面的对象,而不是代码. 对于非static的synchronized方法,锁的就是对象本身,也就是this.
随机推荐
- Windows下如何调试驱动程序
Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html 一.配置Windbg使用双机调试 win10中“windbg+vm ...
- 10月27日Java整理
实验一:凯撒密码 import java.util.Scanner; //zhanxinwu,October,25,2016 public class Addmi { public static vo ...
- 算法学习之剑指offer(九)
一 题目描述 求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C). public class Solution ...
- MySQL 插入记录时自动更新时间戳
将字段设置成timestamp类型,同时默认值设置成 CURRENT_TIMESTAMP.
- JavaScript函数总结—越努力,越幸运!
JavaScript 函数总结 JavaScript为web的编程脚本语言. JavaScript由三部分组成:emc(语法) dom(文档对象模型) bom(浏览器对象模型). [函数的定义] 1. ...
- 收益 or 挑战?Serverless 究竟给前端带来了什么
作者 | 黄子毅(紫益) 阿里前端技术专家 导读:前端开发者是最早享受到 "Serverless" 好处的群体,因为浏览器就是一个开箱即用.甚至无需为计算付费的环境!Serverl ...
- java-try,return和finally相遇时的各种情况
今天碰到了这样一个问题:使用try,return和finally会碰到的各种情况1,try中有return时,执行顺序:2,try和finally中都有return时,执行顺序:3,运算代码在fina ...
- 第三方软件 Serv-u提权
Serv-U FTP Server,是一种被广泛运用的FTP服务器端软件,支持3x/9x/ME/NT/2K等全Windows系列.可以设定多个FTP服务器.限定登录用户的权限.登录主目录及空间大小等 ...
- python中函数定义与调用顺序问题
def main(): try: mtd(3) except Exception as e: print("程序出现异常:", e) mtd(3) def mtd(a): if a ...
- .Net Core 3.0 IdentityServer4 快速入门02
.Net Core 3.0 IdentityServer4 快速入门 —— resource owner password credentials(密码模式) 一.前言 OAuth2.0默认有四种授权 ...