获得更好并发和线程安全型的场景和解决方案

标签: 并发 线程安全 的场 | 发表时间:2014-04-17 11:06 | 作者:
出处:http://www.iteye.com

适用场景:

               你需要从数据库加载股票交易的安全代码并且考虑到性能进行了缓存。这些安全代码需要每30分钟刷新一次。在这里缓存数据需要一个单独的写线程进行生成和刷新,并且被若干读进程进行读取。这种情况下,你又要如何来保证你的读写方案做到良好的扩展和线程安全呢?

 

                    解决方案:java.util.concurrent.locks包提供了可以并行执行的写锁以及被单个线程独占的写锁的实现。这个ReadWriterLock(读写锁)接口包含了一对关联的锁,一个只读锁和一个写锁。其中readLock()(读锁)可以被多个进行读操作的线程同时持有,然而writeLock(写锁)确实独占的。通常情况下,这种实现方式可以在与以下场景下相对于互斥锁可以显著的提高性能以及程序的可扩展性

场景一:

       读操作以及读的频率要频繁于写操作和写的频率。

场景二:执行场景依赖底层操作系统 -- 例如在多核处理器上为了实现更好的并行执行

 

ConcurrentHashMap是另外一个可以在读多于写操作的情况下提高性能的数据结构。ConcurrentHashMap允许并发读取以及独占的更新或者插入操作。

 

这里有一些技术可以让你更加了解锁的实现

Q:什么是可重入锁?

A:可重入锁是作为传统“等待-唤醒”方法的替代物出现的。它的基本实现理念是,每一个需要进入临界区的线程需要获取锁并且应该在随后的操作完成后释放锁。ReetrantLock可以通过减少“synchronized”关键字同步来提供更好的并发

 

   reetrant这个单词其实意味着如果线程试图进去当前线程已持有锁保护的并且需要同步的代码块,线程会默许操作是允许的,并且不会在线程退出第二个(随后的)同步代码区释放持有的锁,只有在线程退出被同一锁保护的首先进入的同步代码块时才会释放锁,因为锁本身维护着锁的获取次数,并且如果一个已经获取锁的线程需要重新获取锁,维护的锁的大小会增加并且当前线程需要释放两次才能真正的释放锁,写线程可以获取读锁(优先级比读线程高) ---但是读线程却不能获取写锁,如果读线程尝试获取写锁,那将是永不能得到的(只能双眼包含热泪的望着).

 

Q:在释放锁的时候需要的注意事项?

A:锁的释放操作需要在finally块中进行,否则如果程序出现异常,那您的那把锁可能永远丢不掉(哈哈!)

Q:什么理由使你在已有synchronized关键字的情况下仍然选择使用重入锁?并且都能从中获取哪些好处?

A:重入锁在并发读的时候有更好的可扩展性。前面已经说过,java.util.concurrent.lock包中的若干锁的实现是针对于高级用户的高级工具,并且也在上面讨论过的特定场景下适用。通常来说,如果没有如下的条件作为前提还是规劝您老实的适用synchronization:

  1.读操作的个数远多于写操作

  2.可以通过一些测试数据证实在特定场景下同步是主要的扩展瓶颈

 

  3.诸如超时锁等待,可中断的锁等待,非阻塞结构的,或者有多个条件需要判断,或者需要轮询锁这些普通锁不具有的或者实现不如此的情况下

 

Q:什么是公平锁和非公平锁?

A:ReetrantLock的构造器有一个boolean类型的参数可以用来指示是否使用公平锁还是非公平锁。公平锁是获取锁的顺序和线程请求锁的线程一致。也就是说,当前的写锁释放的时候,无论是一个写线程等待多久,只要有等待队列中有读线程在写线程之前,读操作的线程就会首先得到读锁。但是当你使用的是非公平锁的时候,获取锁的顺序就不一定是线程请求的顺序了。

  如果读线程是活跃的,那么只有写线程已经获取写锁并且释放写锁后读线程才能被授予读锁,下面是一个读写锁的示例。

 

 

 

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 
public class SharedData<T> {
  
 private List<Integer> numbers = new ArrayList<Integer>(20);
 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
 
 public List<Integer> getTechnologies() {
  return numbers;
 }
  
 public void writeData(Integer number) {
  String threadName = Thread.currentThread().getName();
  lock.writeLock().lock(); //如果没有其他线程持有读锁和写锁,获取写锁
                           //写锁只允许一个线程持有
   
  System.out.println("threads waiting for read/write lock: " + lock.getQueueLength());
  // This should be always one
  System.out.println("writer locks held " + lock.getWriteHoldCount());
  try {
   numbers.add(number);
   System.out.println(">>>>>" + threadName + " writing: " + number);
   Thread.sleep(2000);    // 线程休眠两秒钟
  } catch (InterruptedException e) {
   e.printStackTrace();
  } finally {
   System.out.println(threadName + " releasing write lock");
   lock.writeLock().unlock();  //释放锁
  }
 }
  
 public void readData() {
    String threadName = Thread.currentThread().getName();
    lock.readLock().lock();//在没有线程持有写锁的情况下获取读锁
                           //允许并发读操作
     
    System.out.println("threads waiting for read/write lock: " + lock.getQueueLength());
    System.out.println("reader locks held "  + lock.getReadLockCount());
    try {
   for (Integer num: numbers) {
    System.out.println("<<<<<<<" + threadName + " reading: " + num);
   } 
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    finally{
     System.out.println(threadName + " releasing read lock");
     lock.readLock().unlock();  //释放锁
    }
     
  }
}

 

 

下一步,定义读和写线程类。

 

public class Reader<T> extends Thread {
  
