原文:https://blog.csdn.net/songjinbin/article/details/19857567

在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,

所谓模板板式,就是在父类中定义算法的主要流程,而把一些个性化的步骤延迟到子类中去实现,父类始终控制着整个流程的主动权,子类只是辅助父类实现某些可定制的步骤。

我们用代码来说话吧: 
首先,父类要是个抽象类:

Java代码

public abstract class TemplatePattern {  //模板方法  public final void templateMethod(){  method1();  method2();//勾子方法  method3();//抽象方法  }  private void method1(){  System.out.println("父类实现业务逻辑");  }  public void method2(){  System.out.println("父类默认实现,子类可覆盖");  }  protected abstract void method3();//子类负责实现业务逻辑
}  

父类中有三个方法,分别是method1(),method2()和method3()。 
method1()是私有方法,有且只能由父类实现逻辑,由于方法是private的,所以只能父类调用。 
method2()是所谓的勾子方法。父类提供默认实现,如果子类觉得有必要定制,则可以覆盖父类的默认实现。 
method3()是子类必须实现的方法,即制定的步骤。 
由此可看出,算法的流程执行顺序是由父类掌控的,子类只能配合。

下面我们来写第一个子类: 
Java代码

public class TemplatePatternImpl extends TemplatePattern {  @Override  protected void method3() {  System.out.println("method3()在子类TemplatePatternImpl中实现了!!");  }  }  

这个子类只覆盖了必须覆盖的方法,我们来测试一下: 
Java代码

TemplatePattern t1 = new TemplatePatternImpl();
t1.templateMethod();  

在控制台中我们可以看到: 
Java代码  
父类实现业务逻辑  
父类默认实现,子类可覆盖  
method3()在子类TemplatePatternImpl中实现了!!

OK,我们来看看勾子方法的使用: 
定义第2个子类,实现勾子方法: 
Java代码

public class TemplatePatternImpl2 extends TemplatePattern {  @Override  protected void method3() {  System.out.println("method3()在子类TemplatePatternImpl2中实现了!!");  }  /* (non-Javadoc) * @see com.jak.pattern.template.example.TemplatePattern#method2() */  @Override  public void method2() {  System.out.println("子类TemplatePatternImpl2覆盖了父类的method2()方法!!");  }  }  

来测试一下: 
Java代码

TemplatePattern t2 = new TemplatePatternImpl2();
t2.templateMethod();  

我们看控制台: 
Java代码  
父类实现业务逻辑  
子类TemplatePatternImpl2覆盖了父类的method2()方法!!  
method3()在子类TemplatePatternImpl2中实现了!!

OK,经典的模板模式回顾完了(大家不要拍砖哦~~~~~~~~~~)

接下来,我们回到正题,自己模仿spring动手写一个基于模板模式和回调的jdbcTemplate。

