java核心知識點學習----多線程間的數據共享和對象獨立,ThreadLocal詳解


線程內的數據共享與對象獨立,舉例:張三給李四轉錢,開啟A線程去執行轉錢這個動作,剛好同時王五給趙六轉錢,開啟B線程去執行轉錢,因為是調用的同樣一個動作或者說對象,所以如果不能保證線程間的對象獨立,那么很有可能發生,張三給李四轉錢時把王五轉給趙六的轉錢一塊提交了,而王五轉錢整個動作還未完成,那么就造成了轉錢錯誤, 所以線程間一方面要保證數據的共享,另一方面要保證對象的對立.

1.用Map封裝對象以數據實現共享

package com.amos.concurrent;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
 * @ClassName: ThreadScopeShareData
 * @Description: 下面的例子用的是Map對象將數據實現共享
 * @author: amosli
 * @email:hi_amos@outlook.com
 * @date Apr 20, 2014 6:19:02 PM
 */
public class ThreadScopeShareData {
public static Map<Object, Integer> map = new HashMap<Object, Integer>(); public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    int data = new Random().nextInt();//給data設值,
                    System.out.println(Thread.currentThread().getName() + " set data:" + data);
                map.put(Thread.currentThread(), data);//將值按照Thread去設值,取的時候也按Thread去取,以保證數據的共享,但又保證了對象的獨立.
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }

    static class A {//這里A和B的方法雖然是一樣的,這里是想表示有可能調用不同的對象去執行數據操作
        public int get() {
            data = map.get(Thread.currentThread());
            System.out.println("a from thread:" + Thread.currentThread().getName() + " is " + data);
            return data;
        }
    }

    static class B {
        public int get() {
            int data = map.get(Thread.currentThread());
            System.out.println("b from thread:" + Thread.currentThread().getName() + " is " + data);
            return data;
        }
    }

}

 

運行效果:

 2.使用ThreadLocal實現數據共享

創建ThreadLocal,可以直接new出來,其設值支技泛型,new ThreadLocal<T>,如下將上面代碼改寫:

public class ThreadLocalShareData {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    int data = new Random().nextInt();//給data設值,
                    System.out.println(Thread.currentThread().getName() + " set data:" + data);
                    threadLocal.set(data);//使用ThreadLocal來設值
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
    static class A {//這里A和B的方法雖然是一樣的,這里是想表示有可能調用不同的對象去執行數據操作
        public int get() {
            int data = threadLocal.get();
            System.out.println("a from thread:" + Thread.currentThread().getName() + " is " + data);
            return data;
        }
    }
class B....
...
}

下面是ThreadLocal set(T value)方法的源碼:

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

這里同樣是用Map方式的設值,只不過又封裝了一層ThreadLocalMap.

查看其ThreadLocal  get()方法的源碼:

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

同樣是通過與線程綁定,取值的.

 

3.實例測試

package com.amos.concurrent;
class Account {
    /*
     * 定義一個ThreadLocal類型的變量,該變量是一個線程局部變量
     */
    private ThreadLocal<String> name = new ThreadLocal<String>();

    // 定義一個初始化name屬性的構造器
    public Account(String str) {
        this.name.set(str);
        // 下面的代碼用於訪問當前線程的name副本的值
        System.out.println("------" + this.name.get());
    }
    // name的getter,setter方法
    public String getName() {
        return name.get();
    }
    public void setName(String str) {
        this.name.set(str);
    }
}

class MyTest extends Thread {
    // 定義一個Account屬性
    private Account account;

    public MyTest(Account account, String name) {
        super(name);// 設置thread的名稱
        this.account = account;
    }