 private SharedData<T> sharedData;
  
 public Reader(SharedData<T> sharedData) {
  this.sharedData = sharedData;
 }
 
 @Override
 public void run() {
  sharedData.readData();
 }
}

 

 

public class Writer<T> extends Thread {
  
 private boolean oddNumber = true;
 private SharedData<T> sharedData;
  
 public Writer(SharedData<T> sharedData, boolean oddNumber ) {
  this.sharedData = sharedData;
  this.oddNumber = oddNumber;
 }
 
 @Override
 public void run() {
  for(int i=1; i<=2; i++) {
   if(!oddNumber && i%2 == 0) {
      sharedData.writeData(i);
   }
   else if (oddNumber && !(i%2 == 0)){
      sharedData.writeData(i); 
   }
  }
 }
}

最后,ReadWriteProcessor(读写主线程)类需要生成读写线程并为线程传递SharedData.

会有如下输出结果:

threads waiting for read/write lock: 0
writer locks held 1
>>>>>oddWriter writing: 1
oddWriter releasing write lock
threads waiting for read/write lock: 3
writer locks held 1
>>>>>evenWriter writing: 2
evenWriter releasing write lock
threads waiting for read/write lock: 2
threads waiting for read/write lock: 1
reader locks held 3
threads waiting for read/write lock: 0
reader locks held 3
reader locks held 2
<<<<<<<reader1 reading: 1
<<<<<<<reader2 reading: 1
<<<<<<<reader3 reading: 1
<<<<<<<reader2 reading: 2
<<<<<<<reader1 reading: 2
<<<<<<<reader3 reading: 2
reader1 releasing read lock
reader3 releasing read lock
reader2 releasing read lock

 

其实还有下面的一些java.util.concurrent包下的类可以在其他现实场景下起到很好的作用.

    1.CountDownLatch

    2.CyclicBarrier

    3.Semaphore

    4.Atomic 若干类



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [并发 线程安全 的场] 推荐:

获得更好并发和线程安全型的场景和解决方案

- - ITeye博客
               你需要从数据库加载股票交易的安全代码并且考虑到性能进行了缓存. 这些安全代码需要每30分钟刷新一次. 在这里缓存数据需要一个单独的写线程进行生成和刷新,并且被若干读进程进行读取. 这种情况下,你又要如何来保证你的读写方案做到良好的扩展和线程安全呢.                     解决方案:java.util.concurrent.locks包提供了可以并行执行的写锁以及被单个线程独占的写锁的实现.

Spring并发访问的线程安全性问题

- - 寒江孤影
和Struts一样,Spring的Controller默认是Singleton的,这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果:一是我们不用每次创建Controller,二是减少了对象创建和垃圾收集的时间;由于只有一个Controller的instance,当多个线程调用它的时候,它里面的instance变量就不是线程安全的了,会发生窜数据的问题.

什么是线程安全

- - CSDN博客编程语言推荐文章
线程安全是多线程领域的问题,线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题. 在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源. 如,同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件. 实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的.

Servlet是否线程安全

- - 研发管理 - ITeye博客
Servlet是线程安全吗. 要解决这个问题,首先要知道什么是线程安全:.   如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码. 如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题.

web开发中的线程安全

- - 编程 - 编程语言 - ITeye博客
在web开发中,要关注由于并发访问所导致的对某一同一个值的修改,否则信息会造成泄漏servlet是在多线程环境下的. 即可能有多个请求发给一个servelt实例,每个请求是一个线程. struts下的action也类似,同样在多线程环境下. 译:为多线程环境编写代码. 我们的controller servlet指挥创建你的Action 类的一个实例,用此实例来服务所有的请求.

Spring单实例、多线程安全、事务解析

- - zzm
 在使用Spring时,很多人可能对Spring中为什么DAO和Service对象采用单实例方式很迷惑,这些读者是这么认为的:.     DAO对象必须包含一个数据库的连接Connection,而这个Connection不是线程安全的,所以每个DAO都要包含一个不同的Connection对象实例,这样一来DAO对象就不能是单实例的了.

APP 缓存数据线程安全问题探讨

- - bang’s blog
一般一个 iOS APP 做的事就是:请求数据->保存数据->展示数据,一般用 Sqlite 作为持久存储层,保存从网络拉取的数据,下次读取可以直接从 Sqlite DB 读取. 我们先忽略从网络请求数据这一环节,假设数据已经保存在 DB 里,那我们要做的事就是,ViewController 从 DB 取数据,再传给 view 渲染:.

EntityFramework DbContext 线程安全 - 田园里的蟋蟀 - 博客园

- -
不要被提示信息中的 Use 'await' 所迷惑,如果你仔细查看下代码,发现并没有什么问题,上面这段异常信息,是我们在 async/await 操作的时候经常遇到的,什么意思呢. :在这个上下文,第二个操作开始于上一个异步操作完成之前. 可能有点绕,简单说就是,在同一个上下文,一个异步操作还没完成,另一个操作就开始了.

Spring单例Bean和线程安全 - duanxz - 博客园

- -
Spring的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢. 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢.

设计高效的线程安全的缓存--JCIP5.6读书笔记

- - ITeye博客
[本文是我对Java Concurrency In Practice 5.6的归纳和总结.  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. 几乎每一个应用都会使用到缓存, 但是设计高效的线程安全的缓存并不简单. // 使用synchronized同步整个方法解决线程安全. Memorizer1使用HashMap缓存计算结果.