1併發

package com.bjsxt.base.sync001;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 線程安全概念:當多個線程訪問某一個類(對象或方法)時,這個對象始終都能表現出正確的行爲,那麼這個類(對象或方法)就是線程安全的。
 * synchronized:可以在任意對象及方法上加鎖,而加鎖的這段代碼稱爲"互斥區"或"臨界區"
 * @author alienware
 *
 */
public class MyThread extends Thread{
	
	private int count = 5 ;
	
	//synchronized加鎖
	public void run(){
		count--;
		System.out.println(this.currentThread().getName() + " count = "+ count);
	}
	
	public static void main(String[] args) {
		/**
		 * 分析:當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這裏排對是按照CPU分配的先後順序而定的),
		 * 		一個線程想要執行synchronized修飾的方法裏的代碼:
		 * 		1 嘗試獲得鎖
		 * 		2 如果拿到鎖,執行synchronized代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止,
		 * 		   而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)
		 */
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

執行結果:

 

1線程安全的概念:

當多個線程訪問一個類(對象或者方法)時,這個類始終能表現出正確的行爲,那麼這個類()對象或者方法

就是線程安全的

synchronized 可以在任意對象以及方法上加鎖,餓加鎖的代碼叫‘互斥區’或者‘臨界區

package com.bjsxt.base.sync001;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 線程安全概念:當多個線程訪問某一個類(對象或方法)時,這個對象始終都能表現出正確的行爲,那麼這個類(對象或方法)就是線程安全的。
 * synchronized:可以在任意對象及方法上加鎖,而加鎖的這段代碼稱爲"互斥區"或"臨界區"
 * @author alienware
 *
 */
public class MyThread extends Thread{
	
	private int count = 5 ;
	
	//synchronized加鎖
	public synchronized void run(){
		count--;
		System.out.println(this.currentThread().getName() + " count = "+ count);
	}
	
	public static void main(String[] args) {
		/**
		 * 分析:當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這裏排對是按照CPU分配的先後順序而定的),
		 * 		一個線程想要執行synchronized修飾的方法裏的代碼:
		 * 		1 嘗試獲得鎖
		 * 		2 如果拿到鎖,執行synchronized代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止,
		 * 		   而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)
		 */
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

執行結果:

總結:

當多個線程訪問myThread方法時,以排隊的方法進行處理(排隊是按照cpu分配的先後順序而定的,一個線程想要執行synchronized修飾的方法裏的代碼時,首先嚐試獲得鎖,如果拿到鎖,執行synchronized代碼體的內容,拿不到鎖時,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止,而且時多個線程同時爭取這把鎖(也就是鎖競爭的問題))

=================================

2多個線程多個鎖:多個線程,每個線程都可以拿到自己指定的鎖,分別獲得鎖後,執行synchronized方法體的內容

package com.bjsxt.base.sync002;
/**
 * 關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,
 * 所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖(Lock),
 * 
 * 在靜態方法上加synchronized關鍵字,表示鎖定.class類,類一級別的鎖(獨佔.class類)。
 * @author alienware
 *
 */
public class MultiThread {

	private int num = 0;
	
	/** static */
	public synchronized void printNum(String tag){
		try {
			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}
			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//注意觀察run方法輸出順序
	public static void main(String[] args) {
		
		//倆個不同的對象
		final MultiThread m1 = new MultiThread();
		final MultiThread m2 = new MultiThread();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override 
			public void run() {
				m2.printNum("b");
			}
		});		
		
		t1.start();
		t2.start();
		
	}
	
	
	
	
}

每個對象都有自己的鎖

2.2

同步:synchronized

同步的概念就是共享,如果不是共享的資源,就沒不要進行同步

異步:asynchronized

異步的概念就是獨立,相互之間不受任何影響

同步的目的就是線程安全,對於線程安全來說,需要滿足兩個特性:原子性(同步),可見行

package com.bjsxt.base.sync003;

/**
 * 對象鎖的同步和異步問題
 * @author alienware
 *
 */
public class MyObject {

	public synchronized void method1(){
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/** synchronized */
	public void method2(){
			System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		
		final MyObject mo = new MyObject();
		
		/**
		 * 分析:
		 * t1線程先持有object對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法
		 * t1線程先持有object對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步
		 */
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method1();
			}
		},"t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method2();
			}
		},"t2");
		
		t1.start();
		t2.start();
		
	}
	
}

兩個同時執行,因爲第二個方法沒有鎖,兩個互不影響,直接執行

 

package com.bjsxt.base.sync003;

/**
 * 對象鎖的同步和異步問題
 * @author alienware
 *
 */
public class MyObject {

	public synchronized void method1(){
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/** synchronized */
	public synchronized void method2(){
			System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		
		final MyObject mo = new MyObject();
		
		/**
		 * 分析:
		 * t1線程先持有object對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法
		 * t1線程先持有object對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步
		 */
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method1();
			}
		},"t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method2();
			}
		},"t2");
		
		t1.start();
		t2.start();
		
	}
	
}

