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.
随机推荐
- 52个有效方法(4) - 多用类型常量,少用#define预处理指令
局部常量 在实现文件中使用 static const 来定义"只在编译单元内可见的常量"(translation-unit-specific constant).其命名规则为在前面 ...
- 网络驱动之net_device结构体
在Linux系统中,网络设备都被抽象为struct net_device结构体.它是网络设备硬件与上层协议之间联系的接口,了解它对编写网络驱动程序非常有益,所以本文将着手简要介绍linux-2.6.3 ...
- 深入理解C# 委托(delegate)-戈多编程
今天来谈谈委托,深入理解委托,本文来自各大神经验总结. 1.委托是什么? 委托类型的声明与方法签名相似. 它有一个返回值和任意数目任意类型的参数,是一种可用于封装命名方法或匿名方法的引用类型. 委托类 ...
- Java工程师学习指南(完结篇)
Java工程师学习指南 完结篇 先声明一点,文章里面不会详细到每一步怎么操作,只会提供大致的思路和方向,给大家以启发,如果真的要一步一步指导操作的话,那至少需要一本书的厚度啦. 因为笔者还只是一名在校 ...
- 最新2019Pycharm安装破解教程!内附破解码!
本教程仅用作个人学习,请勿用于商业获利,造成后果自负!!! Pycharm安装 在这插一个小话题哈,Pycharm只是一个编译器,并不能代替Python,如果要使用Python,还是需要安装Pytho ...
- 从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用
标题:从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用. 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/1171 ...
- 第3章(1) Linux内核相关概念
Linux内核的组成 1. Linux内核源代码的目录结构 arch:包含和硬件体系结构相关的代码,每种平台占一个相应的目录,如 i386.arm. arm64.powerpc.mips 等.Linu ...
- ArrayList源码解析[一]
ArrayList源码解析[一] 欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 在工作中集合list集合用的相对来 ...
- 第三方软件 radmin提权
一款远控工具 端口扫描 4899 端口 上传 radmin.asp 木马读取 radmin的加密密文 下载radmin工具连接
- Java Stream函数式编程图文详解(二):管道数据处理
一.Java Stream管道数据处理操作 在本号之前发布的文章<Java Stream函数式编程?用过都说好,案例图文详解送给你>中,笔者对Java Stream的介绍以及简单的使用方法 ...