如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系

字符串作为锁对象的参考

一、synchronized的性质

1、可重入性

官方解释:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获得该锁;

通俗解释:获得一次锁之后,如果调用其它同步方法,不需要重新获取锁,可以直接使用;

在方法1中调用方法2,两个方法都是同步方法,在获取方法1的锁之后,是可以直接调用方法2,而不需要重新获取锁,这就是可重入性,可重入性是线程范围,可以递归进入自己的方法,也可以调用同类的其它方法,还可以调用其它类的方法;内部实现原理是一个锁的计数器,使用一次增加1,释放一次减少1,直至为0,即释放锁;

2、不可中断性

通俗解释:一旦这个锁被别人获得了,如果我想获得,只能等待或者阻塞,直到别的线程释放这个锁,如果别人永远不释 放锁,我只能永远等下去;很执着!

Lock锁:Lock锁拥有中断的能力,如果觉得等待时间太长,有两个选择:

1、有权中断现在已经获取到锁的线程的执行;

2、不想等了,直接退出;

3、可见性

即当读写两个线程同时访问同一个变量时,synchronized用于确保写线程更新变量后,会更新到主内存上,读线程再访问该变量时可以读取到该变量最新的值。

二、synchronized的缺陷

效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时

不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活

无法知道是否成功获得锁,相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败,.....

三、synchronized的注意事项

1、锁对象不能为空;

2、作用域不宜过大;

3、避免死锁;

四、synchronized与Lock

  • synchronized是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。
  • synchronized会自动释放锁,而Lock必须手动释放锁。
  • synchronized是不可中断的,Lock可以中断也可以不中断。
  • 通过Lock可以知道线程有没有拿到锁,而synchronized不能。
  • synchronized能锁住方法和代码块,而Lock只能锁住代码块。
  • Lock可以使用读锁提高多线程读效率。
  • synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。

五、对象锁(monitor)机制

synchronized的具体底层实现。先写一个简单的dem

public class SynchronizedDemo {public static void main(String[] args) {synchronized (SynchronizedDemo.class) {}method();}private static void method() {}
}

上面的代码中有一个同步代码块,锁住的是类对象,并且还有一个同步静态方法,锁住的依然是该类的类对象。编译之后,切换到SynchronizedDemo.class的同级目录之后,然后用javap -v SynchronizedDemo.class查看字节码文件:

如图,上面用黄色高亮的部分就是需要注意的部分了,这也是添Synchronized关键字之后独有的。

1、执行同步代码块后首先要先执行monitorenter指令,退出的时候monitorexit指令。

2、通过分析之后可以看出,使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。

3、而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到monitor。

4、上面的demo中在执行完同步代码块之后紧接着再会去执行一个静态同步方法,而这个方法锁的对象依然就这个类对象,那么这个正在执行的线程还需要获取该锁吗?

5、答案是不必的,从上图中就可以看出来,执行静态同步方法的时候就只有一条monitorexit指令,并没有monitorenter获取锁的指令。

6、这就是锁的重入性,即在同一锁程中,线程不需要再次获取同一把锁。Synchronized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一

任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态

下图表现了对象,对象监视器,同步队列以及执行线程状态之间的关系:

该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。

六、锁获取和锁释放的内存语义

基于java内存抽象模型的Synchronized的内存语义。

从上图可以看出,线程A会首先先从主内存中读取共享变量a=0的值然后将该变量拷贝到自己的本地内存,进行加一操作后,再将该值刷新到主内存,整个过程即为线程A 加锁-->执行临界区代码-->释放锁相对应的内存语义。

线程B获取锁的时候同样会从主内存中共享变量a的值,这个时候就是最新的值1,然后将该值拷贝到线程B的工作内存中去,释放锁的时候同样会重写到主内存中。

从整体上来看,线程A的执行结果(a=1)对线程B是可见的,实现原理为:释放锁的时候会将值刷新到主内存中,其他线程获取锁时会强制从主内存中获取最新的值。

下面对锁释放和锁获取的内存语义做个总结:

  • 线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
  • 线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
  • 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。

七、Lock

Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。本质上Lock仅仅是一个接口