ti執行完後,過4秒執行t2

兩個方法都加里鎖,一個實例對象執行兩個方法時,都要爭奪鎖,都一個方法的鎖釋放完畢,在執行另一個方法。

示例總結:

a線程先持有object對象的lock鎖,b線程如果在這個時候調用對象中的同步(synchronized)方法需等待,也就是同步

a線程先持有object對象的lock鎖,b線程可以以異步的方式調用對象中的非synchronized修飾得分方法

 

======================================

3對於對象的同步和異步的方法,設計程序時,一定要考慮問題的整體。不然會出現數據不一致的錯誤,很經典的就是髒讀

package com.bjsxt.base.sync004;
/**
 * 業務整體需要使用完整的synchronized,保持業務的原子性。
 * @author alienware
 *
 */
public class DirtyRead {

	private String username = "bjsxt";
	private String password = "123";
	
	public synchronized void setValue(String username, String password){
		this.username = username;
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		this.password = password;
		
		System.out.println("setValue最終結果:username = " + username + " , password = " + password);
	}
	
	public void getValue(){
		System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
	}
	
	
	public static void main(String[] args) throws Exception{
		
		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("z3", "456");		
			}
		});
		t1.start();
		Thread.sleep(1000);
		
		dr.getValue();
	}
	
	
	
}

在setvalue時,不希望getvlaue

package com.bjsxt.base.sync004;
/**
 * 業務整體需要使用完整的synchronized,保持業務的原子性。
 * @author alienware
 *
 */
public class DirtyRead {

	private String username = "bjsxt";
	private String password = "123";
	
	public synchronized void setValue(String username, String password){
		this.username = username;
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		this.password = password;
		
		System.out.println("setValue最終結果:username = " + username + " , password = " + password);
	}
	
	public synchronized void getValue(){
		System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
	}
	
	
	public static void main(String[] args) throws Exception{
		
		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("z3", "456");		
			}
		});
		t1.start();
		Thread.sleep(1000);
		
		dr.getValue();
	}
	
	
	
}

示例總結:

在我們對一個對象的方法加鎖時,需要考慮業務的整體行,即setValue/getValue方法 同時加鎖時,保證

業務的原子性,不然會出現業務錯誤。

 

=============================

synchronized鎖重入:

在使用synchronized時,當一個線程得到了一個對象的鎖後,再次請求此對象時可以再次得到該對象的鎖。

package com.bjsxt.base.sync005;
/**
 * synchronized的重入
 * @author alienware
 *
 */
public class SyncDubbo1 {

	public synchronized void method1(){
		System.out.println("method1..");
		method2();
	}
	public synchronized void method2(){
		System.out.println("method2..");
		method3();
	}
	public synchronized void method3(){
		System.out.println("method3..");
	}
	
	public static void main(String[] args) {
		final SyncDubbo1 sd = new SyncDubbo1();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				sd.method1();
			}
		});
		t1.start();
	}
}

package com.bjsxt.base.sync005;
/**
 * synchronized的重入
 * @author alienware
 *
 */
public class SyncDubbo2 {

	static class Main {
		public int i = 10;
		public synchronized void operationSup(){
			try {
				i--;
				System.out.println("Main print i = " + i);
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	static class Sub extends Main {
		public synchronized void operationSub(){
			try {
				while(i > 0) {
					i--;
					System.out.println("Sub print i = " + i);
					Thread.sleep(100);		
					this.operationSup();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				Sub sub = new Sub();
				sub.operationSub();
			}
		});
		
		t1.start();
	}
	
	
}

父類和子類都有synchronized修飾時,也是沒有問題的

=========================================================

package com.bjsxt.base.sync005;
/**
 * synchronized異常
 * @author alienware
 *
 */
public class SyncException {

	private int i = 0;
	public synchronized void operation(){
		while(true){
			try {
				i++;
				Thread.sleep(100);
				System.out.println(Thread.currentThread().getName() + " , i = " + i);
				if(i == 10){
					Integer.parseInt("a");
				}
			} catch (Exception e) {//InterruptedException
				e.printStackTrace();
				//throw new RuntimeException();
				System.out.print("記錄日誌");
				continue;
			}
		}
	}
	
	public static void main(String[] args) {
		
		final SyncException se = new SyncException();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				se.operation();
			}
		},"t1");
		t1.start();
	}
	
	
}

package com.bjsxt.base.sync005;
/**
 * synchronized異常
 * @author alienware
 *
 */
public class SyncException {

	private int i = 0;
	public synchronized void operation(){
		while(true){
			try {
				i++;
				Thread.sleep(100);
				System.out.println(Thread.currentThread().getName() + " , i = " + i);
				if(i == 10){
					Integer.parseInt("a");
					//throw new RuntimeException();
				}
			} catch (Exception e) {//InterruptedException
				e.printStackTrace();
				throw new RuntimeException();
			}
		}
	}
	
