Jedis 與 MySQL的連線執行緒安全問題

wangjuntytl發表於2016-05-09

Jedis的連線是非執行緒安全的

下面是set命令的執行過程,簡單分為兩個過程,客戶端向服務端傳送資料,服務端向客戶端返回資料,從下面的程式碼來看:從建立連線到執行命令是沒有進行任何併發同步的控制

public String set(final String key, String value) {
  checkIsInMulti();
  // 執行set命令,其實是傳送set命令和其引數到server端,實際是呼叫下面的SendCommond(…)方法,傳送資料
  client.set(key, value);
  // 等待伺服器響應
  return client.getStatusCodeReply();
}

set 命令的資料傳送過程

public static void sendCommand(final RedisOutputStream os, final Command command,
    final byte[]... args) {
  sendCommand(os, command.raw, args);
}

private static void sendCommand(final RedisOutputStream os, final byte[] command,
    final byte[]... args) {
  try {
    os.write(ASTERISK_BYTE);
    os.writeIntCrLf(args.length + 1);
    os.write(DOLLAR_BYTE);
    os.writeIntCrLf(command.length);
    os.write(command);
    os.writeCrLf();

    for (final byte[] arg : args) {
      os.write(DOLLAR_BYTE);
      os.writeIntCrLf(arg.length);
      os.write(arg);
      os.writeCrLf();
    }
  } catch (IOException e) {
    throw new JedisConnectionException(e);
  }
}

set命令接收服務端響應過程

private static Object process(final RedisInputStream is) {

  final byte b = is.readByte();
  if (b == PLUS_BYTE) {
    return processStatusCodeReply(is);
  } else if (b == DOLLAR_BYTE) {
    return processBulkReply(is);
  } else if (b == ASTERISK_BYTE) {
    return processMultiBulkReply(is);
  } else if (b == COLON_BYTE) {
    return processInteger(is);
  } else if (b == MINUS_BYTE) {
    processError(is);
    return null;
  } else {
    throw new JedisConnectionException("Unknown reply: " + (char) b);
  }
}

JedisPool是執行緒安全的

Jedis客戶端支援多執行緒下併發執行時通過JedisPool實現的,藉助於commong-pool2實現執行緒池,它會為每一個請求分配一個Jedis連線,在請求範圍內使用完畢後記得歸還連線到連線池中,所以在實際運用中,注意不要把一個Jedis例項在多個執行緒下併發使用,用完後要記得歸還到連線池中

MySQL的連線是執行緒安全的

在一些簡單情況下,我是不用DataSource的,一般都會採用單例的方式建立MySQL的連線,剛開始我也不曉得這樣寫,看別人這樣寫也就跟著這樣寫了,突然有一天我懷疑這樣的寫發是否可選,一個MySQL的Connection是否在多個執行緒下併發操作是否安全。

在JDK中定義java.sql.Connection只是一個介面,也並沒有寫明它的執行緒安全問題。查閱資料得知,它的執行緒安全性由對應的驅動實現:

java.sql.Connection is an interface. So, it all depends on the driver`s implementation, but in general you should avoid sharing the same connection between different threads and use connection pools. Also it is also advised to have number of connections in the pool higher than number of worker threads.

這是MySQL的驅動實現,可以看到它在執行時,是採用排它鎖來保證連線的在併發環境下的同步。

public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    // 在使用連線過程中是採用排它鎖的方式
    synchronized(this.getConnectionMutex()) {
        this.checkClosed();
        Object pStmt = null;
        boolean canServerPrepare = true;
        String nativeSql = this.getProcessEscapeCodesForPrepStmts()?this.nativeSQL(sql):sql;
        if(this.useServerPreparedStmts && this.getEmulateUnsupportedPstmts()) {
            canServerPrepare = this.canHandleAsServerPreparedStatement(nativeSql);
        }
     ...
 ...

一般情況下,為了提高併發性,建議使用池技術來解決單連結的侷限性,比如常用的一些資料來源:C3P0等

相關文章