当前位置:首页 » 《休闲阅读》 » 正文

Java面试——锁

29 人参与  2024年03月22日 16:22  分类 : 《休闲阅读》  评论

点击全文阅读


优质博文IT-BLOG-CN​

公平锁: 是指多个线程按照申请锁的顺序来获取锁,有点先来后到的意思。在并发环境中,每个线程在获取锁时会先查看此锁维护的队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 FIFO 的规则从队列中取到自己。

非公平锁: 指多个线程获取锁的顺序并不是按照申请锁的顺序,上来就尝试占有锁,如果尝试失败,就再采用类似公平锁的方式获取锁。有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

ReentrantLock:并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是false(非公平锁)。非公平的优点在于吞吐量比公平锁大。对于Synchronized锁也是一种非公平锁。

可重入锁(又名递归锁): 指同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码。也就是说,线程可以进入任何一个它已经拥有的锁,所同步的代码块。synchronizedunlock都是可重入锁。

//简单理解,就是方法1 是一个同步方法,里面包含了一个方法2 也是同步方法,但是当进入方法1后,也就获得了方法2的锁,即可重入锁public synchronized void method1(){System.out.println("方法1 synchronized");method2();}public synchronized  void method2(){System.out.printf("方法2 synchronized");}

自旋锁: 是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式尝试获取锁,这样的好处是减少了上下文切换的消耗,确定是循环会消耗CPU。循环比较直到成功为止。