    @Override
    public void run() {
        // 循環
        for (int i = 0; i < 10; i++) {
            if (i == 6) {// 當i=6時,將name名稱更改為當前的線程名
                account.setName(getName());
            }
            System.out.println(account.getName() + " 賬戶i的值:" + i);
        }
    }
}
public class ThreadLocalTest {
    public static void main(String[] args) {
        Account account = new Account("初始名稱");
        // 啟動兩個線程,兩人個線程共享同一個賬戶,即只有一個賬戶名.
        /*
         * 雖然丙個線程共享同一個賬戶,即只有一個賬戶名.但由於賬戶名是ThradLocal類型的,所以每個線程都完全擁有各自的賬戶名副本,
         * 因此在i=6以后,將看到兩人個線程訪問同一個賬戶時出現不同的賬戶名
         */
        new MyTest(account, "張三").start();
        new MyTest(account, "李四").start();
    }
}

效果如下:

 

4.關於ThreadLocal的幾點說明

1).ThreadLoca原理:

Thread Local Variable(線程局部變量)的意思,其功能其實非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,使每一個線程都可以獨立地改變自己的副本,而不會和D他線程的副本沖突,從線程的角度來看,就好像每個線程都完全擁有該變量一樣.

2).常用的方法:

>>T get():返回此線程局部變量中當前線程的值.

>>void remove():刪除此線程局部變量中當前線程的值.

>>void set(T value):設置此線程局部變量中當前線程副本中的值.

3).ThradLocal和線程同步機制的區別:

實現機制不同:和線程同步機制一樣,都是為了解決多線程中,對同一變量的訪問沖突,在普通的同步機制中,是通過對象加鎖來實現多個線程對同一個變量的安全訪問的.而ThreadLocal是將需要並發訪問的資源復制多分,每個線程擁有一份資源,每個線程擁有自己的資源副本,從而也變沒有必要對該變量進行同步了.

 面向問題的領域不同: ThreadLocal 並不能替代同步機制,同步機制是為了同步多個線程對相同資源的並發訪問,是多個線程之間進行通信的有效方式;而ThradLocal是為了隔離多個線程的數據共享,從根本上避免了多個線程之間對共享資源(變量)的競爭,也就不需要對多個線程進行同步了.

4)何時使用?

如果多個線程之間需要共享資源,以達到線程之間的通信功能,就使用同步機制.

如果僅僅需要隔離多個線程之間的共享沖突,則可以使用ThreadLocal

 

 5.擴展---封裝復雜數據對象

package com.amos.concurrent;
import java.util.Random;
/**
 * @ClassName: ThreadLocalShareData
 * @Description: 下面的例子用的是ThreadLocal對象將數據實現共享,封裝復雜數據對象
 * @author: amosli
 * @email:hi_amos@outlook.com
 * @date Apr 20, 2014 6:19:02 PM
 */
public class ThreadLocalShareDataTest {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    int data = new Random().nextInt();//給data設值,
                    System.out.println(Thread.currentThread().getName() + " set data:" + data);
                    MyThreadData.getMyThreadData().setName("name"+data);
                    MyThreadData.getMyThreadData().setAge(data);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
    static class A {//這里A和B中的方法是一樣的,可以只看一個
        public void get() {
            MyThreadData myThreadData = MyThreadData.getMyThreadData();
            int data =myThreadData.getAge();
            System.out.println("a from thread:" + Thread.currentThread().getName() + " age: " + data+"   name:"+myThreadData.getName());
        }
    }
    static class B{
        public void get() {
            MyThreadData myThreadData = MyThreadData.getMyThreadData();
            int data =myThreadData.getAge();
            System.out.println("b from thread:" + Thread.currentThread().getName() + "  age: " + data+"   name:"+myThreadData.getName());
        }
    }
    //自定義對象
    static class MyThreadData {
        private static ThreadLocal<MyThreadData> mapLocal = new ThreadLocal<MyThreadData>();
        private MyThreadData(){}
        //單例模式,獲取數值
        public static MyThreadData getMyThreadData(){
            MyThreadData instance = mapLocal.get();
            if(instance==null){
                instance = new MyThreadData();
                mapLocal.set(instance);
            }
            return instance;
        }
        //name,age setter/getter
        private String name ;
        private Integer age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
    }
    
}
View Code

上面例子都是封裝基本類型的數據,這里是封裝String name,Integer age,封裝了復雜數據對象.

運行效果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM