首页 > 知识百科 > 正文

Java面试——锁原创

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

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

ReentrantLock:并发包中ReentrantLock的可以指定构造函数的boolean类型来获得公平锁或非公平锁,默认为false< /code>(非公平锁)。非公平的优点在于货物比公平锁大。对于同步锁也是一种非公平锁。

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

//简单理解,就是方法1是一个同步方法,里面包含了一个方法2也是同步方法,但是当进入方法1后,就获得了方法2的锁,即可重新进入锁公共 同步 void 方法1 (){< span class="d272-3dcd-b2e2-1d12 token class-name">系统.out.println("方法1同步") ;方法2();}公共 同步 void 方法d2(){ 系统out< span class="0c91-6ebe-7da0-b411 token function">printf("方法2同步");}

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

public 最终 int getAndAddInt( 对象 var1  var2 int var4){ int var5; do{ //根据对象和地址偏移量获取内存中的值 var5 = 这个getIntVolatile(var1 , var2); //将获取到var5的值确定,此方法内部会先比较var2地址的值是否等于var5,否则则修改var5值并返回,否则重新进入循环。 }while(!这个compareAndSwapInt(var1  var2 var5 var5 + var4)); 返回 var5;}< /pre> 

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

公共 class 测试 {< !-- -->公共 静态 void span> main(字符串[] args) 抛出< /span> 异常 {/ *执行结果显示: AA myLockBB myLockAA unLockBB unLock * 分析:我们AA线程休眠了5秒足以让BB线程执行结束,那为什么BB执行到myLock之后就没有继续执行呢。 *其实,BB一直执行着,只不过陷入了死循环中,因为 AA 将线程置为非空。 * 等到 5 秒后,AA 重新解锁将线程=null时,BB 获取线程并执行任务。over */测试测试=  测试();  线程(()->{测试myLock();尝试 {时间单位睡眠(5 );} catch (InterruptedException e ) {e printStackTrace();}测试解锁( );},"AA").开始()< span class="d077-0c91-6ebe-7da0 token punctuation">;TimeUnit.睡眠(< span class="a15e-89b9-d272-3dcd token number">1); 线程((< span class="1a17-d077-0c91-6ebe token punctuation">)->{测试<跨度类=令牌标点符号">。myLock();尝试 { 时间单位睡眠(1 );} catch (InterruptedException e) {e.printStackTrace();}测试解锁();},"BB")开始();}//对保证线程原子性AtomicReference<线程>atomicReference =  AtomicReference<>();//获取锁,其实质,将锁看做一个条件判断,只要这个判断能够保证线程安全即可。//如下:我们将线程是否为空作为条件,如果是空的没锁,自己可以对其加锁,将其值设为自己。//如果使用完毕,使用unlock将线程为null,其他线程通过判断来获得锁,其实就像一种约定而已。public voidpublic void span> myLock(){Thread 线程 = Thread当前线程() span>;系统.outprintln(线程getName()+"myLock"); 同时 (!atomicReferencecompareAndSet(null,线程)){ }}//释放锁public void span> 解锁(){线程 线程 = 线程当前线程();atomicReferencecompareAndSet(线程null);系统.out.println(线程getName()+“解锁”);}}

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

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

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

【独占锁】(写锁): 指该锁只能被一个线程所持有。对ReentrantLock同步方面都是独占锁。
【共享锁】(读锁): 指该锁可被多个线程持有。

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

public class ReadWriteLock { 私有 易失性 地图 地图 =   HashMap(); //写入方法 public void put(字符串 k,对象 v){ 系统.out .println(线程当前线程( )getName()+" 开始写入:&# 34;+k ); 尝试 { TimeUnit< span class="0c91-6ebe-7da0-b411 token punctuation">。微秒睡眠(30); }catch (< /span>异常 e){ 系统.out.println(线程 当前线程()getName()+" 读取数据开始:"+k < span class="a15e-89b9-d272-3dcd token punctuation">); 尝试  { 时间单位微秒睡眠(10); } catch (异常 e){ eprintStackTrace()< /span>; } 对象 v = 映射获取 (k); 系统 .out.println(线程当前线程()。 span>getName()+"读取数据完成场完成"+v); } 公共 < span class="1a17-d077-0c91-6ebe token 关键字">静态 void main (字符串[] args ) { ReadWriteLock readWriteLock =  ReadWriteLock< /span>(); //写入数据 for ( int i=1;i< span class="a15e-89b9-d272-3dcd token 运算符"><6;i++){ final int tempInt = i;  线程(()->{ readWriteLock.put(tempInt+"",< /span>tempInt+""); },字符串valueOf(i)) .开始(); } //读取数据 for(int i= 1;i<6;i++){ 最终 int特mpInt = i;  主题(()->{ readWriteLock.< /span>获取(tempInt+""); }< /span>,字符串valueOf(i))开始 (); } }}

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

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

public class ReadWriteLock { 私有 易失性 Map 地图 =  HashMap( ); 私有 可重入ReadWriteLock rwLock =  可重入ReadWriteLock(); //写入方法 public void put(字符串 k对象 v){rwLock.writeLock()锁定(< span class="89b9-d272-3dcd-b2e2 token punctuation">); 尝试  { 系统.out.println(线程.当前线程()getName()+"开始写入:"+k ); 尝试 { TimeUnit< /span>.微秒.睡眠(30); }catch (异常 e){ e.printStackTrace(); } 映射放置(k,v); 系统.out.println (线程当前线程()getName( )+"写入完成"); }catch (异常 e){ e.printStackTrace(); }< /span>最后 { rwLock.writeLock()解锁(< span class="ae86-a15e-89b9-d272 token punctuation">); }  } //读取方法 public void 获取(字符串 k){ rwLockreadLock()锁定(); 尝试 { 系统.out.println(线程.< /span>当前线程()getName()+" 读取数据开始:"+k ); 尝试 { 时间单位微秒睡眠(10); }catch (异常 e){ eprintStackTrace(); } 对象 v = 映射获取(k); 系统.out.println(线程 当前线程().getName()+"读取数据完成场完成"+v); }  catch (异常 e< span class="d272-3dcd-b2e2-1d12 token punctuation">) { e< span class="7dc5-9fe2-1a17-d077 token function">printStackTrace() ; } 最后 {  rwLockreadLock()解锁< /span>(); } } 公共 静态 void main(字符串[] args) { ReadWriteLock readWriteLock =   读写锁(); / /写入数据 for (int i=1;i<6;i++){ 最终 int tempInt = i;  线程(()->{ readWriteLockput(tempInt+"",tempInt+"");< /span> },字符串valueOf(i) )开始(); } //读取数据 for(int i=1< /span>;i<6;i++){  final int tempInt =  i;  线程(()->{ readWriteLockget(tempInt+"" ); }字符串valueOf (i))开始(); }  }}

【4】加入读写锁后,输出如下:

Java面试——锁原创由知识百科栏目发布,感谢您对的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Java面试——锁原创