public final int getAndAddInt(Object var1, long var2, int var4){    int var5;    do{        //根据对象和地址偏移量获取内存中的值        var5 = this.getIntVolatile(var1, var2);    //将获取到的值 var5 传入,此方法内部会先比较var2地址的值是否等于 var5,相等则修改var5值并返回,否则重新进入循环。    }while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));        return var5;}

手写一个自旋锁: 思想就是通过while中的循环条件来充当锁,当条件成立时,表示未获得锁,进行死循环,直到while条件不成立,也就是获得锁。就退出死循环,执行业务逻辑。具体查看如下代码:

public class Test {public static void main(String[] args) throws Exception {/*执行结果展示:  AA   myLockBB   myLockAA    unLockBB    unLock *  分析:我们 AA 线程休眠了 5秒足以让 BB 线程执行结束,那为什么 BB 执行到 myLock 之后就没有继续执行呢。 *  其实,BB 一直执行着,只不过陷入了 while 死循环中,因为 AA 将线程置为非空。 *  等到 5 秒后,AA unlock 重新将线程=null时,BB 获取线程并执行任务。over */Test test = new Test();new Thread(()->{test.myLock();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}test.unLock();},"AA").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{test.myLock();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.unLock();},"BB").start();}//对线程保证原子性AtomicReference<Thread> atomicReference = new AtomicReference<>();//获取锁,其实质,将锁看做一个条件判断,只要这个判断能够保证线程安全即可。//如下:我们将线程是否为空作为条件,如果是空的就没锁,自己可以对其加锁,将其值设为自己。//如果使用完,使用unlock 将线程设置为 null,其他线程通过判断来获得锁,其实就像一种约定而已。public void myLock(){Thread thread = Thread.currentThread();System.out.println(thread.getName()+"   myLock");while (!atomicReference.compareAndSet(null,thread)){   }}//释放锁public void unLock(){Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread,null);System.out.println(thread.getName()+"    unLock");}}

自旋锁的优点主要包括:
【1】减少线程阻塞:对于锁竞争不激烈且锁占用时间短暂的情况,自旋锁能够显著提高性能,因为它减少了线程因阻塞而产生的上下文切换开销。
【2】避免内核态切换:与非自旋锁相比,自旋锁在尝试获取锁失败时会继续执行循环而不立即陷入内核态,这样可以避免线程在用户态和内核态之间的频繁切换,这在一定程度上提高了系统的整体性能。

然而,自旋锁也存在一些缺点:
【1】高负载下效率低下:如果锁竞争激烈或持有锁的线程需要长时间执行同步块,自旋锁会因为不断重复无效的旋转操作而导致性能下降。在这种情况下,自旋锁的消耗可能会超过线程阻塞后的恢复成本,因此应该关闭自旋锁以避免不必要的性能损失。1234
【2】可能存在不公平性:某些自旋锁实现(如Java中的)不是完全公平的,这意味着它们可能无法为等待时间最长线程提供优先权,这可能导致所谓的“线程饥饿”问题。
【3】单核处理器上的限制:在单核处理器上,自旋锁实际上没有真正的并行性,因为即使当前线程不阻塞其他线程,锁仍然不会被释放,导致资源的浪费。此外,如果处理器数量少于线程数量,自旋锁也可能造成不必要的资源浪费。4
【4】不适合计算密集型任务:如果任务主要是计算密集型的,使用自旋锁可能会导致性能下降,因为自旋锁会占用CPU资源,而在计算密集型任务中,减少锁的使用可能是更优的选择。

综上所述,自旋锁适用于锁竞争不太激烈且锁占用时间较短的场景,但在竞争激烈或锁占用时间较长的情况下,其性能优势不明显,甚至可能导致性能下降。

【独占锁】(写锁): 指该锁只能被一个线程所持有。对ReentrantLockSynchronized而言都是独占锁。
【共享锁】(读锁): 指该锁可被多个线程持有。

【1】不加读写锁时,代码及出现的问题如下:创建5个线程进行写入,5个线程进行读取。

public class ReadWriteLock {    private volatile Map map = new HashMap();    //写入方法    public void put(String k,Object v){        System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );        try {            TimeUnit.MICROSECONDS.sleep(30);        }catch (Exception e){            e.printStackTrace();        }        map.put(k,v);        System.out.println(Thread.currentThread().getName()+"   写入完成");    }    //读方法    public void get(String k){        System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );        try {            TimeUnit.MICROSECONDS.sleep(10);        }catch (Exception e){            e.printStackTrace();        }        Object v = map.get(k);        System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);    }    public static void main(String[] args) {        ReadWriteLock readWriteLock = new ReadWriteLock();        //写入数据        for (int i=1;i<6;i++){            final int tempInt = i;            new Thread(()->{                readWriteLock.put(tempInt+"",tempInt+"");            },String.valueOf(i)).start();        }        //读取数据        for(int i=1;i<6;i++){            final int tempInt = i;            new Thread(()->{                readWriteLock.get(tempInt+"");            },String.valueOf(i)).start();        }    }}

【2】上述代码输出如下:第一个线程未写入完成时,其他线程就进入了该方法,进行了写操作。不符合多线程安全特性。

在这里插入图片描述

【3】加入读写锁:ReentrantReadWriteLock(读写锁)位于JUC包下

public class ReadWriteLock{    private volatile Map map = new HashMap();    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();    //写入方法    public void put(String k,Object v){        rwLock.writeLock().lock();        try {            System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );            try {                TimeUnit.MICROSECONDS.sleep(30);            }catch (Exception e){                e.printStackTrace();            }            map.put(k,v);            System.out.println(Thread.currentThread().getName()+"   写入完成");        }catch (Exception e){            e.printStackTrace();        }finally {            rwLock.writeLock().unlock();        }    }    //读方法    public void get(String k){        rwLock.readLock().lock();        try {            System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );            try {                TimeUnit.MICROSECONDS.sleep(10);            }catch (Exception e){                e.printStackTrace();            }            Object v = map.get(k);            System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);        } catch (Exception e) {            e.printStackTrace();        } finally {            rwLock.readLock().unlock();        }    }    public static void main(String[] args) {        ReadWriteLock readWriteLock = new ReadWriteLock();        //写入数据        for (int i=1;i<6;i++){            final int tempInt = i;            new Thread(()->{                readWriteLock.put(tempInt+"",tempInt+"");            },String.valueOf(i)).start();        }        //读取数据        for(int i=1;i<6;i++){            final int tempInt = i;            new Thread(()->{                readWriteLock.get(tempInt+"");            },String.valueOf(i)).start();        }    }}

【4】加入读写锁后,输出如下:
在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/83734.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1