1. 程式人生 > >並發編程基礎2

並發編程基礎2

阻塞 代碼片段 線程等待 如果 代碼 分配 兩個 ron admin

一:線程安全

當多個線程同時訪問一個實例(對象或者方法)時,輸入的行為是正確的,那麽可以認為這個程序是線程安全的。

看下面這段代碼,10個線程同時訪問1個實例,那麽運行結果會怎樣呢?

/**
 * 
 */
package com.day1;

import java.io.UnsupportedEncodingException;

/**
 * @author Administrator 線程安全:多個線程訪問同一個實例時,如果輸入的行為正確,那麽我們可以稱作線程安全,
 *         下面的實例,10個線程訪問同一個對象,存在線程安全問題,但是如果我們在可能存在安全問題
 *         的方法或者代碼片段上加上鎖synchronized,就可以避免線程安全問題
 *         a:線程獲取cpu的處理權,是隨機的,可以配置優先級,而不是按照程序的書寫順序
 *         b:在使用synchronized關鍵字時,應該盡量縮小使用範圍,這樣可以提高系統處理效率(前提是避免線程安全問題)
 */
public class MultiThread2 extends Thread {

	private int num = 10;

	public void run() {
		num--;
		String threadName = Thread.currentThread().getName();
		System.out.println(threadName + ":::" + num);
	}

	public static void main(String[] args) throws UnsupportedEncodingException {
		MultiThread2 task = new MultiThread2();
		Thread thread1 = new Thread(task, "線程1");
		Thread thread2 = new Thread(task, "線程2");
		Thread thread3 = new Thread(task, "線程3");
		Thread thread4 = new Thread(task, "線程4");
		Thread thread5 = new Thread(task, "線程5");
		Thread thread6 = new Thread(task, "線程6");
		Thread thread7 = new Thread(task, "線程7");
		Thread thread8 = new Thread(task, "線程8");
		Thread thread9 = new Thread(task, "線程9");
		Thread thread10 = new Thread(task, "線程10");
		thread1.start();
		thread2.start();
		thread3.start();
		thread4.start();
		thread5.start();
		thread6.start();
		thread7.start();
		thread8.start();
		thread9.start();
		thread10.start();
	}
}

運行結果:

線程2:::8
線程5:::6
線程1:::7
線程3:::7
線程7:::5
線程4:::4
線程9:::3
線程6:::2
線程8:::1
線程10:::0

通過運行結果,可以看出存在線程安全問題 ,因為10個線程共享一個成員變量num的值,都去操作它

如果想是運行行為正確,可以在方法上面加個同步synchronized關鍵字:

public synchronized void run() {
		num--;
		String threadName = Thread.currentThread().getName();
		System.out.println(threadName + ":::" + num);
	}

線程1:::9
線程3:::8
線程4:::7
線程7:::6
線程2:::5
線程5:::4
線程6:::3
線程8:::2
線程9:::1
線程10:::0

這時運行結果就是正確的了,順序num的值是對的,有人會說線程輸出順序不對,這是因為線程

的執行要看cpu的分配,是隨機的,不是按照程序書寫順序。

一般情況下,在保證同步安全的前提下,要盡可能的縮小鎖定的範圍,這樣可以提高系統的處理效率,如下:

public  void run() {
		synchronized (this) {
			System.out.println(Thread.currentThread().getName() + ":::" + --num);
		}
	}

線程1:::9
線程2:::8
線程4:::7
線程6:::6
線程8:::5
線程3:::4
線程10:::3
線程5:::2
線程7:::1
線程9:::0

運行結果依然是正確的。

二:同步與異步

只有多個線程存在同享時,才需要在同步鎖synchronized,不共享的情況下不需要加,一個類中如果有的方法是同步的

,有的方法是異步的,那麽當多個線程訪問該實例時(一個實例),會發生什麽情況呢?

/**
 * 
 */
package com.day1;