回顾一下,spring为什么要封装JDBC API,对外提供jdbcTemplate呢(不要仍鸡蛋啊¥·%¥#%) 
话说SUN的JDBC API也算是经典了,曾经在某个年代折服了一批人。但随着历史的发展,纯粹的JDBC API已经过于底层,而且不易控制,由开发人员直接接触JDBC API,会造成不可预知的风险。还有,数据连接缓存池的发展,也不可能让开发人员去手工获取JDBC了。

好了,我们来看一段曾经堪称经典的JDBC API代码吧: 
Java代码

public List<User> query() {  List<User> userList = new ArrayList<User>();  String sql = "select * from User";  Connection con = null;  PreparedStatement pst = null;  ResultSet rs = null;  try {  con = HsqldbUtil.getConnection();  pst = con.prepareStatement(sql);  rs = pst.executeQuery();  User user = null;  while (rs.next()) {  user = new User();  user.setId(rs.getInt("id"));  user.setUserName(rs.getString("user_name"));  user.setBirth(rs.getDate("birth"));  user.setCreateDate(rs.getDate("create_date"));  userList.add(user);  }  } catch (SQLException e) {  e.printStackTrace();  }finally{  if(rs != null){  try {  rs.close();  } catch (SQLException e) {  e.printStackTrace();  }  }  try {  pst.close();  } catch (SQLException e) {  e.printStackTrace();  }  try {  if(!con.isClosed()){  try {  con.close();  } catch (SQLException e) {  e.printStackTrace();  }  }  } catch (SQLException e) {  e.printStackTrace();  }  }  return userList;
}  

上面的代码要若干年前可能是一段十分经典的,还可能被作为example被推广。但时过境迁,倘若哪位程序员现在再在自己的程序中出现以上代码,不是说明该公司的开发框架管理混乱,就说明这位程序员水平太“高”了。 
我们试想,一个简单的查询,就要做这么一大堆事情,而且还要处理异常,我们不防来梳理一下: 
1、获取connection 
2、获取statement 
3、获取resultset 
4、遍历resultset并封装成集合 
5、依次关闭connection,statement,resultset,而且还要考虑各种异常 
6、..... 
啊~~~~ 我快要晕了,在面向对象编程的年代里,这样的代码简直不能上人容忍。试想,上面我们只是做了一张表的查询,如果我们要做第2张表,第3张表呢,又是一堆重复的代码: 
1、获取connection 
2、获取statement 
3、获取resultset 
4、遍历resultset并封装成集合 
5、依次关闭connection,statement,resultset,而且还要考虑各种异常 
6、.....

这时候,使用模板模式的时机到了!!!

通过观察我们发现上面步骤中大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是可定制的,因为每张表都映射不同的java bean。这部分代码是没有办法复用的,只能定制。那就让我们用一个抽象的父类把它们封装一下吧: 
Java代码

public abstract class JdbcTemplate {  //template method  public final Object execute(String sql) throws SQLException{  Connection con = HsqldbUtil.getConnection();  Statement stmt = null;  try {  stmt = con.createStatement();  ResultSet rs = stmt.executeQuery(sql);  Object result = doInStatement(rs);//abstract method   return result;  }  catch (SQLException ex) {  ex.printStackTrace();  throw ex;  }  finally {  try {  stmt.close();  } catch (SQLException e) {  e.printStackTrace();  }  try {  if(!con.isClosed()){  try {  con.close();  } catch (SQLException e) {  e.printStackTrace();  }  }  } catch (SQLException e) {  e.printStackTrace();  }  }  }  //implements in subclass  protected abstract Object doInStatement(ResultSet rs);
}  

在上面这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,由子类负责实现。 
好,我们来定义一个子类,并继承上面的父类: 
Java代码

public class JdbcTemplateUserImpl extends JdbcTemplate {  @Override  protected Object doInStatement(ResultSet rs) {  List<User> userList = new ArrayList<User>();  try {  User user = null;  while (rs.next()) {  user = new User();  user.setId(rs.getInt("id"));  user.setUserName(rs.getString("user_name"));  user.setBirth(rs.getDate("birth"));  user.setCreateDate(rs.getDate("create_date"));  userList.add(user);  }  return userList;  } catch (SQLException e) {  e.printStackTrace();  return null;  }  }  }  

由代码可见,我们在doInStatement()方法中,对ResultSet进行了遍历,最后并返回。 
有人可能要问:我如何获取ResultSet 并传给doInStatement()方法啊??呵呵,问这个问题的大多是新手。因为此方法不是由子类调用的,而是由父类调用,并把ResultSet传递给子类的。我们来看一下测试代码: 
Java代码

String sql = "select * from User";
JdbcTemplate jt = new JdbcTemplateUserImpl();
List<User> userList = (List<User>) jt.execute(sql); 

就是这么简单!!

文章至此仿佛告一段落,莫急!不防让我们更深入一些...

试想,如果我每次用jdbcTemplate时,都要继承一下上面的父类,是不是有些不方面呢? 
那就让我们甩掉abstract这顶帽子吧,这时,就该callback(回调)上场了

所谓回调,就是方法参数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。

那我们就来把上面的代码改造一下,改用回调实现吧:

首先,我们来定义一个回调接口: 
Java代码

public interface StatementCallback {  Object doInStatement(Statement stmt) throws SQLException;
}  

这时候,我们就要方法的签名改一下了: 
Java代码

private final Object execute(StatementCallback action) throws SQLException  

里面的获取数据方式也要做如下修改: 
Java代码

Object result = action.doInStatement(stmt);//abstract method   

为了看着顺眼,我们来给他封装一层吧: 
Java代码

public Object query(StatementCallback stmt) throws SQLException{  return execute(stmt);
}  

OK,大功告成! 
我们来写一个测试类Test.java测试一下吧: 
这时候,访问有两种方式,一种是内部类的方式,一种是匿名方式。

先来看看内部类的方式: 
Java代码

//内部类方式  public Object query(final String sql) throws SQLException {  class QueryStatementCallback implements StatementCallback {  public Object doInStatement(Statement stmt) throws SQLException {  ResultSet rs = stmt.executeQuery(sql);  List<User> userList = new ArrayList<User>();  User user = null;  while (rs.next()) {  user = new User();  user.setId(rs.getInt("id"));  user.setUserName(rs.getString("user_name"));  user.setBirth(rs.getDate("birth"));  user.setCreateDate(rs.getDate("create_date"));  userList.add(user);  }  return userList;  }  }  JdbcTemplate jt = new JdbcTemplate();  return jt.query(new QueryStatementCallback());  }  

在调用jdbcTemplate.query()方法时,传一个StatementCallBack()的实例过去,也就是我们的内部类。

再来看看匿名方式: 
Java代码

//匿名类方式  public Object query2(final String sql) throws Exception{  JdbcTemplate jt = new JdbcTemplate();  return jt.query(new StatementCallback() {  public Object doInStatement(Statement stmt) throws SQLException {  ResultSet rs = stmt.executeQuery(sql);  List<User> userList = new ArrayList<User>();  User user = null;  while (rs.next()) {  user = new User();  user.setId(rs.getInt("id"));  user.setUserName(rs.getString("user_name"));  user.setBirth(rs.getDate("birth"));  user.setCreateDate(rs.getDate("create_date"));  userList.add(user);  }  return userList;  }  });  }  

相比之下,这种方法更为简洁。 
为什么spring不用传统的模板方法,而加之以Callback进行配合呢? 
试想,如果父类中有10个抽象方法,而继承它的所有子类则要将这10个抽象方法全部实现,子类显得非常臃肿。而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了。

最后的源码为:

package com.jak.pattern.template.callbacktemplate;
import java.sql.SQLException;
import java.sql.Statement;public interface StatementCallback {Object doInStatement(Statement stmt) throws SQLException;
}
public class JdbcTemplate {//template methodprivate final Object execute(StatementCallback action) throws SQLException{    Connection con = HsqldbUtil.getConnection();Statement stmt = null;try {stmt = con.createStatement();Object result = action.doInStatement(stmt);//abstract method return result;}catch (SQLException ex) {ex.printStackTrace();throw ex;}finally {try {stmt.close();} catch (SQLException e) {e.printStackTrace();}try {if(!con.isClosed()){try {con.close();} catch (SQLException e) {e.printStackTrace();}}} catch (SQLException e) {e.printStackTrace();}}
}public Object query(StatementCallback stmt) throws SQLException{return execute(stmt);}
}