(1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是ReentrantLock(可重入锁),ReadWriteLock(读写锁)的代表实现类是ReentrantReadWriteLock。

Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。
ReadWriteLock 接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。

(2)Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。

1、Lock接口实现类的使用

// 获取锁
void lock()
// 如果当前线程未被中断,则获取锁,可以响应中断
void lockInterruptibly()
// 返回绑定到此 Lock 实例的新 Condition 实例
Condition newCondition()
// 仅在调用时锁为空闲状态才获取该锁,可以响应中断
boolean tryLock()
// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
boolean tryLock(long time, TimeUnit unit)
// 释放锁
void unlock()

1.1. lock()

lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。

如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。

因此,一般来说,使用Lock必须在try…catch…块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock = ...;
lock.lock();
try{//处理任务
}catch(Exception ex){}finally{lock.unlock();   //释放锁
}

1.2 tryLock() & tryLock(long time, TimeUnit unit)

  tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,也就是说,这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。

  tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

  一般情况下,通过tryLock来获取锁时是这样使用的:

Lock lock = ...;
if(lock.tryLock()) {try{//处理任务}catch(Exception ex){}finally{lock.unlock();   //释放锁}
}else {//如果不能获取锁,则直接做其他事情
}

1.3. lockInterruptibly() 

lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程 正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。

例如,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出 InterruptedException,但推荐使用后者。因此,lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {lock.lockInterruptibly();try {  //.....}finally {lock.unlock();}
}

注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。

因此,当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。与 synchronized 相比,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

2、Lock的实现类 ReentrantLock

ReentrantLock,即 可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。

  构造方法(不带参数 和带参数  true: 公平锁; false: 非公平锁):

/*** Creates an instance of {@code ReentrantLock}.* This is equivalent to using {@code ReentrantLock(false)}.*/public ReentrantLock() {sync = new NonfairSync();}/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockThread {Lock lock = new ReentrantLock(); public void lock(String name) {  // 获取锁  lock.lock();  try {  System.out.println(name + " get the lock");  // 访问此锁保护的资源  } finally {  // 释放锁  lock.unlock();  System.out.println(name + " release the lock");  }  }  public static void main(String[] args) {LockThread lt = new LockThread();  new Thread(() -> lt.lock("A")).start();  new Thread(() -> lt.lock("B")).start();  }
}

  从执行结果可以看出,A线程和B线程同时对资源加锁,A线程获取锁之后,B线程只好等待,直到A线程释放锁B线程才获得锁。

总结一下

Lock提供了比synchronized更多的功能。但是要注意以下几点:

1)synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。

2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

3、ReadWriteLock锁

ReadWriteLock 接口只有两个方法:

//返回用于读取操作的锁
Lock readLock()
//返回用于写入操作的锁
Lock writeLock() 

ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持,而写入锁是独占的。

【例子】三个线程同时对一个共享数据进行读写

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;class Queue {//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。private Object data = null;ReadWriteLock lock = new ReentrantReadWriteLock();// 读数据public void get() {// 加读锁lock.readLock().lock();try {System.out.println(Thread.currentThread().getName() + " be ready to read data!");Thread.sleep((long) (Math.random() * 1000));System.out.println(Thread.currentThread().getName() + " have read data :" + data);} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放读锁lock.readLock().unlock();}}// 写数据public void put(Object data) {// 加写锁lock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + " be ready to write data!");Thread.sleep((long) (Math.random() * 1000));this.data = data;System.out.println(Thread.currentThread().getName() + " have write data: " + data);} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放写锁lock.writeLock().unlock();}}
}public class ReadWriteLockDemo {public static void main(String[] args) {final Queue queue = new Queue();//一共启动6个线程,3个读线程,3个写线程for (int i = 0; i < 3; i++) {//启动1个读线程new Thread() {public void run() {while (true) {queue.get();}}}.start();//启动1个写线程new Thread() {public void run() {while (true) {queue.put(new Random().nextInt(10000));}}}.start();}}
}

执行结果

锁的相关概念介绍

1、可重入锁
如果锁具备可重入性,则称作为 可重入锁 。像 synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了 锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

class MyClass {public synchronized void method1() {method2();}public synchronized void method2() {}
}

2、可中断锁

  顾名思义,可中断锁就是可以响应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
  如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

3、公平锁

  公平锁即 尽量 以请求锁的顺序来获取锁。比如,同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。而非公平锁则无法保证锁的获取是按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。

  在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁

参考:

synchronized锁定的到底是什么? - 知乎

https://www.cnblogs.com/wangwudi/p/12302668.htm

lockl

java 锁 Lock接口详解 - myseries - 博客园

Synchronized Lock 锁 同步相关推荐

  1. Java 对synchronized的补充Lock锁

    Java并发编程:Lock 从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchr ...

  2. JUC多线程:synchronized锁机制原理 与 Lock锁机制

    前言: 线程安全是并发编程中的重要关注点,造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案,当存在多 ...

  3. java 多线程 串行 加锁_java多线程 synchronized 与lock锁 实现线程安全

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 通过卖火车票的例子 火车站要卖票,我们 ...

  4. java并发,同步synchronize和lock锁的使用方法和注意,死锁案例分析

    1.什么是线程安全问题 多个线程同时共享同一个全局变量或者静态变量的时候,某个线程的写操作,可能会影响到其他线程操作这个变量.所有线程读一个变量不会产生线程安全问题. 实际场景就是火车站买票问题:剩余 ...

  5. (删)Java线程同步实现二:Lock锁和Condition

    在上篇文章(3.Java多线程总结系列:Java的线程同步实现)中,我们介绍了用synchronized关键字实现线程同步.但在Java中还有一种方式可以实现线程同步,那就是Lock锁. 一.同步锁 ...

  6. synchronized对象锁的同步和异步问题

    先上代码: public class MyObject {public synchronized void method1(){try {System.out.println(Thread.curre ...

  7. 萌新必入!手把手教你玩转Synchronized锁和Lock锁!

    文章目录 声明 前言 Synchronized锁 Lock锁 Synchronized与Lock区别 生产者与消费者问题 Synchronized Lock 8锁现象 后记 声明 1)该文章部分内容整 ...

  8. 多线程和线程安全 同步代码 lock锁

     多线程:Thread Runnable 并发与并行 •并发:指两个或多个事件在同一个时间段内发生. •并行:指两个或多个事件在同一时刻发生(同时发生). 线程 是程序中的执行线程.Java 虚拟机允 ...

  9. 线程同步机制synchronized中锁的判断以及锁的作用范围

    当我们使用多个线程访问同一资源(可以是同一个变量.同一个文件.同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题. 要 ...

最新文章

  1. 报错解决:TypeError: Object type class 'str' cannot be passed to C code
  2. python 去除str的引号 去除括号_用python进行图像修复与去除水印
  3. Linux Unix C 中的curses库——curses.h
  4. KMP算法的nextval[] 即优化next[]
  5. C语言试题三十八之将s所指字符串中除了下标为偶数、同时ascii值也为偶数的字符外,其余的全都删除;串中剩余字符所形成的一个新串放在t所指的一个数组中。
  6. property属性[Python]
  7. mysql 创建定时任务_mysql创建定时任务
  8. 第一次写博,好激动啊!
  9. html5两条直线,Html5新特性用canvas标签画多条直线附效果截图
  10. Set A Light 3D Studio 2.0 for Mac(3D摄影棚布光软件)
  11. java取消科学计数法_Jmeter、Java当double显示的数字过长时取消科学计数法显示
  12. MySQL的回表查询与索引覆盖查询
  13. 基于ARMA模型的客流预测
  14. 结构建模设计——Solidworks软件之绘制一个手机支架模型,使用3D打印技术输出实物的全流程实战
  15. 三极管共射放大电路静态工作点怎么设计
  16. ESP8266 开发之旅 网络篇 无线更新 --OTA 固件更新
  17. Wp和Win8平台在实际开发中的对比
  18. Win7如何去除快捷方式小箭头
  19. 三千繁华,感情在日子里
  20. 知识图谱推理:现代的方法与应用

热门文章

  1. 蒸发水量(mm/s)和 潜热通量(W/m2) 之间的单位转化
  2. Python自动发邮件
  3. 经纬度坐标系转东北天_经纬度坐标系转换
  4. 赴美工作常识(Part 6 - 绿卡排队)
  5. 越是领军人才,越要看基本素质
  6. win10+yolov5尝鲜体验
  7. 【C语言】0x1F<<11等于0还是0xF800 ?
  8. 老九学堂之分布式设计教材
  9. Kali Linux入门教程(非常详细)从零基础入门到精通,看完这一篇就够了。
  10. 我用scratch做了个二分查找