java并发编程-1

以前研究过并发编程,但是没有深入,这次有时间了深入研究下。并发编程中只要掌握两个点就好了,一个是线程之间的互斥,一个是线程的通信。

1、互斥: 互斥的概念就是当线程A在执行某个方法时,只有当A完全执行完之后其他的线程才可以访问这个方法,如果A没有执行完,那么其他要访问这个方法的线程将阻塞。

2、通信:如果要很多线程都阻塞了,那么当A执行完之后应该怎么办呢?是让A线程继续执行还是随机选取一个线程执行?这里就是线程之间的通信。

互斥

现在使用的互斥的工具有两个,一个是synchronized,一个是lock(java.util.conrrent.locks.Lock)。

synchronized:可以用在方法上,也可以用在方法体内部。这个的意思是获取某个对象的监视器,进入这个方法(或者被synchronized包含的方法体)必须要获得这个对象的监视器(monitor)。如果是用在方法上的,比如 public void synchronzed aa(){}  那么获得的是这个方法的实例的对象的监视器(也就是aa这个方法所在类的当前实例的监视器);在方法体内比如 public  void aa(){ synchronized(someObject) {//do something}},就可以设置要同步的对象(也就是要获取谁的监视器)。当某个对象的监视器没有释放(也就是当前有个线程在执行某个带有synchronized的方法并且没有执行完),其他的线程不能获得监视器也就无法执行这个方法。只有当这个线程执行完成或者是调用了wait()方法之后才会释放这个监视器,然后先前被阻塞的线程获得这个监视器并继续执行。

lock:为什么有了synchronized还要有lock呢?一定是因为lock可以完成synchronized不可以完成的。synchronized有一个很大的缺陷,即某些只读的操作也会被阻塞。举个例子,对于某些缓存的框架,比如hibernate的二级缓存在查询时会先进性判断id=1的在缓存中是否存在,如果不存在的话就会从DB中查找,如果存在则直接从缓存中获得,这样就会提高效率。在这个例子中如果不适用同步的限制,就会出现多次查找的问题,比如下图中,如果有多个线程同时执行这个get(String id)方法,就会可能出现查询多次的情况。

public Class Test12161615{
   //用于从DB中查找的dao实例
   private Dao dao = xxxx;
   //缓存
    Map<String,Object> cache = new HashMap<String,Object);
   //根据id查找
    public Object get(String id){
         Object o = cache.get(id);
        
         if(o == null){
              //如果没有同步,假设有A线程执行到这里后B线程执行该方法,B到这里后也停止C在执行......o==null的条件都成立,所以就会执行多次查找,所以上锁是必须的。

             o = dao.queryById(id);
             cache.put(o.getId(),o);
         }
         return o;
    }


}

  如果上的是synchronized的锁,可以解决上面的问题,但是又会出现新的问题。

public Class Test12161615{
   //用于从DB中查找的dao实例
   private Dao dao = xxxx;
   //缓存
    Map<String,Object> cache = new HashMap<String,Object);
   //根据id查找
    //添加了synchronized之后,某个时间点上只有一个线程可以进入这个方法,所以解决了多次查询的问题。但是当cache中有要查询的id时,即多个线程只是查询的线程时也会被阻塞,造成资源的浪费。
    public synchronized Object get(String id){
         Object o = cache.get(id);
        
         if(o == null){
             o = dao.queryById(id);
             cache.put(o.getId(),o);
         }
         return o;
    }


}

 所以要用lock来解决这个问题,lock之所以不同于synchronized是因为他区分了读锁和写锁,写锁和写锁,写锁和读锁是互斥的,但是读锁和读锁是不互斥的,这样就解决了并发读时的阻塞问题,并且不会引起多次写的问题。

public Class Test12161615{
   //用于从DB中查找的dao实例
   private Dao dao = xxxx;
   //缓存
    Map<String,String> cache = new HashMap<String,String);
    Lock lock = new ReentrantReadWriterLock();
    public String getFromCache(String key){
		String value = null;
		try {
			//读取数据,上读锁
			lock.readLock().lock();
			value = cache.get(key);
			if(value == null){
				//没有数据,要写,上写锁,先释放读锁,因为读锁不能升级为写锁。
				lock.readLock().unlock();
				try {
					lock.writeLock().lock();//上写锁
					if( value == null){//再次判断,因为其他线程可能已经执行了这个方法,
						value = getFromDb();
					}	
					lock.readLock().lock();//降级为读锁,写锁可以降为读锁。
				} finally {
					lock.writeLock().unlock();//释放写锁,这个时候其他的读线程可以读取了。
				}				
			}
			return value;//在释放读锁之前返回,保证不会被修改。
		} finally {
			lock.readLock().unlock();
		}
	}
		
	   
}

 这样就会解决了,需要注意的是ReentranReadWriteLock的用法。读锁和写锁是可以互换的,读锁不可以升级为写锁,即如果已经上了读锁,必须解锁读锁才可以上写锁,否则会报错。但是写锁可以降低为读锁,所以如果已经上了写锁,再次上读锁是可以的。

至此,可以学好线程之间的互斥了,并且用synchronized  或者 lock来实现。

猜你喜欢

转载自suichangkele.iteye.com/blog/2264536