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.
随机推荐
- jdbc 以及 事务的java类编写
package com.gaosheng.utils; import java.sql.Connection;import java.sql.SQLException; import javax.sq ...
- Cocos Creator 通用框架设计 —— 网络
在Creator中发起一个http请求是比较简单的,但很多游戏希望能够和服务器之间保持长连接,以便服务端能够主动向客户端推送消息,而非总是由客户端发起请求,对于实时性要求较高的游戏更是如此.这里我们会 ...
- tp5中使用中间控制器代理路由,以避免创建过多的无用控制器方法
在写项目的时候偶尔会加载一些不需要传递参数的静态视图,例如 class Index extends Common { public function index() { return $this-&g ...
- DM7经常使用的命令汇总
由于DM7兼容oracle ,所以当你不知道某个命令时,大抵就是可以参照oracle的命令及语法,当然有极少的情况会不一样.常用命令如下: 1.连接登录 disql SYSDBA/SYSDBA@223 ...
- vue实现跑马灯效果
vue实现跑马灯效果为阿中哥哥应援 1.效果图 2.实现代码 <!DOCTYPE html> <html lang="en"> <head> & ...
- 使用 Helm Chart 部署及卸载 istio
部署 istio 1.添加 istio 官方的 helm 仓库 helm repo add istio https://storage.googleapis.com/istio-release/rel ...
- ES6基本语法入门
一.用let代替var声明变量 ES5中,我们可以在代码中任意位置声明变量,甚至可以重写已经声明的变量,ES6引入了一个let关键字,它是新的var. let language = 'javascri ...
- 09 python学习笔记-操作excel(九)
python操作excel使用xlrd.xlwt和xlutils模块,xlrd模块是读取excel的,xlwt模块是写excel的,xlutils是用来修改excel的.这几个模块可以使用pip安装, ...
- Spring MVC(1)Spring MVC的初始化和流程以及SSM的实现
一.Spring MVC概述 1.Spring MVC 的架构 对于持久层而言,随着软件的发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足迁移数据库的要求.与此 ...
- Java基础(二十五)Java IO(2)文件File类
File类是一个与流无关的类.File类的对象可以获取文件及其文件所在的目录.文件的长度等信息. 1.File对象的常用构造方法. (1)File(String pathname) File file ...