當多個線程訪問一個對象時,有可能會發生污讀,即讀取到未及時更新的數據,這個時候就須要線程同步。html
線程同步:java
即當有一個線程在對內存進行操做時,其餘線程都不能夠對這個內存地址進行操做,直到該線程完成操做, 其餘線程才能對該內存地址進行操做,而其餘線程又處於等待狀態,實現線程同步的方法有不少,臨界區對象就是其中一種。git
在通常狀況下,建立一個線程是不能提升程序的執行效率的,因此要建立多個線程。可是多個線程同時運行的時候可能調用線程函數,在多個線程同時對同一個內存地址進行寫入,因爲CPU時間調度上的問題,寫入數據會被屢次的覆蓋,因此就要使線程同步。算法
同步就是協同步調,按預約的前後次序進行運行。如:你說完,我再說。編程
「同」字從字面上容易理解爲一塊兒動做安全
其實不是,「同」字應是指協同、協助、互相配合。多線程
如進程、線程同步,可理解爲進程或線程A和B一塊配合,A執行到必定程度時要依靠B的某個結果,因而停下來,示意B運行;B依言執行,再將結果給A;A再繼續操做。併發
所謂同步,就是在發出一個功能調用時,在沒有獲得結果以前,該調用就不返回,同時其它線程也不能調用這個方法。按照這個定義,其實絕大多數函數都是同步調用(例如sin, isdigit等)。可是通常而言,咱們在說同步、異步的時候,特指那些須要其餘部件協做或者須要必定時間完成的任務。例如Window API函數SendMessage。該函數發送一個消息給某個窗口,在對方處理完消息以前,這個函數不返回。當對方處理完畢之後,該函數才把消息處理函數所返回的LRESULT值返回給調用者。異步
在多線程編程裏面,一些敏感數據不容許被多個線程同時訪問,此時就使用同步訪問技術,保證數據在任什麼時候刻,最多有一個線程訪問,以保證數據的完整性。ide
因爲同一進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也帶來了訪問衝突問題,爲了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized,當一個線程得到對象的排它鎖,獨佔資源,其餘線程必須等待,使用後釋放鎖便可能存在如下問題:
舉個例子,一個售票口有10張票,當100我的同時去買時,每一個人都獲取到了有100張票的數據,因此每一個人買了一張,致使最後剩下-90張票,線程不一樣步就會致使這種結果。
synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有如下幾種:
1. 修飾一個代碼塊,被修飾的代碼塊稱爲同步語句塊,其做用的範圍是大括號{}括起來的代碼,做用的對象是調用這個代碼塊的對象;
2. 修飾一個方法,被修飾的方法稱爲同步方法,其做用的範圍是整個方法,做用的對象是調用這個方法的對象;
3. 修改一個靜態的方法,其做用的範圍是整個靜態方法,做用的對象是這個類的全部對象;
4. 修改一個類,其做用的範圍是synchronized後面括號括起來的部分,做用主的對象是這個類的全部對象。
咱們寫一個例子,使用線程不安全的List來看看效果
public class MyThread{ public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(2000); System.out.println(list.size()); } }
能夠看到,循環1000次,只存進去998個,重複執行,這個大小還會變化,因此是線程不安全的。
可使用synchronized把list加鎖,就能保證每次都能插入進去。
public class MyThread{ public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ synchronized (list) { list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(2000); System.out.println(list.size()); } }
這樣就可以保證線程安全。
也可使用JUC(java.util.concurrent
)包下的線程安全的列表CopyOnWriteArrayList,代碼以下
import java.util.concurrent.CopyOnWriteArrayList; public class MyThread{ public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(2000); System.out.println(list.size()); } }
使用CopyOnWriteArrayList就能夠不須要synchronized關鍵字實現線程安全
查看源代碼能夠發現,CopyOnWriteArrayList實現了List<E>接口
而後再add方法中使用了synchronized來加鎖,和咱們上面的操做方法一致
//CopyOnWriteArrayList中的add()方法 public boolean add(E e) { synchronized (lock) { Object[] es = getArray(); int len = es.length; es = Arrays.copyOf(es, len + 1); es[len] = e; setArray(es); return true; } }
所謂死鎖,是指多個進程在運行過程當中因爭奪資源而形成的一種僵局,當進程處於這種僵持狀態時,若無外力做用,它們都將沒法再向前推動。
死鎖的條件
只要破壞後三個條件之一就能夠避免死鎖,可使用銀行家算法等方法。
先寫一個不使用鎖的例子
import java.util.concurrent.locks.ReentrantLock; public class MyThread implements Runnable { public static void main(String[] args) { MyThread thread = new MyThread(); Thread thread1 = new Thread(thread); Thread thread2 = new Thread(thread); Thread thread3 = new Thread(thread); thread1.start(); thread2.start(); thread3.start(); } public static int tickets = 10; @Override public void run() { while (true) { if (tickets > 0) { System.out.println(tickets--); } else { break; } } } }
執行後發現順序徹底是亂的
使用ReentrantLock(可重入鎖)來把相關代碼加鎖,便可實現按順序調用
import java.util.concurrent.locks.ReentrantLock; public class MyThread implements Runnable { public static void main(String[] args) { MyThread thread = new MyThread(); Thread thread1 = new Thread(thread); Thread thread2 = new Thread(thread); Thread thread3 = new Thread(thread); thread1.start(); thread2.start(); thread3.start(); } public static int tickets = 10; final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { try { lock.lock(); if (tickets > 0) { System.out.println(tickets--); } else { break; } } finally { lock.unlock(); } } } }
這樣也能夠實現線程同步。
Java提供的線程通訊方法
方法名 | 做用 |
---|---|
wait() | 表示線程一直等待,直到其餘線程通知,與sleep不一樣,會釋放鎖 |
wait(long timeout) | 指定等待的毫秒數 |
notify() | 喚醒一個處於等待狀態的線程 |
notifyAll() | 喚醒同一個對象上全部調用wait()方法的線程,優先級別高的線程優先調度 |
均是0bject類的方法都,只能在同步方法或者同步代碼塊中使用,不然會拋出llegalMonitorStateException
首先定義一個生產者類
//生產者 class Producer extends Thread { SynContainer container; public Producer(SynContainer container) { this.container = container; } //生產 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("生產第" + i + "個"); container.push(new Product(i)); } } }
生產者不斷往緩衝區添加產品,而後定義一個消費者類
//消費者 class Consumer extends Thread { SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消費 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消費第" + container.pop().id + "個"); try { Thread.sleep(500); } catch (InterruptedException ignored) { } } } }
消費者不斷在緩衝區去除產品,這裏添加一個sleep來模擬真實效果
最後定義緩衝區
//緩衝區 class SynContainer { //容器大小 Product[] products = new Product[10]; //計數器 int count = 0; //生產者放入產品 public synchronized void push(Product product) { //若是滿了,通知消費者,生產者等待,不然放入產品 if (count == products.length) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } products[count++] = product; this.notifyAll(); } //消費者消費產品 public synchronized Product pop() { if (count == 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll(); return products[--count]; } }
緩衝區的兩個方法都是使用synchronized修飾,保證可以執行完整,而後根據容器大小來判斷是否讓生產者以及消費者線程等待
當容器中沒有產品時,通知消費者等待,生產者線程開始,當產品滿時,通知生產者等待,消費者線程開始。
最後補上產品類
//產品 class Product { //產品編號 int id; public Product(int id) { this.id = id; } }
類定義和上面相似,只不過在產品類中添加了一個信號量來區分是否有產品,不須要一個緩衝區
//生產者 class Producer extends Thread { Product product; public Producer(Product product) { this.product = product; } //生產 @Override public void run() { for (int i = 0; i < 10; i++) { this.product.push("產品" + i); } } } //消費者 class Consumer extends Thread { Product product; public Consumer(Product product) { this.product = product; } //消費 @Override public void run() { for (int i = 0; i < 10; i++) { this.product.pop(); } } } //產品 class Product { String product; boolean flag = true; //生產 public synchronized void push(String product) { if (!flag) { try { this.wait(); } catch (InterruptedException ignored) { } } System.out.println("生產了" + product); //通知消費 this.notifyAll(); this.product = product; this.flag = !this.flag; } //消費 public synchronized void pop() { if (flag) { try { this.wait(); } catch (InterruptedException ignored) { } } System.out.println("消費了" + this.product); //通知生產者 this.notifyAll(); this.flag = !this.flag; } }
這樣也能夠解決生產者和消費者問題
常常建立和銷燬、使用量特別大的資源,好比並髮狀況下的線程,對性能影響很大。
思路:提早建立好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。能夠避免頻繁建立銷燬、實現重複利用。相似生活中的公共交通工具。
JDK 5.0起提供了線程池相關API: ExecutorService和Executors
ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
代碼演示
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { //建立線程池 ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //關閉鏈接 service.shutdown(); } } class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
這樣就能夠實現經過線程池來管理線程
Java多線程(上)http://www.javashuo.com/article/p-cjqayqvb-wr.html