1. 程式人生 > >Java執行緒總結---第一天

Java執行緒總結---第一天

執行緒和程序各自有什麼區別和優劣:

  • 程序是資源分配的最小單位,執行緒是程式執行的最小單位

  • 程序有自己的獨立地址空間,每啟動一個程序,系統就會為它分配地址空間,建立資料表來維護程式碼段、堆疊段和資料段,這種操作非常昂貴。而執行緒是共享程序中的資料的,使用相同的地址空間,因此CPU切換一個執行緒的花費遠比程序要小很多,同時建立一個執行緒的開銷也比程序要小很多,執行緒的上下文切換的效能消耗要小於程序。

  • 執行緒之間的通訊更方便,同一程序下的執行緒共享全域性變數、靜態變數等資料。

  • 多程序程式更健壯,多執行緒程式只要有一個執行緒死掉,整個程序也死掉了,而一個程序死掉並不會對另外一個程序造成影響,因為程序有自己獨立的地址空間。

並行與併發

併發是沒有時間上的重疊的,兩個任務是交替執行的,由於切換的非常快,對於外界呼叫者來說相當於同一時刻多個任務一起執行了;而並行可以看到時間上是由重疊的,也就是說並行才是真正意義上的同一時刻可以有多個任務同時執行。

小程式demo

public class Demo extends Thread {
    @Override
    public void run() {
        while (true){
            System.out.println(this.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        Demo demo= new Demo();
        demo.start();
    }
}

public class Demo implements Runnable {
    @Override
    public void run() {
        while (true){
            System.out.println("hello thread");
        }
    }

    public static void main(String[] args) {
        Demo demo= new Demo();
        Thread thread=new Thread(demo,"t1");
        thread.start();
    }
}

public class Demo{

    public int count = 0;


    public  void print() {

        while (true){
            System.out.println(count++);
        }
    }

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Demo().print();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Demo().print();
            }
        }).start();
    }
}

synchronized鎖範圍

普通同步方法,鎖是當前例項物件

靜態同步方法,鎖是當前類的class物件

同步方法塊,鎖是括號裡面的物件

public class Demo{

    public synchronized void synsMethod1(){
        System.out.println("method1---");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synsMethod3();
    }
    public synchronized  void  synsMethod2(){
        System.out.println("method2---");
    }

    public synchronized  void  synsMethod3(){
        System.out.println("method3---");
    }
    public static void main(String[] args) {
        Demo demo=new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.synsMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.synsMethod2();
            }
        }).start();
    }
}

輸出結果

method1---
method3---
method2---

再如下

public class Demo{

    public synchronized void synsMethod1(){
        System.out.println("method1---");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synsMethod3();
    }
    public synchronized static void  synsMethod2(){
        System.out.println("method2---");
    }

    public synchronized  void  synsMethod3(){
        System.out.println("method3---");
    }
    public static void main(String[] args) {
        Demo demo=new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.synsMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.synsMethod2();
            }
        }).start();
    }
}

輸出結果(其中一種可能)

method1---
method2---
method3---

所以說靜態方法的鎖和非靜態方法的鎖是不一樣的

Synchronized鎖重入

關鍵字Synchronized擁有鎖重入的功能,也就是在使用Synchronized的時候, 當一個執行緒得到一個物件的鎖後,在該鎖裡執行程式碼的時候可以再次請求該物件的鎖 時可以再次得到該物件的鎖。

當執行緒請求一個由其它執行緒持有的物件鎖時,該執行緒會阻塞,而當執行緒請求由自己持有的物件鎖時,如果該鎖是重入鎖,請求就會成功,否則阻塞。

一個簡單的例子就是:在一個Synchronized修飾的方法或程式碼塊的內部呼叫本 類的其他Synchronized修飾的方法或程式碼塊時,是永遠可以得到鎖的。

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2019-02-25
 **/
public class Demo{

    public synchronized void synsMethod1(){
        System.out.println("method1---");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synsMethod2();
    }
    public synchronized void synsMethod2(){
        System.out.println("method2---");
    }
    public static void main(String[] args) {
        Demo demo=new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.synsMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.synsMethod2();
            }
        }).start();
    }
}

為什麼要引入可重入鎖這種機制哪?

假如有一個執行緒T獲得了物件A的鎖,那麼該執行緒T如果在未釋放前再次請求該物件的鎖時,如果沒有可重入鎖的機制,是不會獲取到鎖的,這樣的話就會出現死鎖的情況,所以最大的作用是避免死鎖。

volatile與synchronized的

volatile關鍵字的作用就是強制從公共堆疊中取得變數的值,而不是執行緒私有的資料棧中取得變數的值。

關鍵字volatile是執行緒同步的輕量級實現,效能比synchronized要好,並且volatile只能修飾變數,而synchronized可以修飾方法,程式碼塊等。

多執行緒訪問volatile不會發生阻塞,而synchronized會發生阻塞。

volatile可以保證資料的可見性,但不可以保證原子性(不是執行緒安全的),而synchronized可以保證原子性,也可以間接保證可見性,因為他會將私有記憶體和公共記憶體中的資料做同步。

volatile解決的是變數在多個執行緒之間的可見性,而synchronized解決的是多個執行緒之間訪問資源的同步性。

ThreadLocal

ThreadLocal提供了執行緒的區域性變數,每個執行緒都可以通過set()和get()來對這個區域性變數進行操作,但不會和其他執行緒的區域性變數進行衝突,實現了執行緒的資料隔離。