/**
 * @author Administrator
 * 兩個線程訪問同一個實例,如果訪問的都是同步方法(加synchronized關鍵字,可以是同一個方法,也可能是
 * 兩個方法),那麽一個線程正在執行程序時,另一個線程處於阻塞狀態。
 * 如果一個線程訪問同步方法,另一個線程訪問異步方法,那麽不存在阻塞問題。
 */
public class MultiThread3 {

	private synchronized void method1() {
		System.out.println("running method1...");
		try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private  void method2() {
		System.out.println("running method2...");
	}

	public static void main(String[] args) {
		MultiThread3 task = new MultiThread3();
		Thread t1 = new Thread(new Runnable(){
			@Override
			public void run() {
				task.method1();
			}
		});
		Thread t2 = new Thread(new Runnable(){
			@Override
			public void run() {
				task.method2();
			}
		});
		t1.start();
		t2.start();
	}

}

兩個線程訪問同一個類時,如果一個線程訪問同步方法,另一個線程訪問異步方法時,那麽不存在線程阻塞。

running method1...
running method2...

運行結果:

兩行結果都打印完之後,然後jvm等待1s後停止,說明兩個線程都是正常執行,沒有存在阻塞問題。

如果把第二個方法上面也加上synchronized關鍵字,

private synchronized void method2() {
		System.out.println("running method2...");
	}

在運行:

running method1...
running method2...

那麽運行結果是,先輸出第一行,然後等待1s後又輸出了第二行,說明第一個線程訪問method1時,

第二個線程沒有拿到對象鎖,處於線程等待狀態,待一個線程執行完畢後,釋放對象鎖,然後第二個

線程執行method2方法。

三:多個線程多個實例控制並發

如果有多個線程訪問多個 實例,那麽他們之間是沒有任何關系的,不存在線程安全問題,但是如果

也想使用鎖控制它們,則需要類鎖

/**
 * 
 */
package com.day1;

/**
 * @author Administrator
 * 兩個線程訪問同一個實例,如果訪問的都是同步方法(加synchronized關鍵字,可以是同一個方法,也可能是
 * 兩個方法),那麽一個線程正在執行程序時,另一個線程處於阻塞狀態。
 * 如果一個線程訪問同步方法,另一個線程訪問異步方法,那麽不存在阻塞問題。
 */
public class MultiThread3 {

	private synchronized void method1() {
		System.out.println("running method1...");
		try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private synchronized void method2() {
		System.out.println("running method2...");
	}

	public static void main(String[] args) {
		final MultiThread3 task1 = new MultiThread3();
		final MultiThread3 task2 = new MultiThread3();
		Thread t1 = new Thread(new Runnable(){
			@Override
			public void run() {
				task1.method1();
			}
		});
		Thread t2 = new Thread(new Runnable(){
			@Override
			public void run() {
				task2.method2();
			}
		});
		t1.start();
		t2.start();
	}

}

運行結果:

running method1...
running method2...

兩行數據同時輸出,等待1s然後停止,說明不存在線程安全問題,因為這裏是兩個實例,不同的鎖

如果也想讓它們串行執行,則需要使用類鎖

1:在兩個方法上加上static,則兩個同步方法的鎖變成了類的字節碼文件

private synchronized static void method1() {
		System.out.println("running method1...");
		try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private synchronized static void method2() {
		System.out.println("running method2...");
	}

2:使用同步代碼塊,將類的字節碼文件作為鎖

private  void method1() {
		synchronized (this.getClass()) {
			System.out.println("running method1...");
			try {
				Thread.sleep(1000L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	private  void method2() {
		synchronized (this.getClass()) {
			System.out.println("running method2...");
		}
	}

3:定義一個靜態的對象,作為類鎖

因為靜態的對象,所有的實例都是共享這個對象,所以在同一時刻只能有一個實例可以持有這個鎖

private static final Object lock = new Object();

	private  void method1() {
		synchronized (lock) {
			System.out.println("running method1...");
			try {
				Thread.sleep(1000L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	private  void method2() {
		synchronized (lock) {
			System.out.println("running method2...");
		}
	}

以上3種方式都可以實現類鎖!

並發編程基礎2