008-ThreadLocal
一、基本用法
ThreadLocal是一个容器,用于存放线程的局部变量。如果ThreadLocalVariable(线程局部变量)更加好理解。
在Jdk 1.2 java.lang.ThreadLocal开始使用,他是为解决多线程并发设计的.
示例序列号生成,保证每个线程生成唯一序列号
编写Sequence接口
package com.lhx.chapter4.threadlocaltest; public interface Sequence { int getNumber(); }
编写线程类
package com.lhx.chapter4.threadlocaltest; public class ClientThread extends Thread { private Sequence sequence; public ClientThread(Sequence sequence){ this.sequence=sequence; } @Override public void run() { for(int i=0;i<3;i++){ System.out.println(Thread.currentThread().getName()+"=>"+sequence.getNumber()); } } }
实现方式一
package com.lhx.chapter4.threadlocaltest; public class SequenceA implements Sequence { private static int number = 0; @Override public int getNumber() { number=number+1; return number; } public static void main(String[] args) { Sequence sequence = new SequenceA(); ClientThread clientThread1 = new ClientThread(sequence); ClientThread clientThread2 = new ClientThread(sequence); ClientThread clientThread3 = new ClientThread(sequence); clientThread1.start(); clientThread2.start(); clientThread3.start(); } }
Thread-0=>1 Thread-1=>2 Thread-0=>3 Thread-1=>4 Thread-0=>5 Thread-1=>6 Thread-2=>7 Thread-2=>8 Thread-2=>9
由输出可以线程间共享一个Static变量,无法保证线程安全。
示例2,
package com.lhx.chapter4.threadlocaltest; public class SequenceB implements Sequence { private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 0; } }; @Override public int getNumber() { numberContainer.set(numberContainer.get()+1); return numberContainer.get(); } public static void main(String[] args) { Sequence sequence = new SequenceB(); ClientThread clientThread1 = new ClientThread(sequence); ClientThread clientThread2 = new ClientThread(sequence); ClientThread clientThread3 = new ClientThread(sequence); clientThread1.start(); clientThread2.start(); clientThread3.start(); } }
输出值:
Thread-0=>1 Thread-0=>2 Thread-0=>3 Thread-1=>1 Thread-1=>2 Thread-1=>3 Thread-2=>1 Thread-2=>2 Thread-2=>3
可以看出每个线程变量独立开来,也就是说ThreadLocal为每个线程提供了一个副本。
ThreadLocal的API,
public void set(T value)将值放入线程局部变量中
public T get() 从线程局部变量中获取值
public void remove()从线程局部变量中移除
protected T initialValue()返回线程局部变量的初始值(默认为null),必须默认实现。
自定义实现ThreadLocal
package com.lhx.chapter4.threadlocaltest; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class MyThreadLocal<T> { private Map<Thread,T> container=Collections.synchronizedMap(new HashMap<Thread,T>()); public void set(T value){ container.put(Thread.currentThread(),value); } public T get(){ Thread thread = Thread.currentThread(); T value = container.get(thread); if(value==null&&!container.containsKey(thread)){ value=initialValue(); container.put(thread,value); } return value; } public void remove(){ container.remove(Thread.currentThread()); } protected T initialValue(){ return null; } }
用法一致
二、使用案例
通过ThreadLocal存放Jdbc Connection,以达到事务控制能力。
示例,修改价格,记录日志
update product set price=? where id=?
insert into log(crerated,description)values(?,?)
编写工具类DBUtil
package com.lhx.test.dbutil1; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String driver = "com.mysql.jdbc.Driver"; private static final String url = "jdbc:mysql://localhost:3306/demo"; private static final String username = "root"; private static final String password = "root"; private static Connection conn = null; public static Connection getConnection() { try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); } catch (Exception e) { e.printStackTrace(); } return conn; } public static void colseConnection() { try { if(conn!=null){ conn.close(); } } catch (Exception e) { e.printStackTrace(); } } }
编写ProductService
package com.lhx.test.dbutil1; public interface ProductService { void updateProductPrice(long productId,int price); }
编写ProductService实现类编写ProductServiceImpl
package com.lhx.test.dbutil1; import java.sql.Connection; import java.sql.PreparedStatement; import java.text.SimpleDateFormat; import java.util.Date; public class ProductServiceImpl implements ProductService { private static final String Update_Product_sql = "update product set price=? where id=?"; private static final String Insert_Log_sql = "insert into log(crerated,description)values(?,?)"; @Override public void updateProductPrice(long productId, int price) { try { Connection connection = DBUtil.getConnection(); connection.setAutoCommit(false); updateProduct(connection,Update_Product_sql,productId,price); insertLog(connection,Update_Product_sql,"Create Product"); connection.commit(); } catch (Exception e) { e.printStackTrace(); }finally { DBUtil.colseConnection(); } } private void updateProduct(Connection connection,String sql,long productId,int price) throws Exception{ PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setInt(1,price); pstmt.setLong(2,productId); int rows = pstmt.executeUpdate(); if(rows!=0){ System.out.println("update product success"); } } private void insertLog(Connection connection,String sql,String msg) throws Exception{ PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date())); pstmt.setString(2,msg); int rows = pstmt.executeUpdate(); if(rows!=0){ System.out.println("insert log success"); } } }
个人测试
public static void main(String[] args) { ProductService productService = new ProductServiceImpl(); productService.updateProductPrice(1,3000); }
并发测试
package com.lhx.test.dbutil1; import com.lhx.test.dbutil1.ProductService; public class ClientThread extends Thread { private ProductService productService; public ClientThread(ProductService productService) { this.productService = productService; } @Override public void run() { System.out.println(Thread.currentThread().getName()); productService.updateProductPrice(1, 3000); } }
public static void main(String[] args) { for (int i = 0; i < 10; i++) { ProductService productService = new ProductServiceImpl(); ClientThread clientThread = new ClientThread(productService); clientThread.start(); } }
使用ThreadLocal编写工具类
package com.lhx.test.dbutil2; import java.sql.Connection; import java.sql.DriverManager; public class DBUtil { private static final String driver = "com.mysql.jdbc.Driver"; private static final String url = "jdbc:mysql://localhost:3306/demo"; private static final String username = "root"; private static final String password = "root"; private static ThreadLocal<Connection> conContainer = new ThreadLocal<>(); public static Connection getConnection() { Connection conn = conContainer.get(); try { if (conn == null) { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); } } catch (Exception e) { e.printStackTrace(); } finally { conContainer.set(conn); } return conn; } public static void colseConnection() { Connection conn = conContainer.get(); try { if (conn != null) { conn.close(); } } catch (Exception e) { e.printStackTrace(); } finally { conContainer.remove(); } } }