//调用测试类

public class Test {//内部类方式
public Object query(final String sql) throws SQLException {
class QueryStatementCallback implements StatementCallback {public Object doInStatement(Statement stmt) throws SQLException {ResultSet rs = stmt.executeQuery(sql);List<User> userList = new ArrayList<User>();User user = null;while (rs.next()) {user = new User();user.setId(rs.getInt("id"));user.setUserName(rs.getString("user_name"));user.setBirth(rs.getDate("birth"));user.setCreateDate(rs.getDate("create_date"));userList.add(user);}return userList;}}JdbcTemplate jt = new JdbcTemplate();return jt.query(new QueryStatementCallback());
}//匿名类方式
public Object query2(final String sql) throws Exception{JdbcTemplate jt = new JdbcTemplate();return jt.query(new StatementCallback() {public Object doInStatement(Statement stmt) throws SQLException {ResultSet rs = stmt.executeQuery(sql);List<User> userList = new ArrayList<User>();User user = null;while (rs.next()) {user = new User();user.setId(rs.getInt("id"));user.setUserName(rs.getString("user_name"));user.setBirth(rs.getDate("birth"));user.setCreateDate(rs.getDate("create_date"));userList.add(user);}return userList;}
});}
public static void main(String[] args) throws Exception {String sql = "select * from User";Test t = new Test();List<User> userList = (List<User>) t.query(sql);List<User> userList2 = (List<User>) t.query2(sql);System.out.println(userList);System.out.println(userList2);}
}                

言归正传,有了上面的基础后,我们正式开始阅读源码:

下面几个接口是对变化的部分进行建模

接口:创建PreparedStatement。根据Connection来创建PreparedStatement。

