线程安全(三)实现方法sychronized与ReentrantLock(阻塞同步)

系列文章目录

线程安全(一)java对象头分析以及锁状态
线程安全(二)java中的CAS机制
线程安全(三)实现方法sychronized与ReentrantLock(阻塞同步)
线程安全(四)Java内存模型与volatile关键字
线程安全(五)线程状态和线程创建
线程安全(六)线程池
线程安全(七)ThreadLocal和java的四种引用
线程安全(八)Semaphore
线程安全(九)CyclicBarrier
线程安全(十)AQS(AbstractQueuedSynchronizer)

0.前言

synchronized与lock的区别,以及ReentrantLock和ReentrantReadWriteLock的实现

1.sychronized

1.1.简介

根据《深入理解Java虚拟机(第二版)》上讲的,线程安全最基本的互斥同步手段就是synchronized关键字。

  1. synchronized进过编译会在同步块前和后形成monitorenter,monitorexitr两个字节码指令,这两个字节码都需要reference类型的参数来指明锁定和解锁的对象。
  2. 如果明确指出要锁定和锁所对象,那就是这个对象的reference,没有明确指定,就根据修饰的是实例方法还是类方法去取对应的对象实例或class对象来作为锁对象。
  3. 在执行monitorenter指令时,首先要尝试获取对象的锁。如果没锁定,或当前线程已拥有对象的锁,就把锁的计数器加1,在执行monitorexit指令时,会减一,当计数器为0时,锁就被释放。如果获取失败,那当前线程就阻塞等待,直到对象锁被另一个线程释放。
  4. synchronized同步块对同一条线程是可重入的,不会出现自己锁死的问题。
  5. 用户态到核心态,状态切换可能需要更长的时间。

1.2.底层原理

当对象的锁级别升级为“重量级锁”时,JVM就开始采用Object Monitor机制控制各线程抢占对象的过程了。
内置锁(ObjectMonitor)
通常所说的对象的内置锁,是对象头Mark Word中的重量级锁指针指向的monitor对象。

    //结构体如下
    ObjectMonitor::ObjectMonitor() {  
      _header       = NULL;  
      _count       = 0;  
      _waiters      = 0,  
      _recursions   = 0;       //线程的重入次数
      _object       = NULL;  
      _owner        = NULL;    //标识拥有该monitor的线程
      _WaitSet      = NULL;    //等待线程组成的双向循环链表,_WaitSet是第一个节点
      _WaitSetLock  = 0 ;  
      _Responsible  = NULL ;  
      _succ         = NULL ;  
      _cxq          = NULL ;    //多线程竞争锁进入时的单向链表
      FreeNext      = NULL ;  
      _EntryList    = NULL ;    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
      _SpinFreq     = 0 ;  
      _SpinClock    = 0 ;  
      OwnerIsThread = 0 ;  
    }  

线程大体会处于三个状态

  • Entry Set:没有操作权限,会待进入监控区部分(Entry Set),停留在这个区域的线程由于还没有获得对象操作权限的原因,停留在synchronized同步块以外,其线程状态被标识为BLOCKED。
  • Owner:持有对象操作权(only one),当前持有对象操作权限的线程互斥量将被记录在这个对象的对象头中。
  • Wait Set(待授权区):某个线程通过wait等相关方法释放了对象的操作权限,会被放置到待授权区域(Wait Set只有通过notify()或者相似方法被通知转移的线程能够参与线程抢占。

1.3.synchronized锁升级

无锁 - 偏向锁 -轻量级锁(自旋锁)-重量级锁
具体见java对象头分析与锁升级

偏向锁 - markword 上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,所以,偏向锁,偏向加锁的第一个线程 。hashCode备份在线程栈上 线程销毁,锁降级为无锁

有竞争 - 锁升级为轻量级锁 - 每个线程有自己的LockRecord在自己的线程栈上,用CAS去争用markword的LR的指针,指针指向哪个线程的LR,哪个线程就拥有锁

自旋超过10次,升级为重量级锁 - 如果太多线程自旋 CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU)-XX:PreBlockSpin

1.4.sychronized同步方法与非同步方法

在这里插入图片描述

1.5.总结

1.JVM字节码 
monitorenter指令
...
monitorexit指令
2.两种锁
static同步方法是类锁,非static是对象锁
3.可重入原理(加锁次数计数器)
4.不可中断
5.方法抛出异常后,会释放锁
6.notify, 和notifyAll 都没有释放对象的锁,而是在Synchronizer同步块结束的时候释放

2.Lock

2.1.Lock方法介绍

在这里插入图片描述在这里插入图片描述

2.2.Lock与synchronized的比较

