java常见锁Reentrantlock,synchronized,SpinLock,ReadWriteLock

java公平锁和非公平锁(Reentrantlock)

  • 公平锁―是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。

  • 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁。在高并发的情况下,有可能会造成优先级反转或者饥饿现象

并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁。

此类的构造函数接受可选的公平性参数。当设置为true时,在争用下,锁有利于向等待时间最长的线程授予访问权限。否则,此锁不保证任何特定的访问顺序。与使用默认设置的程序相比,使用由许多线程访问的公平锁的程序可能显示出较低的总体吞吐量(即,较慢;通常要慢得多),但是在获得锁和保证没有饥饿的时间上差异较小。

但是请注意,锁的公平性并不能保证线程调度的公平性。因此,使用公平锁的多个线程中的一个线程可以连续多次获得公平锁,而其他活动线程则没有进行并且当前没有持有该锁。还要注意,不计时的 tryLock()方法不支持公平性设置。如果锁可用,即使其他线程正在等待,它也会成功。

两者区别

关于两者区别:

公平锁
Threads acquire a fair lock in the order in which they requested it.
公平锁就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁
a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lockhappens to be available when it is requested.
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。

非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized而言,也是一种非公平锁

值得注意的是:jvm对线程的调度策略是抢占式的,优先级越高的线程,可能越会获取cpu的处理权

java可重入锁和递归锁(Reentrantlock/synchronized)

可重入锁(也叫做递归锁)

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁(也即线程对于内存锁代码,不需要再次争抢锁)。

也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。

ReentrantLock/synchronized就是一个典型的可重入锁。

可重入锁最大的作用是避免死锁。

synchronized可重入锁

class Phone {

    public synchronized void sendSMS() throws Exception{
        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");

        // 在同步方法中,调用另外一个同步方法
        sendEmail();
    }


    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail()");
    }
}


public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSMS();
        },"t1").start();

        new Thread(()->{
            phone.sendSMS();
        },"t2").start();
    }

输出结果

t1	 invoked sendSMS()
11	 invoked sendEmail()
t2	 invoked sendSMS()
12	 invoked sendEmail()

ReentrantLock可重入锁

public class ReentrantLockTest {
    public static void sendSMS(){
        System.out.println(Thread.currentThread().getName()+" sendSMS");
        sendEmail();
    }

    public static void sendEmail(){
        System.out.println(Thread.currentThread().getName()+" sendEmail");
    }


    public static void main(String[] args) {


        Lock lock = new ReentrantLock();

        new Thread(()->{
            lock.lock();
            try {
                sendSMS();
            }finally {
                lock.unlock();
            }
        },"t1").start();

        new Thread(()->{
            lock.lock();
            try {
                sendSMS();
            }finally {
                lock.unlock();
            }
        },"t2").start();

    }
}

输出结果

t1	 invoked sendSMS()
11	 invoked sendEmail()
t2	 invoked sendSMS()
12	 invoked sendEmail()

java自旋锁(spinLock)

自旋锁(Spin Lock)

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU (以循环方式消耗cpu的代价,不让线程堵塞(挂起))

unsafe中的getAndAddInt就是一个良好示例

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

提到了互斥同步对性能最大的影响阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态完成,这些操作给系统的并发性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程 “稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
《深入理解JVM.2nd》Page 398

为什么要使用自旋锁,就是多个线程对同一变量进行访问,为了线程安全加锁,但是由于线程使用共享变量很短一段时间,挂起线程进入堵塞状态,然后回复,消耗大。因此采用循环访问锁的方式,获取锁。

public class SpinLockTest {

    AtomicReference<String> atomicReference = new AtomicReference<>("unlock");

    public void lock(){
        System.out.println(Thread.currentThread().getName()+ " come in");

        // 如果值不为unlock时, 进行当前线程进行循环获取锁。值为unlock时,获取该锁并退出
        while (!atomicReference.compareAndSet("unlock","lock")){

        }

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

    }

    public void unlock(){
        atomicReference.compareAndSet("lock","unlock");
        System.out.println(Thread.currentThread().getName()+ " unlock");
    }


    public static void main(String[] args) {
        // 假设我们希望当原子引用共享对象为unlock值时,才可以访问这个共享对象
        SpinLockTest spinLock = new SpinLockTest();

        new Thread(()->{
            spinLock.lock();
            try {
                TimeUnit.SECONDS.sleep(8); }catch (InterruptedException e){
            }finally {
                spinLock.unlock();
            }
        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){}

        new Thread(()->{
            spinLock.lock();
            try {

            }finally {
                spinLock.unlock();
            }
        },"t2").start();

    }
}

java读写锁(ReentrantReadWriteLock)

独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁

共享锁:指该锁可被多个线程所持有。

多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源来,就不应该再有其它线程可以对该资源进行读或写。

对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

读写锁详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白鸽呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值