public interface PreparedStatementCreator {  PreparedStatement createPreparedStatement (Connection conn)  throws SQLException;  }  使用方法就是:PreparedStatementCreator psc = new PreparedStatementCreator() {  public PreparedStatement createPreparedStatement (Connection conn)  throws SQLException {  PreparedStatement ps = conn. prepareStatement (  "SELECT seat_id AS id FROM available_seats WHERE " +  "performance_id = ? AND price_band_id = ?");  ps.setInt(1, performanceId);  ps.setInt(2, seatType);  return ps;  }  };  

给PreparedStatement设置参数。是对PreparedStatmentCreator的设置ps值的一个补充。

public interface PreparedStatementSetter {  void setValues(PreparedStatement ps) throws SQLException;
}  

对ResultSet进行处理。还有具体的子类。

public interface RowCallbackHandler {  void processRow(ResultSet rs) throws SQLException;  }  

使用方式:

 RowCallbackHandler rch = new RowCallbackHandler() {  public void processRow(ResultSet rs) throws SQLException {  int seatId = rs.getInt(1) ;  list.add(new Integer (seatId) );//典型的inner class的应用,list为外部类的变量。    }  };  

和上面的RowCallbackHandler类似。

public interface ResultSetExtractor {    Object extractData(ResultSet rs) throws SQLException, DataAccessException;
}   

下面是JdbcTemplate中提供的模板方法。该方法完成对数据库的查询:)。

这个execute()方法非常关键。

public Object query(  PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)  throws DataAccessException {  Assert.notNull(rse, "ResultSetExtractor must not be null");  if (logger.isDebugEnabled()) {  String sql = getSql(psc); //取得不变的SQL部分。 logger.debug("Executing SQL query" + (sql != null ? " [" + sql  + "]" : ""));  }  return execute(psc, new PreparedStatementCallback() {  public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {  ResultSet rs = null;  try {  if (pss != null) {  pss.setValues(ps);//就是给ps来设置参数用的。ps.setInt(1, 0);  }  rs = ps.executeQuery();//执行查询  ResultSet rsToUse = rs;  if (nativeJdbcExtractor != null) {  rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);  }  return rse.extractData(rsToUse); // ResultSetExtractor从ResultSet中将值取出List。  }  finally {  //最后的善后工作还是需要做好的:) rs.close(),把ps的相关参数清除掉。  JdbcUtils.closeResultSet(rs);  if (pss instanceof ParameterDisposer) {  ((ParameterDisposer) pss).cleanupParameters();  }  }  }  });  }  

看看execute()方法吧。
java 代码

public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)  throws DataAccessException {  Assert.notNull(psc, "PreparedStatementCreator must not be null");  Assert.notNull(action, "Callback object must not be null");  //取得数据库的连接  Connection con = DataSourceUtils.getConnection(getDataSource());  PreparedStatement ps = null;  try {  Connection conToUse = con;  if (this.nativeJdbcExtractor != null &&  this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {  conToUse = this.nativeJdbcExtractor.getNativeConnection(con);  }  //创建PreparedStatement  ps = psc.createPreparedStatement(conToUse);  applyStatementSettings(ps);//这个方法是设置ps的一些属性,我平时不用,Spring框架倒是考虑得相当全的说。  PreparedStatement psToUse = ps;  if (this.nativeJdbcExtractor != null) {  psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);  }  //调用Callback来完成PreparedStatement的设值。就是调用上面的doInPreparedStatement来使用ps。  Object result = action.doInPreparedStatement(psToUse);  SQLWarning warning = ps.getWarnings();  throwExceptionOnWarningIfNotIgnoringWarnings(warning);  return result;  }  //如果有错误的话,那么就开始ps.close(), connection.close();  catch (SQLException ex) {  // Release Connection early, to avoid potential connection pool deadlock  // in the case when the exception translator hasn't been initialized yet.  if (psc instanceof ParameterDisposer) {  ((ParameterDisposer) psc).cleanupParameters();  }  String sql = getSql(psc);  psc = null;  JdbcUtils.closeStatement(ps);  //就是ps.close();ps = null;  DataSourceUtils.releaseConnection(con, getDataSource()); /  con = null;  throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);  }  

//不管怎么样,ps.close(), Connection.close()吧,当然这里是releaseConnection。在我的程序中,Connection只有一个,没有ConnectionPool,当然不会去close Connection。一般来讲,如果没有Connection的线程池的话,我们肯定也不会经常的关闭Connection,得到Connection。毕竟这个东西非常耗费资源。

  finally {  if (psc instanceof ParameterDisposer) {  ((ParameterDisposer) psc).cleanupParameters();  }  JdbcUtils.closeStatement(ps);  DataSourceUtils.releaseConnection(con, getDataSource());  }  }  

JdbcTemplate完成了负责的操作,客户只需要调用query()就可以完成查询操作了。当然,JdbcTemplate会实现很多带其它参数的方法,以方便你的使用。Template设计模式被发扬广大了。
DataSourceUtils:这个专门用于管理数据库Connection的类。

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {  try {  return doGetConnection(dataSource);  ~~~~~~ //这个方法很舒服,Spring Framework中到处有这样的方法。为什么要委派到这个动作方法?  }  catch (SQLException ex) {  throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);  }  }  

这里的doGetConnection就稍微复杂一点了。但是如果没有事务同步管理器的话,那就比较简单。

只是在Connection上多了一个ConnecionHolder类用于持有Connection,实现ConnectionPool的一点小功能。

public static Connection doGetConnection(DataSource dataSource) throws SQLException {  Assert.notNull(dataSource, "No DataSource specified");  ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  ~~~~~//Connection的持有器。通过持有器得到Connection。if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {  conHolder.requested();  if (!conHolder.hasConnection()) {  logger.debug("Fetching resumed JDBC Connection from DataSource");  conHolder.setConnection(dataSource.getConnection());  }  return conHolder.getConnection();  }  // Else we either got no holder or an empty thread-bound holder here.  logger.debug("Fetching JDBC Connection from DataSource");  Connection con = dataSource.getConnection();   ……return con;  }  ConnectionHolder:Connection的持有器。通过ConnectionHandler来完成对Connection的操作:) 典型的委派。public class ConnectionHolder extends ResourceHolderSupport {  private Connection currentConnection; //当前的Connection  private ConnectionHandle connectionHandle;  //Connection的处理器,因此可以通过该类完成对connection的管理。  public ConnectionHolder(Connection connection) {  this.connectionHandle = new SimpleConnectionHandle(connection);  }  public ConnectionHolder(ConnectionHandle connectionHandle) {  Assert.notNull(connectionHandle, "ConnectionHandle must not be null");  this.connectionHandle = connectionHandle;  }  public Connection getConnection() {  Assert.notNull(this.connectionHandle, "Active Connection is required");  if (this.currentConnection == null) {  this.currentConnection = this.connectionHandle.getConnection();  }  return this.currentConnection;  }  public void released() {  super.released();  if (this.currentConnection != null) {  this.connectionHandle.releaseConnection(this.currentConnection);  this.currentConnection = null;  }  }  connectionHandle 的接口太纯粹了。但是我觉得这个设计太过于细致了:) public interface ConnectionHandle {  /** * Fetch the JDBC Connection that this handle refers to. */  Connection getConnection();  /** * Release the JDBC Connection that this handle refers to. * @param con the JDBC Connection to release */  void releaseConnection(Connection con);  }  

最后看一下SimpleConnectionHandle,这个ConnectionHandle的简单实现类。就只有一个Connection可管理。如果有多个Connection可管理的话,这里就是ConnectionPool了:)

java 代码

public class SimpleConnectionHandle implements ConnectionHandle {  private final Connection connection;  /** * Create a new SimpleConnectionHandle for the given Connection. * @param connection the JDBC Connection */  public SimpleConnectionHandle(Connection connection) {  Assert.notNull(connection, "Connection must not be null");  this.connection = connection;  }  /** * Return the specified Connection as-is. */  public Connection getConnection() {  return connection;  }  /** * This implementation is empty, as we're using a standard * Connection handle that does not have to be released. */  public void releaseConnection(Connection con) {  }  public String toString() {  return "SimpleConnectionHandle: " + this.connection;  }  }  

spring源码解读之 JdbcTemplate源码相关推荐

  1. 源码解读_Go Map源码解读之Map迭代

    点击上方蓝色"后端开发杂谈"关注我们, 专注于后端日常开发技术分享 map 迭代 本文主要是针对map迭代部分的源码分析, 可能篇幅有些过长,且全部是代码, 请耐心阅读. 源码位置 ...

  2. jpcsp源码解读12:本地码管理器与Compiler.xml

    jpcsp这个模拟器的优化手段实在让人汗颜. 之前说过,他把系统调用功能全部用本地码实现了,也就是在软件需要的时候,调用java语言的实现,而不是跳转到内存中相应位置去解释执行,或者对系统调用代码做动 ...

  3. 【Vue源码解读】万行源码详细解读

    前言 Vue2 的源码2年前粗略的看过一遍,重点在对响应式属性.对象监听.watch.computed.生命周期等内容的理解,但好记忆不如烂笔头,当初没有做笔记,现在重读一遍,针对重点内容详细解读并记 ...

  4. jpcsp源码解读之一:源码的获取与编译,以及psp详尽硬件信息文档

    是我心血来潮的想法,要解读一下psp模拟器的源码,并添加详尽的中文注释.这个博客则成为文档. 本文面向java语言零基础的程序员,因为我本人的java基础就是零. 水平所限,疏漏错误之处欢迎指正.也欢 ...

  5. java io源码解读_Java IO源码分析(五)——CharArrayReader 和 CharArrayWriter

    简介 CharArrayReader 是字符数组的输入流,它和我们之前讲的ByteArrayInputStream十分类似,顾名思义,区别在于一个用于字符数组,一个用于字节数组,在Java中字符是16 ...

  6. 【源码解读】Screencap源码分析-基础篇

    本文期望达到的目的: 了解screencap使用 了解screencap实现基础原理 为后续screencap源码修改和其他应用做准备 源码位置: android4.0之后内置了截图工具screenc ...

  7. Spring5源码 - 05 invokeBeanFactoryPostProcessors 源码解读_3细说invokeBeanDefinitionRegistryPostProcessors

    文章目录 Pre 细说invokeBeanDefinitionRegistryPostProcessors 流程图 源码分析 解析配置类 parser.parse(candidates) 配置类注册到 ...

  8. Spring5源码 - 05 invokeBeanFactoryPostProcessors 源码解读_2

    文章目录 Pre 源码解读 总体流程 源码分析 细节解析 [初始化对应的集合 & 遍历用户自己手动添加的后置处理器] [调用实现了PriorityOrdered接口的BeanDefinitio ...

  9. Future源码解读

    Future源码解读 〇.[源码版本] jdk 1.8 一.Future概述 [举例1]示例代码 [举例2]示例代码 内存一致性影响 二.Future接口的方法 cancel方法 isCancelle ...

最新文章

  1. jenkins 命令找不到
  2. Jenkins CLI命令行工具,助你轻松管理 Jenkins
  3. 听说”双11”是这么解决线上bug的
  4. 231. 2的幂 golang
  5. Winform控件WebBrowser与JS脚本交互
  6. JavaScript学习系列3 -- JavaScript arguments对象学习
  7. 论文浅尝 | 利用机器翻译和多任务学习进行复杂的知识图谱问答
  8. python 中 yield 的使用
  9. springcloud config服务端配置(二)
  10. laravel leftjoin 右侧取最新一条_高铁规划:湖南至广西将增添一条高铁,填补中西部地区铁路网空白...
  11. cada0图纸框_CAD图框尺寸大全
  12. 全国计算机等级二级ACCESS数据库程序设计(更新完毕)
  13. 【10个精品网站】找素材、找图片、PDF在线工具、免费图片降噪修复、免费可商用字体、PPT模板
  14. 「To B端增长黑客」 获客矩阵
  15. 实训日记(二)——分镜
  16. linux fdisk 挂盘
  17. 世界杯要来了,AI预测冠军哪家强?
  18. 火车票抢票API 根据乘客的车次与座席要求快速订票出票 1
  19. ubuntu16.04截图工具Shutter安装,设置快捷键
  20. 多无人机辅助移动边缘计算中的任务卸载和轨迹优化

热门文章

  1. java web 嵌套播放器_请教一下tableau如何嵌套进自己开发的javaweb 项目中,谢谢了...
  2. Android程序等待1,android – Espresso如何等待一段时间(1小时)?
  3. java 如何循环执行一个对象_一个Java对象到底有多大?
  4. c语言全国二级考试全对,全国计算机c语言二级考试通用复习资料.doc
  5. 如何反映两条曲线的拟合精度_【隆旅干货分享】差压传感器的应用及精度特性分析...
  6. 计算机组装与维护 授课计划,计算机课程教学计划
  7. UriComponentsBuilder 拼接URL、解析URL
  8. 【weblogic】部署jfinal编写的应用
  9. linux mq发送测试消息,WebSphere MQ测试常用指令
  10. google套件_Google 推出 3 款语音识别应用,想用 AI 帮语言障碍者说话