优点
(1)可以尝试非阻塞地获取锁。
(2)获取锁过程中可以被中断。
(3)超时获取锁,可以避免线程长时间阻塞。
需要注意要主动释放锁
模板如下:
public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }

原理:多个线程会放到node队列

3.ReentrantLock

3.1 简介

也是可重入锁,多了以下特点
1.等待可中断
2.可实现公平锁
3.锁可以绑定条件

3.1.非公平锁(默认)

  static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> getReentrantLock(), "线程"+ i).start();
        }
    }

    static void getReentrantLock() {
        try{
            System.out.println(Thread.currentThread().getName() + " 准备 获取锁");
            lock.lock();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "     获取锁");
        } catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName() + " 准备 释放锁");
            lock.unlock();
        }
    }

在这里插入图片描述

3.2.公平锁

static ReentrantLock lock = new ReentrantLock(true);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> getReentrantLock(), "线程"+ i).start();
        }
    }

    static void getReentrantLock() {
        try{
            System.out.println(Thread.currentThread().getName() + " 准备 获取锁");
            lock.lock();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "     获取锁");
        } catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName() + " 准备 释放锁");
            lock.unlock();
        }
    }

在这里插入图片描述

3.3.非公平锁与公平锁比较

构造方法多了一个参数static ReentrantLock lock = new ReentrantLock(true);

源码比较

    class FairSync
        final void lock() {
            acquire(1);
        }

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
         protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            // 查询是否有线程在排队等待获取锁,如果有线程在排队,则不去抢锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
