欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java高并发锁的3种实现示例代码

程序员文章站 2024-02-24 12:12:04
初级技巧 - 乐观锁 乐观锁适合这样的场景:读不会冲突,写会冲突。同时读的频率远大于写。 以下面的代码为例,悲观锁的实现: public object get...

初级技巧 - 乐观锁

乐观锁适合这样的场景:读不会冲突,写会冲突。同时读的频率远大于写。

以下面的代码为例,悲观锁的实现:

public object get(object key) { 
  synchronized(map) { 
   if(map.get(key) == null) { 
     // set some values 
   } 
    return map.get(key); 
  } 
} 

乐观锁的实现:

public object get(object key) { 
  object val = null; 
  if((val = map.get(key) == null) { 
    // 当map取值为null时再加锁判断 
    synchronized(map) { 
      if(val = map.get(key) == null) { 
        // set some value to map... 
      } 
    } 
  } 
  return map.get(key); 
} 

中级技巧 - string.intern()

乐观锁不能很好解决大量写冲突问题,但是如果很多场景下,锁实际上只是针对某个用户或者某个订单。比如一个用户必须先创建session,才能进行后面的操作。但是由于网络原因,创建用户session的请求和后续请求几乎同时达到,而并行线程可能会先处理后续请求。一般情况,需要对用户sessionmap加锁,比如上面的乐观锁。在这种场景下,可以讲锁限定到用户本身上,即从原来的

lock.lock();

  int num=storage.get(key);

  storage.set(key,num+1);

lock.unlock();

更改为:

lock.lock(key);

  int num=storage.get(key);

  storage.set(key,num+1);

lock.unlock(key);

这个比较类似于数据库表锁和行锁的概念,显然行锁的并发能力比表锁高很多。

使用string.inter()是这种思路的一种具体实现。类 string 维护一个字符串池。 当调用 intern 方法时,如果池已经包含一个等于此 string 对象的字符串(该对象由 equals(object) 方法确定),则返回池中的字符串。可见,当string相同时,string.intern()总是返回同一个对象,因此就实现了对同一用户加锁。由于锁的粒度局限于具体用户,使系统获得了最大程度的并发。

public void dosomething(string uid) { 
  synchronized(uid.intern()) { 
    // ... 
  } 
}

copyonwritemap?

既然说到了“类似于数据库中的行锁的概念”,就不得不提一下mvcc,java中copyonwrite类实现了mvcc。copy on write是这样一种机制。当我们读取共享数据的时候,直接读取,不需要同步。当我们修改数据的时候,我们就把当前数据copy一份副本,然后在这个副本 上进行修改,完成之后,再用修改后的副本,替换掉原来的数据。这种方法就叫做copy on write。

但是,,,jdk并没有提供copyonwritemap,为什么?下面有个很好的回答,那就是已经有了concurrenthashmap,为什么还需要copyonwritemap?

fredrik bromee 写道

i guess this depends on your use case, but why would you need a copyonwritemap when you already have a concurrenthashmap?

for a plain lookup table with many readers and only one or few updates it is a good fit.

compared to a copy on write collection:

read concurrency:

equal to a copy on write collection. several readers can retrieve elements from the map concurrently in a lock-free fashion.

write concurrency:

better concurrency than the copy on write collections that basically serialize updates (one update at a time). using a concurrent hash map you have a good chance of doing several updates concurrently. if your hash keys are evenly distributed.

if you do want to have the effect of a copy on write map, you can always initialize a concurrenthashmap with a concurrency level of 1.

高级技巧 - 类concurrenthashmap

string.inter()的缺陷是类 string 维护一个字符串池是放在jvm perm区的,如果用户数特别多,导致放入字符串池的string不可控,有可能导致oom错误或者过多的full gc。怎么样能控制锁的个数,同时减小粒度锁呢?直接使用java concurrenthashmap?或者你想加入自己更精细的控制?那么可以借鉴concurrenthashmap的方式,将需要加锁的对象分为多个bucket,每个bucket加一个锁,伪代码如下:

map locks = new map(); 
list lockkeys = new list(); 
for(int number : 1 - 10000) { 
  object lockkey = new object(); 
  lockkeys.add(lockkey); 
  locks.put(lockkey, new object()); 
}  
public void dosomething(string uid) { 
  object lockkey = lockkeys.get(uid.hash() % lockkeys.size()); 
  object lock = locks.get(lockkey); 
  synchronized(lock) { 
   // do something 
  } 
} 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。