关于线程的两个类ThreadLocal和FutureTask。
一、关于FutureTask。
场景说明:用户注册后需要发送注册邮件和注册短信。传统的做法有两种:串行的方式和并行方式。
1、串行方式
将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务完成后再返回给客户。
用户--->注册信息写入数据库--->发送注册邮件--->发送注册短信--->返回给用户
2、并行方式
将注册信息写入数据库成功后,同时发送邮件和短信(启动两个FutureTask来执行)--->返回信息给用户
FutureTask相比Thread的优点就是当线程执行完的时候可以返回线程执行的结果。
用户--->注册信息写入数据库--->发送注册邮件&发送注册短信--->返回给用户
总结:
假设3个业务点每个使用50毫秒,不考虑网络等其他的开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。
public static void main(String[] args) throws Exception{ serial();//串行 parallel();//并行 } //串行执行 public static void serial() throws Exception{ logger.info("串行开始"); long startTime = System.nanoTime(); //1、注册信息写入数据库--50ms TimeUnit.MILLISECONDS.sleep(50); //2、发送短信--50ms logger.info("发送短信中");TimeUnit.MILLISECONDS.sleep(50); logger.info("发送邮件中");TimeUnit.MILLISECONDS.sleep(50); //4、将返回信息提供给用户 logger.info("短信发送的结果:"+true); logger.info("邮件发送的结果:"+true); logger.info("串行结束,耗时:"+(System.nanoTime()-startTime)/1000/1000+"毫秒!"); } //并行执行 public static void parallel() throws Exception{ logger.info("并行开始"); long startTime = System.nanoTime(); //1、注册信息写入数据库--50ms TimeUnit.MILLISECONDS.sleep(50); //2、发送短信,并返回发送短信的结果--50ms FutureTask<Boolean> messageTask=new FutureTask<Boolean>(new Callable<Boolean>() { @Override public Boolean call() throws Exception { logger.info("发送短信中"); TimeUnit.MILLISECONDS.sleep(50); return true; } }); new Thread(messageTask).start(); //3、发送注册邮件,并返回发送注册邮件的结果--50ms FutureTask<Boolean> emailTask=new FutureTask<Boolean>(new Callable<Boolean>() { @Override public Boolean call() throws Exception { logger.info("发送邮件中"); TimeUnit.MILLISECONDS.sleep(50); return true; } }); new Thread(emailTask).start(); //4、将返回信息提供给用户 logger.info("短信发送的结果:"+messageTask.get()); logger.info("邮件发送的结果:"+emailTask.get()); logger.info("并行结束,耗时:"+(System.nanoTime()-startTime)/1000/1000+"毫秒!"); }
程序执行结果:
2018.05.10 16:27:11.782 INFO ApplicationFutureTask - 串行开始 2018.05.10 16:27:11.886 INFO ApplicationFutureTask - 发送短信中 2018.05.10 16:27:11.936 INFO ApplicationFutureTask - 发送邮件中 2018.05.10 16:27:11.987 INFO ApplicationFutureTask - 短信发送的结果:true 2018.05.10 16:27:11.987 INFO ApplicationFutureTask - 邮件发送的结果:true 2018.05.10 16:27:11.987 INFO ApplicationFutureTask - 串行结束,耗时:198毫秒! 2018.05.10 16:27:11.987 INFO ApplicationFutureTask - 并行开始 2018.05.10 16:27:12.057 INFO ApplicationFutureTask - 发送短信中 2018.05.10 16:27:12.074 INFO ApplicationFutureTask - 发送邮件中 2018.05.10 16:27:12.109 INFO ApplicationFutureTask - 短信发送的结果:true 2018.05.10 16:27:12.125 INFO ApplicationFutureTask - 邮件发送的结果:true 2018.05.10 16:27:12.125 INFO ApplicationFutureTask - 并行结束,耗时:137毫秒!
二、关于ThreadLocal
ThreadLocal
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
//成员变量 将线程和连接绑定,保证事务能统一执行 ------Hibernate的数据库连接池就是将connection放进threadlocal实现的!!! private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); //获得当前连接 --默认线程里面取 public static Connection getConnection(){ Connection conn= threadLocal.get(); if(null==conn){ threadLocal.set(createConnection()); } return threadLocal.get(); } //私有方法 private static Connection createConnection(){ Connection conn=null; try { Class.forName(ConfigUtil.getProperty("mysql_local_driver")); conn = DriverManager.getConnection(ConfigUtil.getProperty("mysql_local_url"), ConfigUtil.getProperty("mysql_local_username"), ConfigUtil.getProperty("mysql_local_password")); }catch(Exception e){ //由于异常发生后不能处理,所以只能记录日志 e.printStackTrace(); } return conn; }
启动线程来通过ThreadLocal获取Connection。
//异步线程1-获取数据库连接,然后执行增删查改操作 FutureTask<Connection> f1=new FutureTask<Connection>(new Callable<Connection>() { @Override public Connection call() throws Exception { logger.info("--"); //线程内-处理业务逻辑1 logger.info("线程"+Thread.currentThread().getName()+"内-处理业务逻辑1->Connection:"+DbUtil.getConnection()); //线程内-处理业务逻辑2 logger.info("线程"+Thread.currentThread().getName()+"内-处理业务逻辑2->Connection:"+DbUtil.getConnection()); //线程内-处理业务逻辑3 logger.info("线程"+Thread.currentThread().getName()+"内-处理业务逻辑3->Connection:"+DbUtil.getConnection()); return DbUtil.getConnection(); } }); new Thread(f1).start(); f1.get(); //启动异步线程2-获取数据库连接,然后执行增删查改操作 FutureTask<Connection> f2=new FutureTask<Connection>(new Callable<Connection>() { @Override public Connection call() throws Exception { logger.info("--"); //线程内-处理业务逻辑1 logger.info("线程"+Thread.currentThread().getName()+"内-处理业务逻辑1->Connection:"+DbUtil.getConnection()); //线程内-处理业务逻辑2 logger.info("线程"+Thread.currentThread().getName()+"内-处理业务逻辑1->Connection:"+DbUtil.getConnection()); //线程内-处理业务逻辑3 logger.info("线程"+Thread.currentThread().getName()+"内-处理业务逻辑3->Connection:"+DbUtil.getConnection()); return DbUtil.getConnection(); } }); new Thread(f2).start(); f2.get();
打印日志:
2018.05.10 16:36:19.548 INFO TestThreadLocal - -- 2018.05.10 16:36:19.824 INFO TestThreadLocal - 线程Thread-0内-处理业务逻辑1->Connection:com.mysql.jdbc.JDBC4Connection@772b2185 2018.05.10 16:36:19.824 INFO TestThreadLocal - 线程Thread-0内-处理业务逻辑2->Connection:com.mysql.jdbc.JDBC4Connection@772b2185 2018.05.10 16:36:19.824 INFO TestThreadLocal - 线程Thread-0内-处理业务逻辑3->Connection:com.mysql.jdbc.JDBC4Connection@772b2185 2018.05.10 16:36:19.825 INFO TestThreadLocal - -- 2018.05.10 16:36:19.855 INFO TestThreadLocal - 线程Thread-1内-处理业务逻辑1->Connection:com.mysql.jdbc.JDBC4Connection@33ba57d3 2018.05.10 16:36:19.855 INFO TestThreadLocal - 线程Thread-1内-处理业务逻辑1->Connection:com.mysql.jdbc.JDBC4Connection@33ba57d3 2018.05.10 16:36:19.855 INFO TestThreadLocal - 线程Thread-1内-处理业务逻辑3->Connection:com.mysql.jdbc.JDBC4Connection@33ba57d3
可以看到,同一个线程获取到的是同一个Connection,不同的线程获取到的是不同的Connection。