上一节讲到Synchronized关键字,synchronized上锁的区域:对象锁=方法锁/类锁

本节补充介绍一下synchronized锁重入:

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时,是可以再次得到该对象锁的。这也证明在一个synchronized方法/块内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

(写在例子前面一个维基百科关于可重入的概念)若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期的结果。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。通俗来说:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

public class Service {
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized public void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
} public class MyThread extends Thread {
@Override
public void run(){
Service service = new Service();
service.service1();
}
} public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}

可重入锁的概念是自己可以再次获得自己的内部锁,比如有一个线程获得了某个对象的锁,此时这个对象的锁还没有释放,当他再次想要获得这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

此时就会出现问题:对象的锁和类锁?区别是什么

1. 类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段;

2.对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段;

对象锁也叫方法锁,是针对一个对象实例的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,而并不会对其他对象实例的锁产生任何影响,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞。

类锁是锁住整个类,当有多个线程来声明这个类的对象时候将会被阻塞,直到拥有这个类锁的对象被销毁或者主动释放了类锁,这个时候在被阻塞的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住,即一句话:不管多少个对象,多少个对象,共用一把锁,且只有一把,不管怎么调用,都会同步

这里介绍一下可重入锁与不可重入锁,首先列举一个不可重入锁的例子:

  

public class Lock {
private boolean isLocked = false; public synchronized void lock() throws InterruptedException {
while (isLocked) {
System.out.println("isLocked...");
wait();
}
isLocked = true;
System.out.println("Locked! ... ");
} public synchronized void unlock() {
isLocked = false;
System.out.println("notify() !");
notify();
}
} public class Count {
Lock lock = new Lock(); public void print() throws InterruptedException {
lock.lock();
doAdd();
lock.unlock();
} public void doAdd() throws InterruptedException {
lock.lock();
//do something
System.out.println("i'm doing something...");
lock.unlock();
} public static void main(String[] args) throws InterruptedException {
Count c = new Count();
c.print();
}
}

运行结果为:

Locked! ...
isLocked...

当调用print()方法时,获得了锁,这时就无法再调用doAdd()方法,这时必须先释放锁才能调用,所以称这种锁为不可重入锁,也叫自旋锁。

同一个类的多个方法被synchronized修饰,他们是同一把锁吗(针对上文代码中的service1 service2 service3之间的调用)

synchronized是Java中的关键字,是一种同步锁。修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;修饰方法时锁定的是调用该方法的对象,即如果一个对象中有两个方法同时被synchronized,则同一个对象,调用这两个方法时,只能同时执行一个。但它并不能使调用该方法的多个对象在执行顺序上互斥。

如以下代码,只能同时执行set方法或out方法:

class Resoure{
private String name;
private int count = 1;
//生产者调用
public synchronized void set(String name){
this.name = name+count;
//编号自增
count++;
//打印生产了哪个产品
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
}
//消费者调用
public synchronized void out(){
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
}
}

再来一个可重入代码的对比:

public class Xttblog extends SuperXttblog {
public static void main(String[] args) {
Xttblog child = new Xttblog();
child.doSomething();
} public synchronized void doSomething() {
System.out.println("child.doSomething()" + Thread.currentThread().getName());
doAnotherThing(); // 调用自己类中其他的synchronized方法
} private synchronized void doAnotherThing() {
super.doSomething(); // 调用父类的synchronized方法
System.out.println("child.doAnotherThing()" + Thread.currentThread().getName());
}
} class SuperXttblog {
public synchronized void doSomething() {
System.out.println("father.doSomething()" + Thread.currentThread().getName());
}
}

输出结果为:

child.doSomething()Thread-5492
father.doSomething()Thread-5492
child.doAnotherThing()Thread-5492

现在可以验证出 synchronized 是可重入锁了吧!因为这些方法输出了相同的线程名称,表明即使递归使用synchronized也没有发生死锁,证明其是可重入的。

还看不懂?那我就再解释下!

这里的对象锁只有一个,就是 child 对象的锁,当执行 child.doSomething 时,该线程获得 child 对象的锁,在 doSomething 方法内执行 doAnotherThing 时再次请求child对象的锁,因为synchronized 是重入锁,所以可以得到该锁,继续在 doAnotherThing 里执行父类的 doSomething 方法时第三次请求 child 对象的锁,同样可得到。如果不是重入锁的话,那这后面这两次请求锁将会被一直阻塞,从而导致死锁。

