可重入锁(synchronized ,ReentrantLock ),读锁(共享锁),写锁(独占锁),公平锁,非公平锁与自旋锁(也称乐观锁,自己编写)

介绍这几种锁之前,先了解一下jdk1.8的并发相关的包,今天我们了解l这几种锁都在looks包下

可以看到locks包下主要有三个接口,可重入锁介绍的Lock接口的主要实现类ReentrantLock

lock与synchronized的区别

synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。

1,可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。

 synchronized 和   ReentrantLock 都是可重入锁。可重入锁的意义之一在于防止死锁(各自持有对方的锁)。

//可重入锁
public class ReentrantLockDemo{
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sms();
        },"a").start();

        new Thread(()->{
            phone.sms();
        },"b").start();
    }
}
class Phone{
    public synchronized  void sms(){
        System.out.println(Thread.currentThread().getName()+"----sms");
        call();//这个方法也有锁
    }
    public synchronized  void  call(){
        System.out.println(Thread.currentThread().getName()+"---call");
    }
}

class Phone2{
    Lock lock = new ReentrantLock();
    public  void sms(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"----sms");
            call();//这个方法也有锁
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public  void  call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"---call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

代码解释:按道理来说A线程输出sms调call方法前就释放sms方法的锁,然后在B线程参与锁的竞争,可能会产生的的输出结果是 Asms Bsms Acall Bcall,但是输出的结果永远都是A先sms,call Bsms,call就是因为A线程并不是在调用call方法前释放了sms的锁而是在call方法执行完才释放了sms的锁,很多人就会问他不是还得获取call方法的锁,这就是可重入锁,他并不用获取call方法的锁,相当于获得的大门了钥匙,大门里面的小门钥匙就不用获取

2,读写锁,也称共享锁与独占锁(synchronized),共享锁见名知意,共享嘛,可以被多个线程去读,独占锁,写的时候只有一个线程去写

ReadWriteLock接口的主要实现ReentrantReadWriteLock ,

没加锁之前运行下面这段代码可以看到运行结果

public class ReadWriteLockDemo {
    private volatile Map<String, Object>  map = new HashMap<>();
    //写入,我只希望同时有只有一个线程写
    public void put (String key,Object value){
        System.out.println(Thread.currentThread().getName()+"写入---》"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"写入ok---》"+value);
    }
    //读 我希望有多个线程可以读
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取---》"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取成功---》"+o);
    }

    public static void main(String[] args) {
        ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
        //写入
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            new Thread(()->{
                readWriteLockDemo.put(temp+"",temp+"");
            },String.valueOf(i)).start();

        }
        //读取
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            new Thread(()->{
                readWriteLockDemo.get(temp+"");
            },String.valueOf(i)).start();

        }

    }

可以清晰的在多线程的情况下问题很大,1还没有写入成功就要读取成功了,而且1在写入的时候被其他线程插队,下面我们用读写锁改进 

public class ReadWriteLockDemo {
    private volatile Map<String, Object>  map = new HashMap<>();
    //读写锁,更加细粒的控制线程
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //写入,我只希望同时有只有一个线程写 原子性操作
    public void put (String key,Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入---》"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入ok---》"+value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    //读 我希望有多个线程可以读 非原子性操作 可以插队
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取---》"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取成功---》"+o);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
        //写入
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            new Thread(()->{
                readWriteLockDemo.put(temp+"",temp+"");
            },String.valueOf(i)).start();

        }
        //读取
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            new Thread(()->{
                readWriteLockDemo.get(temp+"");
            },String.valueOf(i)).start();

        }

    }

 加了读写锁之后,发现写入他是一个原子性操作,而读取不是,他允许插队

3,公平锁FairSync与非公平锁NonfairSync        

非公平锁,可以线程插队执行,假设一个线程先执行,要3小时才执行完,一个只需要3秒的线程,如果是公平锁那就必须等3个小时线程执行完才会执行3秒的,可能会导致性能倒置问题,有的时候我们希望3秒的先执行就使用非公平锁。点进ReentrantLock源码我们不难发现,它其实有两个构造方法一个有参一个无参的构造方法,无参的构造方法默认是new了一个非公平锁,有参的需要自己传一个boolean值来设置true为公平锁false为非公平锁,在进入方法仔细观察他们的区别

 public ReentrantLock() {
        sync = new NonfairSync();
    }

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

 static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

 static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        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;
        }
    }

可以看到,公平锁的tryAcquire实现和非公平锁的tryAcquire实现的区别在于:公平锁多加了一个判断条件:hasQueuedPredecessors,如果发现有线程在等待获取锁了,那么就直接返回false,否则在继承尝试获取锁,这样就保证了线程是按照排队时间来有限获取锁的。而非公平锁的实现则不考虑是否有节点在排队,会直接去竞争锁,如果获取成功就返回true,否则返回false。

当然,这些分支执行的条件是state为0,也就是说当前没有线程独占着锁,或者获取锁的线程就是当前独占着锁的线程,如果是前者,就按照上面分析的流程进行获取锁,如果是后者,则更新state的值,如果不是上述的两种情况,那么直接返回false说明尝试获取锁失败。

公平锁的lock使用了AQS的acquire,而acquire会将参与锁竞争的线程加入到等待队列中去按顺序获得锁,队列头部的节点代表着当前获得锁的节点,头结点释放锁之后会唤醒其后继节点,然后让后继节点来竞争获取锁,这样就可以保证锁的获取是按照一定的优先级来的。而非公平锁的实现则会首先尝试去竞争锁,如果不成功,再走AQS提供的acquire方法非公平锁的tryAcquire方法使用了父类的nonfairTryAcquire方法来实现。

3,自旋锁(乐观锁)

所谓自旋锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。

public class OptimisticLockDemo {
    //Thread null
     AtomicReference<Thread> atomicReference = new AtomicReference<>();
     //加锁
    public void  mylock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==>mylock");

        //自旋锁
        while(!atomicReference.compareAndSet(null, thread)){

        }
    }
    //解锁
    public void myUnlock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==>myUnlock");
        atomicReference.compareAndSet(thread, null);
    }

    public static void main(String[] args) throws InterruptedException {
        OptimisticLockDemo optimisticLockDemo = new OptimisticLockDemo();
        new Thread(()->{
            optimisticLockDemo.mylock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                optimisticLockDemo.myUnlock();
            }
        },"T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            optimisticLockDemo.mylock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                optimisticLockDemo.myUnlock();
            }
        },"T2").start();

    }
}

代码中T1线程先获取锁,把null改为thread 然后睡眠5秒在解锁把thread改为null,T2线程在T1获取锁的未释放锁的时候,进入加锁的方法时会一直自旋,直到T1解锁完才能获取锁,他是一种乐观的形态,一直去尝试去获取锁,

而与synchronized和WriteLock不同的是synchronized与WriteLock他只要有线程获取锁,其他线程必须等待他解锁完才能去竞争锁,是一种悲观行为

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值