	public static void main(String[] args) {
		
		final SyncException se = new SyncException();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				se.operation();
			}
		},"t1");
		t1.start();
	}
	
	
}

package com.bjsxt.base.sync005;
/**
 * synchronized異常
 * @author alienware
 *
 */
public class SyncException {

	private int i = 0;
	public synchronized void operation(){
		while(true){
			try {
				i++;
				Thread.sleep(100);
				System.out.println(Thread.currentThread().getName() + " , i = " + i);
				if(i == 10){
					Integer.parseInt("a");
					//throw new RuntimeException();
				}
			} catch (InterruptedException e) {//InterruptedException
				e.printStackTrace();
				//throw new RuntimeException();
			}
		}
	}
	
	public static void main(String[] args) {
		
		final SyncException se = new SyncException();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				se.operation();
			}
		},"t1");
		t1.start();
	}
	
	
}

重要:當在i-=10時,拋出一個異常,他還會繼續往下走,把下邊的走完!!!Exception  可以在catch中記錄一下日誌

當整個方法是一個整體,一個失敗,會影響下邊的程序,拋出InterruptedException,他會停止走下邊

當整個方法是一個整體,一個失敗,會影響下邊的程序,在catch中拋出異常:throw new RuntimeException();他會停止走下邊

 

===========================

volatile:

主要作用:使變量在多個線程間可見

有一個共享變量,a=0,開了兩個線程t1,t2.t1對a進行了修改,t2線程也對a進行了修改。兩個線程直接是互不可見的。

希望a保持一個一致性,t1改成了20,t2也應該改成20.volitile可以起到這個作用。

沒有volatile時,我們用鎖做到了這個效果,保持了a變量的一致性。但是這樣的效率低,因爲同一時間只有一個線程能操作共有的變量,其他線程只能等着

package com.bjsxt.base.sync007;

public class RunThread extends Thread{

	private  boolean isRunning = true;//private volatile boolean isRunning = true;
	private void setRunning(boolean isRunning){
		this.isRunning = isRunning;
	}
	
	public void run(){
		System.out.println("進入run方法..");
		int i = 0;
		while(isRunning == true){
			//..
		}
		System.out.println("線程停止");
	}
	
	public static void main(String[] args) throws InterruptedException {
		RunThread rt = new RunThread();
		rt.start();
		Thread.sleep(1000);
		rt.setRunning(false);
		System.out.println("isRunning的值已經被設置了false");
	}
	
	
}

雖然設置成了false,但是程序並沒有停止。

原因:jdk1.5後,對每個線程加了一個運行空間,裝主線程的一些引用變量。直接去父本取

裝了isRunning=true.在變量上加volatile,他就會停止了。

==========================================

package com.bjsxt.base.sync007;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * volatile關鍵字不具備synchronized關鍵字的原子性(同步)
 * @author alienware
 *
 */
public class VolatileNoAtomic extends Thread{
	private static volatile int count;
	//private static AtomicInteger count = new AtomicInteger(0);
	private static void addCount(){
		for (int i = 0; i < 1000; i++) {
			count++ ;
			//count.incrementAndGet();
		}
		System.out.println(count);
	}
	
	public void run(){
		addCount();
	}
	
	public static void main(String[] args) {
		
		VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
		for (int i = 0; i < 10; i++) {
			arr[i] = new VolatileNoAtomic();
		}
		
		for (int i = 0; i < 10; i++) {
			arr[i].start();
		}
	}
	
	
	
	
}

volatile關鍵字不具備synchronized關鍵字的原子性(同步)

package com.bjsxt.base.sync007;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * volatile關鍵字不具備synchronized關鍵字的原子性(同步)
 * @author alienware
 *
 */
public class VolatileNoAtomic extends Thread{
	//private static volatile int count;
	private static AtomicInteger count = new AtomicInteger(0);
	private static void addCount(){
		for (int i = 0; i < 1000; i++) {
			//count++ ;
			count.incrementAndGet();
		}
		System.out.println(count);
	}
	
	public void run(){
		addCount();
	}
	
	public static void main(String[] args) {
		
		VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
		for (int i = 0; i < 10; i++) {
			arr[i] = new VolatileNoAtomic();
		}
		
		for (int i = 0; i < 10; i++) {
			arr[i].start();
		}
	}
	
	
	
	
}

總結:volatile雖然擁有多個線程之間的可見性,但是不具備同步性(也就是原子性),可以算上一個輕量級的synchronized。性能比synchronized強很多,不會造成阻塞。比如netty的底層代碼就大量使用了volatutle,可見netty性能一定時不錯的,但需要注意的是,一般volatitle用於只針對多個線程可見的變量操作,並不能替代synchronized的同步功能。

 

volatile只具有可見性,沒有原子性。要實現原子性建議使用atomic類的系列對象,支持原子性操作

(注意 atomic類只保證本身方法原子性沒,並不能保持多次操作的原子性)

=============================

 

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