簡要言之:往ThreadLocal中填充的變數屬於當前執行緒,該變數對其他執行緒而言是隔離的。

最典型的應用

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
/**
 * 採用ThreadLocal封裝Connection
 * 
 * @author Administrator
 *
 */
public class ConnectionManager {
 
	//定義ThreadLocal靜態變數,確定存取型別為Connection
	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
	
	/**
	 * 得到Connection
	 * @return
	 */
	public static Connection getConnection() {
		Connection conn = connectionHolder.get();
		//如果在當前執行緒中沒有繫結相應的Connection
		if (conn == null) {
			try {
				Class.forName("oracle.jdbc.driver.OracleDriver");
				String url = "jdbc:oracle:thin:@localhost:1521:bjpowern";
				String username = "drp1";
				String password = "drp1";
				conn = DriverManager.getConnection(url, username, password);
				//將Connection設定到ThreadLocal
				connectionHolder.set(conn);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		return conn;
	}
	
	/**
	 * 關閉資料庫連線方法
	 * @return
	 */
	public static void closeConnection() {
		Connection conn = connectionHolder.get();
		if (conn != null) {
			try {
				conn.close();
				//從ThreadLocal中清除Connection
				connectionHolder.remove();
			} catch (SQLException e) {
				e.printStackTrace();
			}	
		}
	}
	
	/**
	 * 關閉資料庫連線方法
	 * @return
	 */
	public static void close(Connection conn) {
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(Statement pstmt) {
		if (pstmt != null) {
			try {
				pstmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(ResultSet rs ) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 事務開啟
	 * @return
	 */
	public static void beginTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (conn.getAutoCommit()) {
					conn.setAutoCommit(false); //手動提交
				}
			}
		}catch(SQLException e) {}
	}
	
	/**
	 * 事務提交
	 * @return
	 */
	public static void commitTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (!conn.getAutoCommit()) {
					conn.commit();
				}
			}
		}catch(SQLException e) {}
	}
	
	/**
	 * 事務回滾
	 * @return
	 */
	public static void rollbackTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (!conn.getAutoCommit()) {
					conn.rollback();
				}
			}
		}catch(SQLException e) {}
	}	
}

初始化預設值

public class ThreadlLocalDemo {

    /**
     * 初始化一個ThreadLocal物件,並且初始值設定為0
     */
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public int getNextNumber() {
        threadLocal.set(threadLocal.get() + 1);
        return threadLocal.get();
    }

    static class DemoThread extends Thread {
        ThreadlLocalDemo threadlLocalDemo = null;

        public DemoThread(ThreadlLocalDemo localDemo) {
            this.threadlLocalDemo = localDemo;
        }


        @Override
        public void run() {

            for (int i = 0; i < 5; i++) { // 每個執行緒列印 5個值
                System.out.println("Thread:" + Thread.currentThread().getName()
                        + ",threadlLocalDemo:" + threadlLocalDemo.getNextNumber());
            }

        }
    }

    public static void main(String[] args) {
        ThreadlLocalDemo threadlLocalDemo = new ThreadlLocalDemo();
        DemoThread demoThread = new DemoThread(threadlLocalDemo);
        DemoThread demoThread2 = new DemoThread(threadlLocalDemo);
        DemoThread demoThread3 = new DemoThread(threadlLocalDemo);
        DemoThread demoThread4 = new DemoThread(threadlLocalDemo);

        demoThread.start();
        demoThread2.start();
        demoThread3.start();
        demoThread4.start();

    }
}

ThreadLocal實現的原理

首先,我們來看一下ThreadLocal的set()方法,因為我們一般使用都是new完物件,就往裡邊set物件了

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

上面有個ThreadLocalMap,我們去看看這是什麼?


static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
		
		 /**
	 * Construct a new map initially containing (firstKey, firstValue).
	 * ThreadLocalMaps are constructed lazily, so we only create
	 * one when we have at least one entry to put in it.
	 */
	 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
		 table = new Entry[INITIAL_CAPACITY];
		 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
		 table[i] = new Entry(firstKey, firstValue);
		 size = 1;
		 setThreshold(INITIAL_CAPACITY);
	 }
		//....很長
}

通過上面我們可以發現的是ThreadLocalMap是ThreadLocal的一個內部類。用Entry類來進行儲存

我們的值都是儲存到這個Map上的,key是當前ThreadLocal物件!

如果該Map不存在,則初始化一個:

void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue);
}

如果map存在


/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param  t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
	return t.threadLocals;
}

Thread維護了ThreadLocalMap變數


/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null

從上面又可以看出,ThreadLocalMap是在ThreadLocal中使用內部類來編寫的,但物件的引用是在Thread中! 於是我們可以總結出:Thread為每個執行緒維護了ThreadLocalMap這麼一個Map,而ThreadLocalMap的key是LocalThread物件本身,value則是要儲存的物件。

有了上面的基礎,我們看get()方法就一點都不難理解了:


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

ThreadLocal原理總結

每個Thread維護著一個ThreadLocalMap的引用

ThreadLocalMap是ThreadLocal的內部類,用Entry來進行儲存

呼叫ThreadLocal的set()方法時,實際上就是往ThreadLocalMap設定值,key是ThreadLocal物件,值是傳遞進來的物件

呼叫ThreadLocal的get()方法時,實際上就是往ThreadLocalMap獲取值,key是ThreadLocal物件

ThreadLocal本身並不儲存值,它只是作為一個key來讓執行緒從ThreadL