精华内容
下载资源
问答
  • 同步锁分类对象(this)类(类的字节码文件对象即类名.class)字符串(比较特别)应用场景在多线程下对共享资源的安全操作。需求:启动5个线程对共享资源total进行安全操作。同步锁在多线程单例模式下的使用以上三类...

    同步锁分类

    对象锁(this)

    类锁(类的字节码文件对象即类名.class)

    字符串锁(比较特别)

    应用场景

    在多线程下对共享资源的安全操作。

    需求:启动5个线程对共享资源total进行安全操作。

    同步锁在多线程单例模式下的使用

    以上三类同步锁都可以。

    package cn.myThread;

    public class MyThread implements Runnable {

    private static int total = 10;

    @Override

    public void run() {

    synchronized (this){ //使用this对象锁

    //synchronized (MyThread.class){ //使用MyThread.class类锁

    //synchronized (""){//使用字符串锁

    System.out.println(Thread.currentThread().getName() + "正在运行");

    try {

    Thread.sleep(100);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    total--;

    System.out.println(total);

    System.out.println(Thread.currentThread().getName() + "线程结束");

    }

    }

    }

    package cn.test;

    import cn.myThread.MyThread;

    public class TestMyThread {

    public static void main(String[] args){

    MyThread myThread = new MyThread();

    Thread thread = null;

    for (int i = 1 ; i <= 5 ; i++){

    thread = new Thread(myThread,"线程"+i); //开启5个线程,传入同一个对象

    thread.start();

    }

    }

    }

    线程1正在运行

    9

    线程1线程结束

    线程3正在运行

    8

    线程3线程结束

    线程5正在运行

    7

    线程5线程结束

    线程2正在运行

    6

    线程2线程结束

    线程4正在运行

    5

    线程4线程结束

    分析:从运行结果可以看出5个线程串行执行同步锁里面的代码,因为5个线程中的同步锁对象this指向同一个的对象(同步锁对象MyThread.class类锁是同一个对象、同步锁对象 ”” 字符串锁是同一个对象),所以5个线程会串行执行同步锁里面的代码。

    同步锁在多线程多例模式下的使用

    错误用法

    package cn.myThread;

    public class MyThread implements Runnable {

    private static int total = 10;

    @Override

    public void run() {

    synchronized (this){//使用this对象锁

    System.out.println(Thread.currentThread().getName() + "正在运行");

    try {

    Thread.sleep(100);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    total--;

    System.out.println(total);

    System.out.println(Thread.currentThread().getName() + "线程结束");

    }

    }

    }

    package cn.test;

    import cn.myThread.MyThread;

    public class TestMyThread {

    public static void main(String[] args){

    Thread thread = null;

    for (int i = 1 ; i <= 5 ; i++){

    thread = new Thread(new MyThread(),"线程"+i);//开启5个线程,传入5个不同对象

    thread.start();

    }

    }

    }

    线程2正在运行

    线程1正在运行

    线程3正在运行

    线程5正在运行

    线程4正在运行

    9

    7

    9

    8

    线程1线程结束

    线程5线程结束

    线程2线程结束

    线程3线程结束

    6

    线程4线程结束

    分析:从运行结果可以看出5个线程并行执行同步锁里面的代码,因为5个线程中的同步锁对象this指向5个不同的对象,所以5个线程会同时执行同步锁里面的代码。

    正确用法

    方式一:

    package cn.myThread;

    public class MyThread implements Runnable {

    private static int total = 10;

    @Override

    public void run() {

    synchronized (MyThread.class){//使用MyThread.class类锁

    System.out.println(Thread.currentThread().getName() + "正在运行");

    try {

    Thread.sleep(100);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    total--;

    System.out.println(total);

    System.out.println(Thread.currentThread().getName() + "线程结束");

    }

    }

    }

    package cn.test;

    import cn.myThread.MyThread;

    public class TestMyThread {

    public static void main(String[] args){

    Thread thread = null;

    for (int i = 1 ; i <= 5 ; i++){

    thread = new Thread(new MyThread(),"线程"+i); //开启5个线程,传入5个不同对象

    thread.start();

    }

    }

    }

    线程1正在运行

    9

    线程1线程结束

    线程5正在运行

    8

    线程5线程结束

    线程4正在运行

    7

    线程4线程结束

    线程3正在运行

    6

    线程3线程结束

    线程2正在运行

    5

    线程2线程结束

    分析:从运行结果可以看出5个线程串行执行同步锁里面的代码,因为5个线程中的同步锁对象MyThread.class类锁是同一个对象,所以5个线程会串行执行同步锁里面的代码。

    方式二:

    package cn.myThread;

    public class MyThread implements Runnable {

    private static int total = 10;

    @Override

    public void run() {

    synchronized (""){//使用字符串锁

    System.out.println(Thread.currentThread().getName() + "正在运行");

    try {

    Thread.sleep(100);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    total--;

    System.out.println(total);

    System.out.println(Thread.currentThread().getName() + "线程结束");

    }

    }

    }

    package cn.test;

    import cn.myThread.MyThread;

    public class TestMyThread {

    public static void main(String[] args){

    Thread thread = null;

    for (int i = 1 ; i <= 5 ; i++){

    thread = new Thread(new MyThread(),"线程"+i); //开启5个线程,传入5个不同对象

    thread.start();

    }

    }

    }

    线程1正在运行

    9

    线程1线程结束

    线程4正在运行

    8

    线程4线程结束

    线程5正在运行

    7

    线程5线程结束

    线程3正在运行

    6

    线程3线程结束

    线程2正在运行

    5

    线程2线程结束

    分析:从运行结果可以看出5个线程串行执行同步锁里面的代码,因为5个线程中的同步锁对象 ”” 字符串锁是同一个对象,所以5个线程会串行执行同步锁里面的代码。

    以上这篇java同步锁的正确使用方法(必看篇)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

    展开全文
  • 1、synchronized简介synchronized实现同步的基础:java中每...2、synchronized的使用场景方法同步public synchronized void method1(){}复制代码锁住的是该对象的一个实例,当不同线程调用该实例对象中该同步方法,...

    1、synchronized简介

    synchronized实现同步的基础:java中每个对象都可以作为锁。当线程试图访问同步代码时,必须先获得对象锁,退出或抛出异常时必须释放锁。

    表现形式为:代码块同步和方法同步。

    2、synchronized的使用场景

    方法同步

    public synchronized void method1(){}复制代码

    锁住的是该对象的一个实例,当不同线程调用该实例对象中该同步方法,线程只有一个得到锁,其余被阻塞。但如果不同线程同时对该类的不同实例对象执行该同步方法,则不会阻塞,因为他们使用不同的锁。

    代码块同步

    synchronized(this)( //ToDo}

    synchronized(普通变量){ }复制代码

    同上

    静态方法同步

    public synchronized static void method3(){}复制代码

    锁住的是该类,当不同线程调用该类的该static同步方法时,就只能有一个线程获得锁,其余线程被阻塞。

    静态代码块

    synchronized(Test.class){ //ToDo}

    synchronized(静态变量){ //ToDo}复制代码

    同上

    3、synchronized锁升级

    锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁和重量级锁

    锁可以升级但不能降级,意味着偏向锁升级为轻量级锁后不能降级为偏向锁。

    偏向锁

    大所述情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得所的代价更低而引入偏向锁。

    当一个线程访问同步代码并获取锁时,会对对象头和栈帧中的锁记录里保存锁偏向的线程ID,以后该线程再进入和退出同步锁时,不需要进行CAS操作来加锁和解锁,而只是简单地测试一下对象头的Mark Word里是否存储执行当前线程的偏向锁。

    偏向锁作用是:在没有别的线程竞争的时候,一直偏向当前线程,当前线程可以一直执行。

    轻量级锁(自旋锁)

    轻量级锁,由偏向锁升级而来。

    偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。

    重量级锁

    轻量级锁膨胀之后,就升级为重量级锁。

    重量级锁时依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被称为互斥锁(synchronized就是重量级锁)。

    偏向锁

    优点:加锁和解锁不需要额外的消耗

    缺点:线程存在竞争,会带来额外的锁撤销的消耗

    场景:单一线程访问同步块场景

    轻量级锁

    优点:竞争的线程不会阻塞,提高了程序的响应速度。

    缺点:线程自旋时不释放CPU

    场景:追求响应时间,同步块执行速度非常快。

    重量级锁

    优点:线程竞争不使用自旋,释放CPU

    缺点:线程阻塞,响应时间缓慢。

    场景:追求吞吐量,同步块执行速度较长。

    二、Lock接口

    1、Lock接口

    Lock,锁对象。在Lock接口出现之前,Java程序时靠synchronized关键字实现锁功能。而在Java SE5.0之后并发包中新增了Lock接口来实现锁的功能。

    它能提供synchronized关键字类似的同步功能,但需要显示获得锁和释放锁,至于二者区别后文补充。

    Lock接口的主要方法:

    void lock()

    执行该方法时,若锁处于空闲状态,当前线程将获得锁。相反,如果锁已经被其他线程持有,则禁止当前线程获得锁。

    boolean tryLock()

    若锁可用,则获得锁,并立即返回true,否则返回false。

    tryLock()和Lock()的区别在于:

    tryLock()只是尝试获得锁,若锁不可用,不会导致当前线程被禁止,当前线程仍然继续往下执行代码。

    Lock()则是一定要获得锁,如果锁不可用,就一直等待,在未获得锁之前,当前线程并不继续向下自行。

    void unlock()

    执行该方法时,当前线程将释放持有锁,锁只能由持有者释放,若线程没有持有锁,则执行该方法,可能导致异常方式。

    Condition newCondition()

    条件对象,获得等待通知组件。该组件和当前的锁绑定,当前线程只有获得锁,才会调用该组件的await()方法,调用后,当前线程将释放锁。

    2、Reentrantlock的使用

    Reentrantlock的使用很简单,只需要显示调用,获得同步锁,释放同步锁即可。

    ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁

    .....................

    try {

    lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果

    //操作

    }catch(Exception e){

    //异常处理

    } finally {

    lock.unlock(); //释放锁

    }复制代码

    reentrantlock锁,在高并发的条件下使用的性能远远高于synchronized关键字。

    并且reentratnlock公平和非公平锁的队列都是基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。

    3、ReadWriteLock接口

    ReadWriteLock接口中主要方法如下:

    public interface ReadWriteLock {

    /**

    * Returns the lock used for reading.

    *

    * @return the lock used for reading

    */

    Lock readLock();

    /**

    * Returns the lock used for writing.

    *

    * @return the lock used for writing

    */

    Lock writeLock();

    }复制代码

    ReadWriteLock管理一组锁,一个是只读锁,一个是写锁。

    Java并发库中ReentrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。

    读锁在同一时刻允许有多个线程在访问,但是写进程访问时,所有的读线程和其他写进程都被阻塞。

    读写锁维护一对锁,一个读锁和一个写锁,通过读写锁分离,使得并发性相比一般的排它锁有很大的提升。

    ReentrantReadWriteLock读写锁的几个特性:

    公平选择性

    重入性

    锁降级

    public class Cache{

    static Map map = new HashMap();

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    static Lock rLock = rwl.readLock();

    static Lock wLock = rwl.writeLock();

    //获取一个key对应的value

    public static final Object get(String key){

    r.lock();

    try{

    return map.get(key);

    }finally{

    r.unlock();

    }

    }

    //设置key对应的value并返回旧的value

    public static fianl Object put(String key,Object value){

    w.lock();

    try{

    return map.put(key,value);

    }final{

    w.unlock();

    }

    }

    //清空缓存

    public static fianl void clear(){

    w.lock();

    try{

    map.clear();

    } finally{

    w.unlock();

    }

    }

    }复制代码

    读写锁的锁降级

    锁降级是指写锁降级成为读锁。如果当前线程持有写锁,然后将其释放再获取读锁的过程不能称为锁降级。锁降级指的在持有写锁的时候再获取读锁,获取到读锁后释放之前写锁的过程称为锁释放。

    public void work() {

    reentrantReadWriteLock.readLock().lock();

    if (!update) {

    reentrantReadWriteLock.readLock().unlock();

    // 锁降级开始

    reentrantReadWriteLock.writeLock().lock();

    try {

    if (!update) {

    // 准备数据

    ++index;

    try {

    TimeUnit.MILLISECONDS.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    update = true;

    }

    reentrantReadWriteLock.readLock().lock();

    } finally {

    reentrantReadWriteLock.writeLock().unlock();

    // 锁降级结束,降级为读锁

    }

    }

    try {

    // 使用数据

    for (int i=0; i<5; i++) {

    try {

    TimeUnit.MILLISECONDS.sleep(100);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(Thread.currentThread().getName() + ":" + index);

    }

    } finally {

    reentrantReadWriteLock.readLock().unlock();

    }

    }

    复制代码

    三、synchronized和Lock区别

    1、synchronized和Lock区别

    synchronized是关键字,而Lock是接口

    synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁

    synchronized自动释放锁(a线程执行完同步代码会自动释放锁,b线程执行过程中发生异常会释放锁)

    lock需在finally中手动释放锁(unlock()方法释放锁),否则容易造成线程死锁。

    synchronized关键字的两个线程1和线程2,若当前线程1获得锁,线程2等待,如果线程1阻塞,线程2会一直等待下去。

    而lock锁不一定会等待下去,如果尝试获得不到锁,线程可以不用一直等待就结束了。

    synchronized的锁可重入、不可中断、非公平。

    lock锁可重入,可中断、可公平

    lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题

    不可重入锁:自旋锁、wait()、notify()、notifyAll()

    不可重入锁,即不可递归调用,递归调用会发生死锁

    2、reentrantlock和synchronized区别

    reentrantLock拥有synchronized相同的并发性和内存语义,此外还多列锁投票、定时锁等候和中断所等候

    使用synchronized锁,A不释放,B将一直等待下去

    使用reentrantlock锁,A不释放,B等待一段时间就会中断等待,而干别的事情。

    synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且代码执行时出现异常,JVM会走到哪个释放锁定。但是Lock不行

    在资源竞争不是很激烈的情况下,synchronized的性能优于reentrantlock锁,而竞争激烈的情况下,synchronized的性能下降几十倍,而reentrantlock的性能维持常态。

    性能分析

    synchronized会多次自旋,以获得锁,在这个过程中等待的线程不会被挂起,因而节省了挂起和唤醒的上下文切换的开销

    而reentrantlock,不会自旋,而是直接挂起

    因而在线程并发量不大的情况下,synchronized因为拥有自旋锁、偏向锁和轻量级锁的原因,不用将等待线程挂起,偏向锁甚至不用自旋,所以在这种情况下要比reenttrantlock高效。

    默认情况下synchronized非公平锁;reentrantlock默认是非公平锁。

    绑定多个条件

    一个Reentrantlock对象可以同时绑定多个Condition对象,

    而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件。

    如果要和多余一个添加关联的时候,synchronized就不得不额外地添加一个锁,而Reentrantlock则无须这么做只需要多次调用new Condition()方法即可。

    展开全文
  • Java 同步锁

    2021-02-12 13:04:10
    一、synchronized1.类型(1)对象对象是作用在实例方法...synchronized(account){....}(2)类而类是作用在静态方法或者Class对象上面的每个类只有一个Class对象,所以类只有一个类只是一个概念上的东西,...

    一、synchronized

    1.类型

    (1)对象锁

    对象锁是作用在实例方法或者一个对象实例上面的

    一个类可以有多个实例对象,因此一个类的对象锁可能会有多个

    例子:

    Account account = new Account();

    synchronized(account){

    ....

    }

    (2)类锁

    而类锁是作用在静态方法或者Class对象上面的

    每个类只有一个Class对象,所以类锁只有一个

    类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定的是实例方法还是静态方法区别的

    例子:

    synchronized(Account.class){

    ....

    }

    2.synchronizedd的使用:

    (1)锁对象的引用,以及这个锁保护的代码块。

    例子:

    private Object obj = new Objcet();

    publci void test(){

    synchronized(obj){

    ....

    }

    }

    (2)如果作用在实例方法上面,锁就是该方法所在的当前对象,静态synchronized方法会从Class对象上获得锁。

    例子:

    public synchronized void test(){

    ....

    }

    (3)究竟是synchronized方法好还是synchronized代码块好呢?

    有一个原则就是锁的范围越小越好,加锁的目的就是将锁进去的代码作为原子性操作,因为非原子操作都不是线程安全的,因此synchronized代码块应该是在开发过程中优先考虑使用的加锁方式。

    3.synchronizedd特性

    (1)synchronizedd是一种悲观锁,也称为独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

    (2)synchronized不具有继承性。

    如果父类 、子类中存在同样的方法。父类方法是同步的,子类是不同步的。那么子类的方法就不会无法继承父类的方法性质。必须自己加上synchronized关键字!

    例子:

    class A{

    public synchronized void test(){

    System.out.println("父类");

    }

    }

    class B extends A{

    public void test(){

    System.out.println("子类无法继承父类的锁");

    }

    }

    (3)重入性

    关键字synchronized具有锁重入功能,当一个线程已经持有一个对象锁后,再次请求该对象锁时是可以得到该对象的锁的,这种方式是必须的,否则在一个synchronized方法内部就没有办法调用该对象的另外一个synchronized方法了。锁重入是通过为每个所关联一个计数器和一个占有它的线程,当计数器为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM会记录锁的占有者,并将计数器设置为1。如果同一个线程再次请求该锁,计数器会递增,每次占有的线程退出同步代码块时计数器会递减,直至减为0时锁才会被释放。

    4.实现原理

    (1)Java对象头

    synchronized用的锁是存在Java对象头里的,那么什么是Java对象头呢?Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。

    Mark Word(标记字段)

    Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

    Klass Pointer(类型指针)

    Klass Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,

    (2)Monitor Record

    Monitor Record 是线程私有的数据结构,每一个线程都有一个可用monitor record(监视记录)列表,同时还有一个全局的可用列表。每一个被锁住的对象都和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识。

    Monitor Record 其结构如下:

    Owner: 初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;

    EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。

    RcThis:表示blocked或waiting在该monitor record上的所有线程的个数

    Nest:用来实现重入锁的计数。

    HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)

    Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

    同步代码块

    monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁。

    同步方法:

    synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

    二、ReentrantLock

    1.轮询锁的和定时锁

    可轮询和可定时的锁请求是通过tryLock()方法实现的,和无条件获取锁不一样. ReentrantLock可以有灵活的容错机制.死锁的很多情况是由于顺序锁引起的, 不同线程在试图获得锁的时候阻塞,并且不释放自己已经持有的锁, 最后造成死锁. tryLock()方法在试图获得锁的时候,如果该锁已经被其它线程持有,则按照设置方式立刻返回,而不是一直阻塞等下去,同时在返回后释放自己持有的锁.可以根据返回的结果进行重试或者取消,进而避免死锁的发生。

    2.公平性

    ReentrantLock构造函数中提供公平性锁和非公平锁(默认)两种选择。所谓公平锁,线程将按照他们发出请求的顺序来获取锁,不允许插队;但在非公平锁上,则允许插队:当一个线程发生获取锁的请求的时刻,如果这个锁是可用的,那这个线程将跳过所在队列里等待线程并获得锁。我们一般希望所有锁是非公平的。因为当执行加锁操作时,公平性将讲由于线程挂起和恢复线程时开销而极大的降低性能。考虑这么一种情况:A线程持有锁,B线程请求这个锁,因此B线程被挂起;A线程释放这个锁时,B线程将被唤醒,因此再次尝试获取锁;与此同时,C线程也请求获取这个锁,那么C线程很可能在B线程被完全唤醒之前获得、使用以及释放这个锁。这是种双赢的局面,B获取锁的时刻(B被唤醒后才能获取锁)并没有推迟,C更早地获取了锁,并且吞吐量也获得了提高。在大多数情况下,非公平锁的性能要高于公平锁的性能。

    3.可中断获锁获取操作

    lockInterruptibly方法能够在获取锁的同时保持对中断的响应,因此无需创建其它类型的不可中断阻塞操作。

    4.Condition

    Lock提供的线程之间交互类:

    await:阻塞线程

    signal:释放阻塞的线程

    signalAll:释放所有阻塞的线程

    5.ReadWriteLock

    (1)ReentrantLock是一种标准的互斥锁,每次最多只有一个线程能持有锁。读写锁不一样,暴露了两个Lock对象,其中一个用于读操作,而另外一个用于写操作。

    (2)ReentrantReadWriteLock

    ReentrantReadWriteLock实现了ReadWriteLock接口,构造器提供了公平锁和非公平锁两种创建方式。读写锁适用于读多写少的情况,可以实现更好的并发性,性能高于重入锁。

    ReadLock:读锁

    WriteLock:写锁

    6.ReentrantLock特性

    (1)ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题.ReentrantLock实现了。

    (2)ReentrantLock是一种乐观锁。

    7.实现原理

    (1)ReentrantLock的方法都依赖于AbstractQueuedSynchronizer的实现 (AQS)

    (2)ReentrantLock的锁资源以state状态描述,利用CAS则实现对锁资源的抢占,并通过一个CLH队列阻塞所有竞争线程,在后续则逐个唤醒等待中的竞争线程。ReentrantLock继承AQS完全从代码层面实现了java的同步机制,相对于synchronized,更容易实现对各类锁的扩展。同时,AbstractQueuedSynchronizer中的Condition配合ReentrantLock使用,实现了wait/notify的功能

    部分信息来自网络

    展开全文
  • Atomic原子操作在 Java 5.0 提供了java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子...

    Atomic原子操作

    在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类

    Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

    在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。

    原子更新基本类型类: AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

    原子更新数组类:AtomicIntegerArray,AtomicLongArray

    原子更新引用类型:AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

    原子更新字段类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

    Atomic的原理

    下面通过AtomicInteger的源码来看一下是怎么在没有锁的情况下保证数据正确性。首先看一下incrementAndGet()方法的实现:

    1

    2

    3

    4

    5

    6

    7

    /**

    * Atomically increments by one the current value.

    * @return the updated value

    */

    public final int incrementAndGet() {

    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

    }

    我们继续看,unsafe.getAndAddInt() 的实现是什么样的。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    /**

    * Atomically adds the given value to the current value of a field

    * or array element within the given object o

    * at the given offset.

    *

    * @param o object/array to update the field/element in

    * @param offset field/element offset

    * @param delta the value to add

    * @return the previous value

    * @since 1.8

    */

    public final int getAndAddInt(Object o, long offset, int delta) {

    int v;

    do {

    v = getIntVolatile(o, offset);

    } while (!compareAndSwapInt(o, offset, v, v + delta));

    return v;

    }

    public final native boolean compareAndSwapInt(Object o, long offset,

    int expected,

    int x);

    这是一个循环,offset是变量v在内存中相对于对象o起始位置的偏移,传给JNI层用来计算这个value的内存绝对地址。

    然后找到JNI的实现代码,来看 native层的compareAndSwapInt()方法的实现。这个方法的实现是这样的:

    1

    2

    3

    4

    5

    6

    UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))

    UnsafeWrapper("Unsafe_CompareAndSwapInt");

    oop p = JNIHandles::resolve(obj);

    jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); //计算变量的内存绝对地址

    return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

    UNSAFE_END

    这个函数其实很简单,就是去看一下obj 的 offset 上的那个位置上的值是多少,如果是 e,那就把它更新为 x,返回true,如果不是 e,那就什么也不做,并且返回false。里面的核心方法是Atomic::compxchg(),这个方法所属的类文件是在os_cpu目录下面,由此可以看出这个类是和CPU操作有关,进入代码如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {

    // alternative for InterlockedCompareExchange

    int mp = os::is_MP();

    __asm {

    mov edx, dest

    mov ecx, exchange_value

    mov eax, compare_value

    LOCK_IF_MP(mp)

    cmpxchg dword ptr [edx], ecx

    }

    }

    这个方法里面都是汇编指令,看到LOCK_IF_MP也有锁指令实现的原子操作,其实CAS也算是有锁操作,只不过是由CPU来触发,比synchronized性能好的多。

    什么是CAS

    ​ CAS,Compare and Swap即比较并交换。 java.util.concurrent包借助CAS实现了区别于synchronized同步锁的一种乐观锁。乐观锁就是每次去取数据的时候都乐观的认为数据不会被修改,所以不会上锁,但是在更新的时候会判断一下在此期间数据有没有更新。CAS有3个操作数:内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。CAS的关键点在于,系统在硬件层面保证了比较并交换操作的原子性,处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。

    CAS的优缺点

    CAS由于是在硬件层面保证的原子性,不会锁住当前线程,它的效率是很高的。

    CAS虽然很高效的实现了原子操作,但是它依然存在三个问题。

    1、ABA问题。CAS在操作值的时候检查值是否已经变化,没有变化的情况下才会进行更新。但是如果一个值原来是A,变成B,又变成A,那么CAS进行检查时会认为这个值没有变化,但是实际上却变化了。ABA问题的解决方法是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就变成1A-2B-3A。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。

    2、并发越高,失败的次数会越多,CAS如果长时间不成功,会极大的增加CPU的开销。因此CAS不适合竞争十分频繁的场景。

    3、只能保证一个共享变量的原子操作。当对多个共享变量操作时,CAS就无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

    实现自旋锁

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    /**

    * 使用AtomicInteger实现自旋锁

    */

    public class SpinLock {

    private AtomicInteger state = new AtomicInteger(0);

    /**

    * 自旋等待直到获得许可

    */

    public void lock(){

    for (;;){

    //CAS指令要锁总线,效率很差。所以我们通过一个if判断避免了多次使用CAS指令。

    if (state.get() == 1) {

    continue;

    } else if(state.compareAndSet(0, 1)){

    return;

    }

    }

    }

    public void unlock(){

    state.set(0);

    }

    }

    原理很简单,就是一直CAS抢锁,如果抢不到,就一直死循环,直到抢到了才退出这个循环。

    自旋锁实现起来非常简单,如果关键区的执行时间很短,往往自旋等待会是一种比较高效的做法,它可以避免线程的频繁切换和调度。但如果关键区的执行时间很长,那这种做法就会大量地浪费CPU资源。

    针对关键区执行时间长的情况,该怎么办呢?

    实现可等待的锁

    如果关键区的执行时间很长,自旋的锁会大量地浪费CPU资源,我们可以这样改进:当一个线程拿不到锁的时候,就让这个线程先休眠等待。这样,CPU就不会白白地空转了。大致步骤如下:

    需要一个容器,如果线程抢不到锁,就把线程挂起来,并记录到这个容器里。

    当一个线程放弃了锁,得从容器里找出一个挂起的线程,把它恢复了。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    /**

    * 使用AtomicInteger实现可等待锁

    */

    public class BlockLock implements Lock {

    private AtomicInteger state = new AtomicInteger(0);

    private ConcurrentLinkedQueue waiters = new ConcurrentLinkedQueue<>();

    @Override

    public void lock() {

    if (state.compareAndSet(0, 1)) {

    return;

    }

    //放到等待队列

    waiters.add(Thread.currentThread());

    for (;;) {

    if (state.get() == 0) {

    if (state.compareAndSet(0, 1)) {

    waiters.remove(Thread.currentThread());

    return;

    }

    } else {

    LockSupport.park(); //挂起线程

    }

    }

    }

    @Override

    public void unlock() {

    state.set(0);

    //唤醒等待队列的第一个线程

    Thread waiterHead = waiters.peek();

    if(waiterHead != null){

    LockSupport.unpark(waiterHead); //唤醒线程

    }

    }

    }

    我们引入了一个 waitList,用于存储抢不到锁的线程,让它挂起。这里我们先借用一下JDK里的ConcurrentLinkedQueue,因为这个Queue也是使用CAS操作实现的无锁队列,所以并不会引入JDK里的其他锁机制。如果大家去看AbstractQueuedSynchronizer的实现,就会发现,它的acquire()方法的逻辑与上面的实现是一样的。

    不过上面的代码是不是没问题了呢?如果一个线程在还未调用park挂起之前,是不是有可能被其他线程先调用一遍unpark?这就是唤醒发生在休眠之前。发生这样的情况会不会带来问题呢?

    展开全文
  • Java锁学习笔记

    2021-04-18 09:15:05
    Java中每一个对象都可以作为,普通同步方法,是当前对象的实例;静态同步方法,是当前类的class对象;静态代码块,是括号里面的对象。同步代码块:同步代码块是使用monitorenter和monitorexit指令实现的;...
  • 引题比如在同一个节点上,两个线程并发的操作A...其他的线程只有当该线程修改完成后并且释放,才能对其访问,这种加锁--修改--释放的模式就解决了多个线程同时修改资源而造成的错误。但是,在分布式集群系统中,...
  • 深入理解 Java 机制

    2021-03-13 01:53:05
    Java 提供了两种机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。一、synchronized说起 Java 中的,第一反应就是 synchronized,我们可以...
  • JAVA SE】第十六章 进程、线程、同步锁和线程的简介
  • 不知道什么是对象,的到底是个什么玩意,最近看了下面这位大佬的解释,感觉很到位一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在Java里 边就是拿到某个同步对象的(一个对象只有...
  • ## 前言 ´・ᴗ・` - 继上一次我们学习了线程礼让yield 线程强制执行join 守护线程 线程优先级相关的知识 ... - 并发 同步 阻塞 synchronized概念浅析 - synchronized四种实现形式的理解以及demo
  • 欢迎点赞阅读,一同学习交流,有疑问请留言 。GitHub上也有开源 JavaHouse,欢迎star引用当开发过程中,我们遇到并发问题。...在Java中,我们的就是synchronized关键字和Lock接口。synchronized关键字sy...
  • 转自:http://blog.csdn.net/yangzhijun_cau/article/details/6432216一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的(一个对象只有一把);...
  • Java 各种的小结

    2021-03-08 21:31:32
    cool-girl.jpg一. synchronized在 JDK 1.6 之前,synchronized 是重量级,效率低下。...synchronized 同步锁一共包含四种状态:无锁、偏向、轻量级、重量级,它会随着竞争情况逐渐升级。synchr...
  • 换句话说,你可以被告知没有人有锁,但是当你试图获取它,你阻止,因为另一个线程拿出了检查和你试图获取之间的。Brian是正确的指向Lock,但我想你真正想要的是它的tryLock方法:Lock lock = new ReentrantLock();...
  • 更新:在一次和一位专家的交谈中,他对一下代码能否能够成功同步,给予了否定的答案, 他的理由是”以构造函数的成员变量作为synchronized的,在多线程的情况下,每一个线程都持有自己私有变量的,这个的地址...
  • Java 提供了两种机制来控制多个线程对共享资源的互斥访问(原子操作),第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。 原子性 原⼦(atomic)本意是“不能被进⼀步分割的最⼩粒⼦”...
  • Java

    2021-03-09 23:06:57
    什么是线程安全?当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的...我们应用最多的就是互斥;jav...
  • 2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块 2. 同步方法 我们知道,...
  • java锁的种类与介绍

    2021-02-01 10:44:05
    顾名思义,锁就是将一种资源锁住,防止其他线程同步修改。锁机制是保证获取数据有序性的重要手段,为此java提供了大量的锁。无论是在面试或学习中,会遇到各种锁,如对象锁与类锁、轻量级与重量级锁、悲观锁与乐观锁...
  • java机制

    2021-03-04 00:22:41
    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的(一个对象只有一把);如果这个时候同步对象的被其他线程拿走了,他(这个线程)就只能等了(线程...
  • 一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的(一个对象只有一把);如果这个时候同步对象的被其他线程拿走了,他(这个线程)就只能等了(线程...
  • 假设只有二个线程,当第一次进入synchronized锁住对象,那么另一个线程发生自旋(锁) 重量锁:就是一般情况 对于锁而言只有升级,没有降级 ============================================= 线程的一些方法 yield与...
  • 文章目录同步锁前言同步与异步synchronized同步关键字写法前提特点练习-改造售票案例之前遇到过的同步例子线程创建的其他方式ExecutorService/Executors练习:线程的其他创建方式拓展:线程悲观和乐观两种常见的...
  • java锁的机制

    2021-03-17 23:48:47
    java提供了内置,即synchronized,除此以外,还提供了显式,下面我们分别分析其实现的机制,并讨论如何在这两者之间进行选择。AQSAQS即AbstractQueuedSynchronizer,一般用于管理同步类中的状态,它管理了一个...
  • 悲观锁:1、当一个线程访问这个数据的时候,悲观锁会把这个数据给锁住,不被其他线程所访问,直到这个线程完成了对数据的提交后,其他线程才能够访问或者操作这个数据。2、悲观锁具有强烈的独占和排他特性。悲观锁...
  • JAVA锁-各种锁

    2021-07-19 22:50:58
    如果线程对同步资源加上共享后,其他线程只能对同步资源再加共享,不能加独享 。获得共享的线程对同步资源只能做读取,不能修改。 1.4 自旋与适应性自旋 ​ 1.4.1 自旋 ​ 线程执行是靠等待CPU分配...
  • 二、同步机制 Java 对于多线程的安全问题提供了专业的解决方式:同步机制 针对上面的售票案例简单描述一下同步机制: 当窗口1线程进入操作的时候,窗口2和窗口3线程只能在外面等着,窗口1操作结束,窗口1、窗口2和...
  • 关于的总结UML图总结中间偏下方的 d 是 ...同时拥有 同步队列 与 等待队列阻塞blocked:获取(对象或者类)失败,进入阻塞队列,阻塞线程当前状态被保存下来,cpu切换到其他线程任务。阻塞的任务是...
  • JAVA 常见面试题

    2021-01-07 16:57:05
    每个 Java 对象都有一个关联的 monitor,使用 synchronized 时 JVM 会根据使用环境找到对象的 monitor,根据 monitor 的状态进行加解锁的判断。如果成功加锁就成为该 monitor 的唯一持有者,monitor 在被释放前不能...
  • Lock 是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题。代码如下:public class LockTest {public static ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,165
精华内容 13,666
关键字:

java锁住同步资源

java 订阅
友情链接: java-OA.rar