class NonfairSync
	final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());//只是赋值当前线程
            else
                acquire(1);
        }
     public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

   final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) { 
            // 不查询,直接抢锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

3.4 Condition

模拟一个场景,当银行卡余额小于1000时,发送短信通知

public class RichMan {

    private long monuey;
    private Lock lock = new ReentrantLock();
    private Condition balanceConditon = lock.newCondition();

    public RichMan(long monuey) {
        this.monuey = monuey;
    }

    public void huafei(long price) {
        lock.lock();
        try {
            this.monuey = this.monuey - price;
            balanceConditon.signal();// 每次消费完唤醒等待线程
            System.out.println(Thread.currentThread().getName() + "-》花费:" + price + ", 余额:" + monuey);
        } finally {
            lock.unlock();
        }
    }

    public void waitBuy() {
        lock.lock();
        try {
            while (this.monuey > 1000L) {
                balanceConditon.await();
                System.out.println(Thread.currentThread().getName() + " is be notify");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        sendMsg();
    }

    public void sendMsg() {
        System.out.println(Thread.currentThread().getName() + "-》您的余额为不足1000元,请充值");
    }
}
    private static RichMan richMan = new RichMan(1010L);

    private static class CheckMoney extends Thread {
        @Override
        public void run() {
            richMan.waitBuy();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new CheckMoney();
        t.setName("checkMoney");
        t.start();
        Thread.sleep(2000);
        for (int i = 0; i < 40; i++) {
            richMan.huafei(3L);
            Thread.sleep(500L);
        }
    }

在这里插入图片描述

3.4.1Condition类和Object类锁方法区别

  • Condition类的await方法 和 Object类的wait方法等效。
  • Condition类的signal方法 和 Object类的notify方法等效。
  • Condition类的singnalAll方法 和 Object类的notifyAll方法等效。
  • ReetrantLock类可以唤醒指定条件的此线程,二object的唤醒是随机的。

3.5.ReentrantReadWriteLock

 private UseRwLock useRwLock;

    public static void main(String[] args) throws InterruptedException {

        UseRwLock useRwLock = new UseRwLock(new User(1));
        for (int i = 0; i < 5; i++) {
            new Thread(new ReadThread(useRwLock)).start();
        }
        for (int i = 0; i < 1; i++) {
            new Thread(new WriteThread(useRwLock)).start();
        }

    }

    private static class ReadThread extends Thread {
        private UseRwLock useRwLock;

        public ReadThread(UseRwLock useRwLock) {
            this.useRwLock = useRwLock;
        }

        @Override
        public void run() {
            useRwLock.getNumber();
            System.out.println("get");
        }
    }

    private static class WriteThread extends Thread {
        private UseRwLock useRwLock;

        public WriteThread(UseRwLock useRwLock) {
            this.useRwLock = useRwLock;
        }

        public WriteThread(int i) {
            this.useRwLock = useRwLock;
        }

        @Override
        public void run() {
            useRwLock.setNumber(1);
            System.out.println("set");
        }
    }

    public static class UseRwLock {
        private User user;
        private final ReadWriteLock lock = new ReentrantReadWriteLock();
        private final Lock getLock = lock.readLock();//读锁
        private final Lock setLock = lock.writeLock();//写锁

        public UseRwLock(User user) {
            this.user = user;
        }


        public int getNumber() {
            getLock.lock();
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                getLock.unlock();
            }
            return this.user.getNumber();
        }

        public void setNumber(int number) {
            setLock.lock();
            try {
                Thread.sleep(5);
                user.setNumber(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                setLock.unlock();
            }
        }
    }

    public static class User {
        private int number;

        public User(int number) {
            this.number = number;
        }

        public int getNumber() {
            return number;
        }

        public void setNumber(int number) {
            this.number = number;
        }
    }
总结
1.线程进入读锁的前提
没有其他线程的写锁
没有写请求或者有写请求,但调用线程与持有线程是同一个
2.线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁

可重入,写锁可降级为读锁
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: synchronized和reentrantlock都是Java中用于实现线同步的机制。 synchronized是Java中最基本的同步机制,它可以用于方法或代码块级别的同步。当一个线程进入synchronized代码块时,它会尝试获取锁,如果锁已经被其他线程持有,则该线程会被阻塞,直到锁被释放。synchronized机制是Java中最常用的同步机制之一,但它有一些限制,例如无法中断正在等待锁的线程,也无法尝试获取锁而不阻塞reentrantlock是Java中另一种同步机制,它提供了更多的灵活性和控制。与synchronized不同,reentrantlock可以中断正在等待锁的线程,也可以尝试获取锁而不阻塞。此外,reentrantlock还提供了一些高级功能,例如公平锁和可重入锁。但是,reentrantlock的使用比synchronized更复杂,需要手动获取和释放锁,并且需要注意避免死锁等问题。 总的来说,synchronized是Java中最基本的同步机制,适用于大多数情况。而reentrantlock则提供了更多的灵活性和控制,适用于一些特殊的场景。 ### 回答2: synchronized和ReentrantLock是Java中用于实现线同步的两种机制。 synchronized是Java中最基本的线同步机制,它使用了内置的监视器锁(也称为对象锁或互斥锁)来控制线程的访问。synchronized关键字可以用于方法或代码块,并且是隐式锁。当线程执行到synchronized关键字时,会自动获取锁,执行完后会自动释放锁。synchronized关键字确保了在同一时间只有一个线程可以访问被锁定的代码或方法,从而保证了线程的安全性。然而,synchronized关键字也有一些缺点,比如无法获得锁时会一直阻塞等待和释放锁。 ReentrantLock是Java中的另一种线同步机制,它是通过显示锁来实现的。ReentrantLock提供了更灵活的锁定操作,相比于synchronized,它具有更多的特性。ReentrantLock可以使用lock()和unlock()方法来手动加锁和解锁,这意味着我们可以更加精确地控制代码的同步性。ReentrantLock还支持可重入性,即线程可以多次获得同一个锁,提供了更高的灵活性。它还支持公平锁和非公平锁两种模式,可以按照先进先出的规则来选择获取锁的线程。此外,ReentrantLock还提供了一些高级功能,比如可中断锁、超时锁和条件变量等。 总的来说,synchronized是Java中最基本的线同步机制,使用简单但灵活性较低。而ReentrantLock则提供了更多的功能和灵活性,但使用相对复杂一些。在实际应用中,我们可以根据具体需求来选择使用哪种线同步机制。 ### 回答3: synchronized和ReentrantLock都是Java中用于实现线同步的工具。 synchronized是Java中最常见的同步关键字,它可以应用在方法或代码块上。当一个线程获取到对象的synchronized锁时,其他线程就无法同时访问这个对象。当该线程执行完代码块或者方法,会释放锁,其他线程才能获得锁并执行。synchronized是隐式获取和释放锁的过程,非常方便,但也存在一些限制,例如无法中断一个正在等待获取锁的线程。 ReentrantLock是Java中提供的一种显示锁,可以实现与synchronized类似的功能。与synchronized不同的是,ReentrantLock提供了更灵活和更强大的功能。它允许更细粒度的控制锁定的获取和释放,例如通过lock()和unlock()方法手动获取和释放锁,也可以通过tryLock()方法尝试获取锁而不需要一直等待。此外,ReentrantLock还支持公平性,即按照线程请求的顺序获取锁,避免产生线程饥饿现象。而synchronized则不保证公平性。 另外,ReentrantLock提供了Condition接口,可以使用newCondition()方法创建多个条件变量。条件变量可以用于线程间的通信,可以让某个线程等待特定条件满足后再继续执行,或者通知其他等待线程某个条件已经满足。 总的来说,synchronized是Java中隐式锁机制的实现,使用方便但功能有限;而ReentrantLock是显示锁机制的实现,提供了更多灵活和强大的功能,但使用起来需要更细致的控制。根据具体情况需要,我们可以选择合适的同步机制来保证线程安全

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值