`

实践缩小Java synchronized 粒度

 
阅读更多

项目需求:

产品需要监视不同种类的多个数据库,例如:多个mysql库,多个oracle库,多个sybase库,多个msserver库等等,连接池需要根据客户数据库种类和数量的实际情况进行动态创建。

 

难点:

1.每个库创建一个连接池,不能重复创建。

2.每个连接池维护自身的线程安全性,访问每个连接池的线程单独排队,相互之间不能影响。

 

实现思路:

将每个库的连接池实例保存到一个static Map中,key为库路径,value为连接池对象。如果对象已经存在,则不再创建,直接获取。

 

Map的read-write的组合操作,需要线程安全控制。创建连接池对象时,需要先初始化pool对象,这是一个耗时的操作,这时引申出一个问题,以什么作为锁来较好地控制对Map的操作?

 

如果是全局变量锁,则会导致不同的数据库都会在这里阻塞,如果一台数据库宕掉,其他数据库也会大量阻塞。如果用库地址作为独占锁,则可以实现不同的数据库各自排队,库和库之间没有影响。因为库路径是String对象,为此也专门测试了一下Java String对象是否可以做锁变量。结论是Java String对象不能作为Synchronized()的锁变量。但是Object对象可以作为锁,很奇怪!

 

不能在全局变量上加锁的主要原因是初始化连接池对象时间较长,线程排队会比较严重,而且多数据库间互相影响。所以考虑添加一个锁集合,以数据库路径为key,以Object对象为value,这样可以较快的获取Object对象,并且可以用这个对象代替key(数据库路径)作为synchrozied的锁变量。上代码:

 

 

import java.sql.Connection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.mchange.v2.c3p0.ComboPooledDataSource;


public class XJDBCPool {
    public static enum DBTYPE {
        Oracle, Sybase, Mysql, Mssql, DB2, Informix
    };
    
    //locks map
    public final static Map<String, Object> locks = new ConcurrentHashMap<String, Object>();
    //pool map
    public final static Map<String, C3p0Pool> jdbcPoolC3p0s = new ConcurrentHashMap<String, C3p0Pool>();
    
    
    private static XConnection getXCon(String key, String type) {
        Logger.logDebug("~~~~ XJDBCPool getXCon key : "+ key);
        
        int timeoutIndex = key.lastIndexOf("#");
        String timeStr = key.substring(timeoutIndex + 1) == null ? "" : key.substring(timeoutIndex + 1);
        int timeout = Integer.valueOf(timeStr);
        key = key.substring(0, timeoutIndex);
        
        C3p0Pool c3p0Pool = null;
        if(type.equals(JDBC_TYPE_C3P0)){
            
            Object obj = null;
            synchronized(locks){
                obj = locks.get(key);
                if(obj == null){
                    obj = new Object();
                    locks.put(key, obj);
                }
            }
            
            Logger.logDebug("~~~~ ["+ Thread.currentThread().getName() +"] wait lock : "+ obj +" key : "+ key);
            synchronized(obj){
            Logger.logDebug("~~~~ ["+ Thread.currentThread().getName() +"] get lock : "+ obj +" key : "+ key);
                
                c3p0Pool = jdbcPoolC3p0s.get(key);
                if (c3p0Pool == null){
		    //init pool
                    c3p0Pool = new C3p0Pool(key, timeout);
                    jdbcPoolC3p0s.put(key, c3p0Pool);
                    
                } else{
                    //if timeout was modified, restart pool.
                    int timeoutOld = 0;
                    ComboPooledDataSource cpds = c3p0Pool.getCpds();
                    if(cpds != null){
                        timeoutOld = cpds.getCheckoutTimeout();
                        if(timeout * 1000 != timeoutOld){
                            c3p0Pool.release();
                            cpds = c3p0Pool.init(key, timeout);
                            jdbcPoolC3p0s.put(key, c3p0Pool);
                        }
                    }
                }
            }
            Logger.logDebug("~~~~ ["+ Thread.currentThread().getName() +"] release lock : "+ obj +" key : "+ key);
            
        }
        
        return c3p0Pool.getConnection();
    }
    
    ....

}

 

 

“String对象不能作为synchrozied的锁变量”

测试程序如下,控制台输出大家相信都能看得懂,就不再做解释。

 

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;


public class TestSynchronizedStr {
	public static Logger logger = Logger.getLogger(LoggerManager.PLATFORM);
	public final static Map<String, Object> locks = new ConcurrentHashMap<String, Object>();
	private final static Map<String, Object> jdbcPoolC3p0s = new ConcurrentHashMap<String, Object>();
		
	/**
	 * test ConcurrentHashMap
	 */
	public static void testConcurrentMap(String key){
		Object obj = null;
        synchronized(locks){
            obj = locks.get(key);
            if(obj == null){
                obj = new Object();
                locks.put(key, obj);
            }
        }
		
		logger.debug("~~~~ ["+ Thread.currentThread().getName() +"] wait lock : "+ key);
		synchronized(obj){
			logger.debug("~~~~ ["+ Thread.currentThread().getName() +"] get lock : "+ key);			
		
	        try{
				Thread.sleep(10000L);
			} catch(Exception e){
				e.printStackTrace();
			}
	        logger.debug("~~~~ ["+ Thread.currentThread().getName() +"] release lock : "+ key);
		}
	}
	
	
	public static void startThreads(final String i) {		
		Thread[] threads = new Thread[10];
		
		for(int j = 0; j < threads.length; j++){
			threads[j] = new Thread(){
				public void run(){
					logger.debug("\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
					logger.debug("**** start ["+ Thread.currentThread().getName() +"]");
					
					testConcurrentMap(i);
				}
			};
			
			threads[j].start();
		}
	}
	
	public static void main(String[] args) {
		for (int i = 0; i < 50; i++) {
			Random random = new Random();
			int randNum = Math.abs(random.nextInt())%3;
			startThreads(randNum +"");
		}
	}
}

 

 

 

0
0
分享到:
评论
2 楼 liudajiang 2014-10-16  
呵呵
            startThreads(randNum +""); 
每次传进去的都是不同的string对象(+操作被编译为stringbuiler.append(str).tostring()),当然锁不住,用1楼的intern()返回常量引用就解决了
1 楼 laser_mesty 2014-07-04  
String 对象是可以做为多变量的。例子显示 threand1-10获取所变量s=1,thread11-20 获取String变量 s1=1时,各自获取各自锁变量,当String.intern()之后,thread1-20获取相同的锁变量 值1

相关推荐

Global site tag (gtag.js) - Google Analytics