高併發(5)- 多線程之間的共享

前言

上篇文章講解了多線程的運行狀態。本篇文章就來講講線程之間的共享。


一、爲什麼要線程共享

因爲線程都是獨立的,相互之間是不可見的,所以當兩個線程對一個數據進行操作時,就很容易出現問題。

/**
 * @version 1.0
 * @Description 不同步線程demo
 * @Author wb.yang
 */
public class NoSyncDemo {

	static Integer count = 0;

	public static class CountThread extends Thread {
		@Override
		public void run() {
			count++;
		}
	}


	public static void main(String[] args) {
		for (int i = 0; i < 1000; i++) {
			new CountThread().start();
		}
		System.out.println(count);
	}
}

            從代碼中看出,我們循環了1000次相加count關鍵字,結果應該是1000,我們看下結果

       可見,結果才輸出了994,明顯跟預期的結果不同。導致這個問題的就是線程的共享,因爲線程之間是不不見的,可能線程A拿了count = 100,這時候還沒有進行count++的操作,這時候線程B搶到了cpu,也取出了count=100這個時候,就已經數據不同步了。造成了線程安全問題。

針對線程安全問題,我們就要進行線程之間的數據共享。

二、線程共享方式

1.volatile

volatile關鍵字,最輕量的同步機制​。使用volatile關鍵字,可以使變量成爲可見性。保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。volatile 只能保證對單次讀/寫的原子性。i++ 這種操作不能保證原子性。

/**
 * @version 1.0
 * @Description volatile線程demo
 * @Author wb.yang
 */
public class VolatileSyncDemo {
	private volatile static boolean ready;
	private static int number;

	private static class CountThread extends Thread {
		@Override
		public void run() {
			System.out.println("計數線程啓動.......");
			//無限循環
			while (!ready) ;
			System.out.println("number = " + number);
		}
	}

	public static void main(String[] args) {
		new CountThread().start();
		SleepTools.second(1);
		number = 51;
		ready = true;
		SleepTools.second(5);
		System.out.println("main is ended!");
	}
}

                   從代碼中可以看出,我們先啓動了線程,之間打印了number,然後在主線程中賦值number=51,如果按照線程之間數據不可見的話,那麼應該打印null。我們看下結果把。

              我們可以看出,輸出的賦值後的數據,這事爲什麼呢?這個就涉及到了volatile關鍵字,通過volatile關鍵字,使變量在線程之間可見,所以可以知道number的變化。

2.synchronized

synchronized內置鎖​,有以下幾種鎖法

  • 普通同步方法,鎖是當前對象實例
  • 靜態同步方法,鎖是當前Class對象
  • 同步代碼塊,鎖是括號中的參數對象

當一個線程訪問同步代碼塊時,必須先獲得鎖才能執行同步代碼塊中的代碼,當退出或者拋出異常時,必須要釋放鎖。

/**
 * @version 1.0
 * @Description TODO
 * @Date 2020/3/25 17:41
 */
public class SynchronizedSyncDemo {

	static Integer count = 0;
	private static Object object = new Object();

	public static class CountThread extends Thread {
		@Override
		public void run() {
			synchronized (object){
				count++;
			}
		}

	}

	public static void main(String[] args) {
		for (int i = 0; i < 1000; i++) {
			new CountThread().start();
		}
		SleepTools.second(2);
		System.out.println(count);
	}
}

         從代碼可以看出,我們在run方法中添加了synchronized,並且鎖了方法,我們看下結果

        我們看到結果是正常的。之所以正常,是因爲用了synchronized,來將count++給加鎖,進行鎖住了,當一個線程加鎖鎖住這些代碼的時候,其他的線程過來必須等待上一個線程執行完代碼釋放鎖,否則不會持有這把鎖,只能在這裏等待,保證了線程的共享。

3.ThreadLocal

ThreadLocal是在每個線程都使用一個副本,線程之間隔離,操作的是副本中變量的值,只在這個線程中生效,其他線程中的這個變量,又是新的本地副本,所以不會變化

    

/**
 * @version 1.0
 * @Description ThreadLocalDemo
 * @Author wb.yang
 */
public class ThreadLocalSyncDemo {

	static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);


	public static class CountThread extends Thread {

		int id;

		public CountThread(int id) {
			this.id = id;
		}

		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + ":start");
			Integer s = threadLocal.get();
			s = s + id;
			threadLocal.set(s);
			System.out.println(Thread.currentThread().getName() + ":"
					+ threadLocal.get());
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 3; i++) {
			new CountThread(i).start();
		}
	}
}

          這個是一個ThreadLocal變量,默認值是1,然後啓動了三個線程,進行操作。

    從結果中看出,每個線程都是單獨的處理,縣城之間並沒有干擾,這個就是ThreadLocal的作用,來保證線程的共享。


總結

線程之間難免要線程共享來保證線程安全,所以就產生了以上幾種方法來保證線程同步安全。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章