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.
随机推荐
- Zabbix安装与简单配置
目录 0. 前言 1. 安装 1.1 准备安装环境 1.1.1 下载安装包 1.1.2 修改文件配置 1.2 开始安装 2. 实验环境 2.1 简易拓扑图 2.2 基本配置 3. 配置 0. 前言 不 ...
- django自关联,auth模块
一.自关联 写蛮好的一篇博客:https://www.cnblogs.com/Kingfan1993/p/9936541.html 1.一对多关联 1.表内自关联是指表内数据相关联的对象和表是相同字段 ...
- Java 面试-即时编译( JIT )
当我们在写代码时,一个方法内部的行数自然是越少越好,这样逻辑清晰.方便阅读,其实好处远不止如此,通过即时编译,甚至可以提高执行时的性能,今天就让我们好好来了解一下其中的原理. 简介 当 JVM 的初始 ...
- 后渗透神器Cobalt Strike的安装
0x01 简介 Cobalt Strike集成了端口转发.扫描多模式端口监听Windows exe木马,生成Windows dll(动态链接库)木马,生成java木马,生成office宏病毒,生成木马 ...
- 转 NAT技术详解
NAT产生背景 今天,无数快乐的互联网用户在尽情享受Internet带来的乐趣.他们浏览新闻,搜索资料,下载软件,广交新朋,分享信息,甚至于足不出户获取一切日用所需.企业利用互联网发布信息,传递资料和 ...
- 修改配置文件application.properties
附录A.常用应用程序属性 可以在application.properties文件内部application.yml,文件内部或命令行开关中指定各种属性.本附录提供了常见Spring Boot属性的列表 ...
- MySQL常用sql语句-----数据表的查询操作
常用的sql语句如下,应对工作足以 1.查询指定字段 select c_id,c_age,c_name from t_student; select c_id as 编号,c_name as 姓名,c ...
- SSH框架项目配置和启动的加载顺序及请求的执行顺序
1:======配置和启动====== (1)配置web.xml 配置<context-param>,其中内容为Spring的配置文件applicationContext.xml.注意&l ...
- 使用eclipse在tomcat中设置项目启动的虚拟路径
很多时候我们在启动项目的时候都会在浏览器输入"localhost:+端口号+项目名称" 其实tomcat是可以省去这种麻烦的,通过设置项目的虚拟路径就可访问项目了 第一步 选择ec ...
- POJO和JavaBean
1.POJO POJO(Plain Ordinary Java Object):POJO就是一个简单的普通的Java对象,它不包含业务逻辑或持久逻辑等,但不是JavaBean.EntityBean等, ...