我們在開發中經常會看到別人用static這個關鍵字,來修飾變量。作為全局變量來使用。問題就在於,如果是多線程的情況下。往往會報錯。
直接看問題,
例如:我們生成一個序列號。
第一:創建接口
package com.huojg.Sequence; /** * 一個序列號生成器 * @author huojg21442 *每次調用getNumbenr就得到一個序列號,下次調用序列號就會自增 */ public interface Sequence { int getNumber(); }
第二:創建線程
package com.huojg.Sequence; /** * 做一個線程類。 * @author huojg21442 * */ 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.huojg.Sequence; /** * 接口實現類 * @author huojg21442 * */ 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 thread1=new ClientThread(sequence); ClientThread thread2=new ClientThread(sequence); ClientThread thread3=new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } /** * 查看結果我們知道,一個線程執行完畢,另一個,卻沒有從0開始執行。這是因為線程之間共享了static變量, * 無法保證對於不同的線程是安全的 ,也就是說無法保證線程的安全。 * * 那么如何保證線程安全呢。 * * * Thread-0=>1 Thread-0=>2 Thread-0=>3 Thread-1=>4 Thread-1=>5 Thread-1=>6 Thread-2=>7 Thread-2=>8 Thread-2=>9 * * * */ }
問題分析:在多線程情況下,static定義的靜態全局變量,不能滿足多線程的情況。正常的結果,不可能出現大於3的數。然而結果,累加起來。
這是因為,static定義全局變量,多線程共享使用了這個變量的 結果。產生線程不安全的結果。那么如何讓線程安全呢?。線程池ThreadLocal類。
用例如下:
package com.huojg.Sequence; /** * * 安全線程 用線程池。來完成、 * */ public class SequenceB implements Sequence { private static ThreadLocal<Integer> numbercontainer=new ThreadLocal<Integer>(){ @Override protected Integer initialValue(){ return 0; } }; public int getNumber() { numbercontainer.set(numbercontainer.get()+1); return numbercontainer.get(); } public static void main(String[] args) { Sequence sequence =new SequenceB(); ClientThread thread1=new ClientThread(sequence); ClientThread thread2=new ClientThread(sequence); ClientThread thread3=new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } /** * * 通過ThreadLocal封裝了一個Ingeter類型的numberContainer靜態成員變量。並且初始值,是0 * * 每個線程相互獨立了。,同樣是static變量,對於不同的線程而言。它沒有被共享。而是每個 線程一份,這也保證了線程的安全。 * * ThreadLocal為每一個線程提供了一個獨立的副本。 * * * 總結 ThreadLocal API * * public void set(T value),將值放入線程的局部變量中。 * protected T initialValue();返回線程局部變量的初始值。默認為null、 * * * * */ }
結果查看:
Thread-2=>1 Thread-0=>1 Thread-1=>1 Thread-0=>2 Thread-2=>2 Thread-0=>3 Thread-1=>2 Thread-2=>3 Thread-1=>3
我們利用ThreadLocal完成了,多線程,各自為資源的使用。這是為什么呢?.為什么用ThreadLocal就可以保證。線程各自利用各自的資源的呢 ?我們查看源碼
分析如下。ThreadLocal的源碼:方法
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的初始化默認值value是null。因為他是protected。這個單詞告訴我們就是當我們使用ThreadLocal的時候,需要重寫這個方法。
並且里面只是封裝了一個Map而已。那么我們自己就可以開發一個,山寨版的,ThreadLocal類
package com.huojg.Sequence; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * * * 熟悉了Threadlocal原理,,其實就是里面封裝了一個Map * * 自己寫一個ThreadLocal * * */ 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; } }
通過以上我們我知道。線程池內部原理以及實現方式了,
那么這個類在實際開發中我們應該用到那里呢,
例如:利用線程池管理數據庫連接的問題:
1.獲取連接類
package com.huojg.Sequence; 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/pay-routing"; private static final String username="root"; private static final String password="huojianguo"; //定義一個數據庫連接。多線程情況下報錯。No operations allowed after connection closed. 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 closeConnection(){ try { if(conn!=null){ conn.close(); } } catch (Exception e) { e.printStackTrace(); } } }
2.產品接口類
package com.huojg.Sequence; /** * 里面設置了static Connection,下面定義一個接口,供給邏輯層調用。 * * * */ public interface ProductService { void updateProductPrice(String productId,int price); }
3,多線程
package com.huojg.Sequence; /** * * ProductServiceImpl實現類數據庫的,產品價格更新,log新增的操作。 * 但是多線程情況下。會出錯 * 分析原因,是由於,static靜態資源競爭的問題。 * * 下面寫這個測試多線程的 情況下 , * */ public class ClientThreadProduct extends Thread { private ProductService productService; //利用構造函數,把接口初始化了、 public ClientThreadProduct(ProductService productService) { this.productService = productService; } @Override public void run() { System.out.println(Thread.currentThread().getName()); productService.updateProductPrice("1", 7000); } }
4,接口實現類
package com.huojg.Sequence; 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_PRODUCT_SQL="insert into log(created,description) values(?,?)"; @Override public void updateProductPrice(String productId, int price) { try { //獲取鏈接 Connection conn=DBUtil.getConnection(); conn.setAutoCommit(false);//關閉自動提交事務(開啟事務); //執行操作,更新產品,插入日志 updateProductPrice(conn,UPDATE_PRODUCT_SQL,productId,price); insertLog(conn,INSERT_PRODUCT_SQL,"create product."); //最后提交事務 conn.commit(); } catch (Exception e) { e.printStackTrace(); }finally { //關閉連接 DBUtil.closeConnection(); } } public static void updateProductPrice (Connection conn,String updateProductSQL,String productId,int price)throws Exception{ PreparedStatement pt=conn.prepareStatement(updateProductSQL); pt.setInt(1, price); pt.setString(2, productId); int rows=pt.executeUpdate(); if (rows!=0) { System.out.println("update product success!"); } } public static void insertLog(Connection conn,String insertProductSQL,String logDescription)throws Exception{ PreparedStatement pt=conn.prepareStatement(insertProductSQL); pt.setString(1, new SimpleDateFormat("YYYy-MM-dd HH:mm:ss").format(new Date())); pt.setString(2,logDescription); int rows=pt.executeUpdate(); if (rows!=0) { System.out.println("insert log success!"); } } public static void main(String[] args) { //模擬一下數據測試。 //productService.updateProductPrice("1",300); //多線程情況下測試 出現錯誤 No operations allowed after connection closed. for (int i=0;i<10;i++){ ProductService productService=new ProductServiceImpl(); ClientThreadProduct thread=new ClientThreadProduct(productService); thread.start(); } } }
結果:
出現錯誤 No operations allowed after connection closed
查看結果我們看出來。是連接關閉。為什么10個線程,連接就自己關閉了呢。分析我們發現。報錯原因。就是因為static定義的Connection連接。
用於static定義成全局變量以后,多線程下。可能,線程1關閉了,線程2開啟的 關系。因此,我們如何 來解決這個問題。利用ThreadLocal線程池。
那么我們只需要把connection .管理起來就可以,只需要寫過一下。DBUtil類的連接。
代碼如下:
package com.huojg.Sequence; 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/pay-routing"; private static final String username="root"; private static final String password="huojianguo"; //定義一個數據庫連接。多線程情況下報錯。No operations allowed after connection closed. //private static Connection conn=null; //由於Connection單連接,不能在多線程中使用,因此改寫成如下形式, //定義一個用於放置數據庫連接的局部線程變量(使每個線程都有自己的連接) private static ThreadLocal<Connection> connContainer =new ThreadLocal<Connection>(); //獲取連接 public static Connection getConnection(){ Connection conn=connContainer.get(); try { if (conn==null) { Class.forName(driver); conn=DriverManager.getConnection(url, username, password); } } catch (Exception e) { e.printStackTrace(); }finally { connContainer.set(conn); } return conn; } public static void closeConnection(){ Connection conn=connContainer.get(); try { if(conn!=null){ conn.close(); } } catch (Exception e) { e.printStackTrace(); }finally { connContainer.remove(); } } /** * 把connection連接放入ThreadLocal線程池中。這樣每個線程之間隔離了。不會相互干擾了、 * * * * */ }
結果測試;
Thread-0 Thread-1 Thread-2 Thread-5 Thread-9 Thread-6 Thread-3 Thread-4 Thread-7 Thread-8 update product success! insert log success! update product success! insert log success! update product success! insert log success! update product success! insert log success! update product success! insert log success! update product success! insert log success! update product success! insert log success! update product success! insert log success! update product success! insert log success! update product success! insert log success!
成功解決。數據庫在多線程的情況下使用。
ThreadLocal類會把Connection變成。內部的局部變量來管理。各自啟動各自的connection連接。