所以在 java 内部,同一线程在调用自己类中其他 synchronized 方法/块或调用父类的 synchronized 方法/块都不会阻碍该线程的执行。就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。因为java线程是基于“每线程(per-thread)”,而不是基于“每调用(per-invocation)”的(java中线程获得对象锁的操作是以线程为粒度的,per-invocation 互斥体获得对象锁的操作是以每调用作为粒度的)。

直接看结论:

从互斥锁的设计上来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功,在java中synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。

可重入锁的实现原理?

看到这里,你终于明白了 synchronized 是一个可重入锁。但是面试官要再问你,可重入锁的原理是什么?

对不起,你又卡壳了。

可重入锁的原理。具体我们后面再写 ReentrantLock 的时候来验证或看它源码。

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

 

Java多线程的所有代码均在 github「nikura44」

java多线程学习笔记(四)的更多相关文章

  1. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

  2. JAVA多线程学习笔记(1)

    JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...

  3. Java多线程学习(四)等待/通知(wait/notify)机制

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  4. 零拷贝详解 Java NIO学习笔记四(零拷贝详解)

    转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...

  5. Java多线程学习笔记(一)——多线程实现和安全问题

    1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...

  6. Java IO学习笔记四:Socket基础

    作者:Grey 原文地址:Java IO学习笔记四:Socket基础 准备两个Linux实例(安装好jdk1.8),我准备的两个实例的ip地址分别为: io1实例:192.168.205.138 io ...

  7. Java多线程学习笔记

    进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...

  8. Java多线程学习笔记--生产消费者模式

    实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...

  9. Java 多线程学习笔记:生产者消费者问题

    前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后 ...

随机推荐

  1. 3403. 题解【NOIP2013模拟】数列变换 (Standard IO)

    先看题目: Description 小X 看到堆成山的数列作业十分头疼,希望聪明的你来帮帮他.考虑数列A=[A1,A2,...,An],定义变换f(A,k)=[A2,A3,,,,.Ak,A1,Ak+2 ...

  2. 用函数递归的方法解决古印度汉诺塔hanoi问题

    问题源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上.并且规 ...

  3. JavaFx开发桌面软件

    JavaFx开发桌面软件 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} Jav ...

  4. Workbox使用策略

    1.什么是Workbox Strategies? 当service workers 首次被引入时,可以设定一组常见的缓存策略. 缓存策略是一种模式,用于确定service workers 在收到fet ...

  5. HDU 4886 TIANKENG’s restaurant(Ⅱ) ( 暴力+hash )

    TIANKENG’s restaurant(Ⅱ) Time Limit: 16000/8000 MS (Java/Others)    Memory Limit: 130107/65536 K (Ja ...

  6. 修改bug 提交出错:操作失败: 无法更改关系,因为一个或多个外键属性不可以为 null

    提交出错:操作失败: 无法更改关系,因为一个或多个外键属性不可以为 null.对关系作出更改后,会将相关的外键属性设置为 null 值.如果外键不支持 null 值,则必须定义新的关系,必须向外键属性 ...

  7. Juqery插件编写 基础说明

    <!DOCTYPE html> <html> <head> <meta name="viewport" content="wid ...

  8. 【知识强化】第四章 网络层 4.3 IP

    这节课我们来学习一下IP数据报的格式.那之所以把路由算法这一小节跳过呢,就是因为我们之后会要讲到路由的选择协议.那在路由选择协议这一块讲路由算法,我觉得是比较合适的.那我们先来看一下这节课要讲的知识. ...

  9. 配置阿里云SLB全站HTTPS集群

    配置阿里云SLB全站HTTPS集群(以下内容仅为流程,信息可能有些对应不上) 1 登录阿里云购买两台实例 1.1 按量付费购买两台实例 1.2 配置网络可以不选择分配外网 1.3 自定义密码 1.4 ...

  10. js变量var与let的区别

    1.作用域 通过var定义的变量,作用域是整个封闭函数,是全域的 .通过let定义的变量,作用域是在块级或是子块中. for (let i = 0; i < 10; i++) { // ... ...