一、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锁住了谁?的更多相关文章

  1. java synchronized究竟锁住的是什么

    刚学java的时候,仅仅知道synchronized一个线程锁.能够锁住代码,可是它真的能像我想的那样,能够锁住代码吗? 在讨论之前先看一下项目中常见关于synchronized的使用方法: publ ...

  2. synchronized到底锁住的是谁?

    本文代码仓库:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sync 先来一道校招级并发编程笔试题 题 ...

  3. synchronized锁住的是代码还是对象,以及synchronized底层实现原理

    synchronized (this)原理:涉及两条指令:monitorenter,monitorexit:再说同步方法,从同步方法反编译的结果来看,方法的同步并没有通过指令monitorenter和 ...

  4. 线程同步synchronized和ReentrantLock

    一.线程同步问题的产生及解决方案 问题的产生: Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突. 如下例:假设有一个卖票 ...

  5. 关于synchronized和ReentrantLock之多线程同步详解

    一.线程同步问题的产生及解决方案 问题的产生: Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突. 如下例:假设有一个卖票 ...

  6. synchronized是对象锁还是全局锁

    昆昆欧粑粑 2019-02-20 15:09:59 1148 收藏 1分类专栏: java学习 文章标签: synchronized 全局锁 对象锁 同步版权都可以锁!synchronized(thi ...

  7. Java中的ReentrantLock和synchronized两种锁机制的对比

    原文:http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html 多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之 ...

  8. synchronized锁住的是代码还是对象

    不同的对象 public class Sync { public synchronized void test() { System.out.println("test start" ...

  9. Java synchronized(this)锁住的是什么

    synchronized锁住的是括号里面的对象,而不是代码. 对于非static的synchronized方法,锁的就是对象本身,也就是this.

随机推荐

  1. Zabbix安装与简单配置

    目录 0. 前言 1. 安装 1.1 准备安装环境 1.1.1 下载安装包 1.1.2 修改文件配置 1.2 开始安装 2. 实验环境 2.1 简易拓扑图 2.2 基本配置 3. 配置 0. 前言 不 ...

  2. django自关联,auth模块

    一.自关联 写蛮好的一篇博客:https://www.cnblogs.com/Kingfan1993/p/9936541.html 1.一对多关联 1.表内自关联是指表内数据相关联的对象和表是相同字段 ...

  3. Java 面试-即时编译( JIT )

    当我们在写代码时,一个方法内部的行数自然是越少越好,这样逻辑清晰.方便阅读,其实好处远不止如此,通过即时编译,甚至可以提高执行时的性能,今天就让我们好好来了解一下其中的原理. 简介 当 JVM 的初始 ...

  4. 后渗透神器Cobalt Strike的安装

    0x01 简介 Cobalt Strike集成了端口转发.扫描多模式端口监听Windows exe木马,生成Windows dll(动态链接库)木马,生成java木马,生成office宏病毒,生成木马 ...

  5. 转 NAT技术详解

    NAT产生背景 今天,无数快乐的互联网用户在尽情享受Internet带来的乐趣.他们浏览新闻,搜索资料,下载软件,广交新朋,分享信息,甚至于足不出户获取一切日用所需.企业利用互联网发布信息,传递资料和 ...

  6. 修改配置文件application.properties

    附录A.常用应用程序属性 可以在application.properties文件内部application.yml,文件内部或命令行开关中指定各种属性.本附录提供了常见Spring Boot属性的列表 ...

  7. MySQL常用sql语句-----数据表的查询操作

    常用的sql语句如下,应对工作足以 1.查询指定字段 select c_id,c_age,c_name from t_student; select c_id as 编号,c_name as 姓名,c ...

  8. SSH框架项目配置和启动的加载顺序及请求的执行顺序

    1:======配置和启动====== (1)配置web.xml 配置<context-param>,其中内容为Spring的配置文件applicationContext.xml.注意&l ...

  9. 使用eclipse在tomcat中设置项目启动的虚拟路径

    很多时候我们在启动项目的时候都会在浏览器输入"localhost:+端口号+项目名称" 其实tomcat是可以省去这种麻烦的,通过设置项目的虚拟路径就可访问项目了 第一步 选择ec ...

  10. POJO和JavaBean

    1.POJO POJO(Plain Ordinary Java Object):POJO就是一个简单的普通的Java对象,它不包含业务逻辑或持久逻辑等,但不是JavaBean.EntityBean等, ...