所有執行緒共享主內存,每個執行緒都有自己的工作記憶體
refreshing local memory to/from main memory must comply to JMM rules
產生線程安全的原因
線程的working memory是cpu的暫存器和高速緩存的抽象描述:現在的計算機,cpu在計算的時候,並不總是從內存讀取數據,它的資料讀取順序優先權是: 暫存器-快取-記憶體 。線程耗費的是CPU,線程計算的時候,原始的數據來自內存,在計算過程中,有些數據可能被頻繁讀取,這些數據被存儲在寄存器和高速緩存中,當線程計算完後,這些緩存的資料在適當的時候應該寫回記憶體。當多個執行緒同時讀寫某個記憶體資料時,就會產生多執行緒並發問題,涉及到三個特 性:原子性,有序性,可見性。 支援多執行緒的平台都會面臨 這種問題,運行在多執行緒平台上支援多執行緒的語言應該提供解決該問題的方案。
JVM是一個虛擬的計算機,它也會面臨多執行緒並發問題,java程式運行在java虛擬機平台上,java程式設計師不可能直接去控制底層執行緒對暫存器高速緩存記憶體之間的同步,那麼java從語法層面,應該給開發人員一種解決方案,這個方案就是諸如synchronized, volatile,鎖定機制(如同步塊,就緒隊列,阻塞隊列)等等。這些方案只是語法層面的,但我們要從本質上去理解它;
每個線程都有自己的執行空間(即工作內存),線程執行的時候用到某變量,首先要將變量從主記憶體拷貝的自己的工作記憶體空間,然後對變數進行操作:讀取,修改,賦值等,這些均在工作記憶體完成,操作完成後再將變數寫回主記憶體;
各線程都從主內存中獲取數據,線程之間數據是不可見的;打個比方:主內存變量A原始值為1,線程1從主內存取出變量A,修改A的值為2,在線程1未將變數A寫回主記憶體的時候,線程2拿到變數A的值仍然為1;
這便引出「可見性」的概念:當一個共享變數在多個執行緒的工作內存中都有副本時,如果一個執行緒修改了這個共享變數的副本值,那麼其他執行緒應該可以看到這個被修改後的值,這就是多執行緒的可見性問題。
普通變數狀況:如線程A修改了一個普通變數的值,然後向主記憶體寫回,另外一條線程B在線程A回寫完成了之後再從主記憶體進行讀取操作,新變數的值才會對執行緒B可見;
#寫執行緒安全的程式碼,本質上就是管理對狀態(state)的訪問,而且通常都是共享的、可變的狀態。這裡的狀態就是物件的變數(靜態變數和實例變數)
#執行緒安全的前提是該變數是否被多個執行緒存取, 保證物件的執行緒安全性需要使用同步來協調對其可變狀態的存取;若是做不到這一點,就會導致髒數據和其他不可預期的後果。無論何時,只要有多於一個的執行緒存取給定的狀態變量,而且其中某個執行緒會寫入該變量,此時必須使用同步來協調執行緒對該變數的存取。 Java中首要的同步機制是synchronized關鍵字,它提供了獨佔鎖定。除此之外,術語「同步」還包括volatile變量,顯示鎖和原子變量的使用。
在沒有正確同步的情況下,如果多個執行緒存取了同一個變量,你的程式就存在隱患。有3種方法修復它:
l 不要跨執行緒共享變數;
l 使狀態變數為不可變的;或
l 在任何存取狀態變數的時候使用同步。
volatile要求程式對變數的每次修改,都寫回主內存,這樣便對其它線程課件,解決了可見性的問題,但是不能保證資料的一致性;特別注意:原子操作:根據Java規範,對於基本型別的賦值或傳回值操作,是原子操作。但這裡的基本資料型別不包括long和double, 因為JVM看到的基本儲存單位是32位,而long 和double都要用64位來表示。所以無法在一個時脈週期內完成
通俗的講一個物件的狀態就是它的數據,儲存在狀態變數中,例如實例域或靜態域;無論何時,只要多於一個的執行緒訪問給定的狀態變數。而且其中某個執行緒會寫入該變量,此時必須使用同步來協調執行緒對該變數的存取;
同步鎖定:每個JAVA物件都有且只有一個同步鎖,在任何時刻,最多只允許一個執行緒擁有這把鎖。
当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。
1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。
(一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁
如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)
Synchronized关键字保证了数据读写一致和可见性等问题,但是他是一种阻塞的线程控制方法,在关键字使用期间,所有其他线程不能使用此变量,这就引出了一种叫做非阻塞同步的控制线程安全的需求;
顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
Object的wait()和notify()、notifyAll()方法,使用一个对象作为锁,然后调用wait()就会挂起当前线程,同时释放对象锁;
notify()使用要首先获取对象锁,然后才能唤醒被挂起的线程(因为等待对象锁而挂起的)
notifyAll():唤醒在此对象监视器上等待的所有线程。
wait()在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
抛出: IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者
package com.taobao.concurrency; public class WaitTest { public static String a = "";// 作为监视器对象 public static void main(String[] args) throws InterruptedException { WaitTest wa = new WaitTest(); TestTask task = wa.new TestTask(); Thread t = new Thread(task); t.start(); Thread.sleep(12000); for (int i = 5; i > 0; i--) { System.out.println("快唤醒挂起的线程************"); Thread.sleep(1000); } System.out.println("收到,马上!唤醒挂起的线程************"); synchronized (a) { a.notifyAll(); } } class TestTask implements Runnable { @Override public void run() { synchronized (a) { try { for (int i = 10; i > 0; i--) { Thread.sleep(1000); System.out.println("我在运行 ***************"); } a.wait(); for (int i = 10; i > 0; i--) { System.out.println("谢谢唤醒**********又开始运行了*******"); } } catch (InterruptedException e) { e.printStackTrace(); } } } } }
用wait notify 解决生产者消费者问题代码:
package com.taobao.concurrency; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; class Meal { private final int orderNum; public Meal(int orderNum) { this.orderNum = orderNum; } public String toString() { return "Meal " + orderNum; } } class WaitPerson implements Runnable { private Restaurant restaurant; public WaitPerson(Restaurant r) { this.restaurant = r; } @Override public void run() { try { while (!Thread.interrupted()) { synchronized (this) { while (restaurant.meal == null) wait();// ..for the chef to produce a meal } System.out.println("WaitPerson got" + restaurant.meal); synchronized (restaurant.chef) { restaurant.meal = null; restaurant.chef.notifyAll();// ready for another } } TimeUnit.MICROSECONDS.sleep(100); } catch (InterruptedException e) { System.out.println("WaitPerson interrupted"); } } } class Chef implements Runnable { private Restaurant restaurant; private int count = 0; public Chef(Restaurant r) { this.restaurant = r; } @Override public void run() { try { while (!Thread.interrupted()) { synchronized (this) { while (restaurant.meal != null) wait();// ...for the meal to be taken } if (++count == 10) { System.out.println("Out of food,closing"); restaurant.exec.shutdownNow(); } System.out.println("Order up!"); synchronized (restaurant.waitPerson) { restaurant.meal = new Meal(count); restaurant.waitPerson.notifyAll(); } } } catch (InterruptedException e) { } } } public class Restaurant { Meal meal; ExecutorService exec = Executors.newCachedThreadPool(); WaitPerson waitPerson = new WaitPerson(this); Chef chef = new Chef(this); public Restaurant() { exec.execute(chef); exec.execute(waitPerson); } public static void main(String[] args) { new Restaurant(); } }
用ArrayBlockingQueue解决生产者消费者问题 ;默认使用的是非公平锁
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止,若请求不到此线程被加入阻塞队列;
如果使用公平锁,当有内容可以消费时,会从队首取出消费者线程进行消费(即等待时间最长的线程)
add(anObject):把anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则招聘异常
package com.taobao.concurrency; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class TestBlockingQueues { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<String>(20); Thread pro = new Thread(new Producer(queue), "生产者"); pro.start(); for (int i = 0; i < 10; i++) { Thread t = new Thread(new Concumer(queue), "消费者 " + i); t.start(); } } } class Producer implements Runnable { BlockingQueue<String> queue; public Producer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { int i = 0; while (true) { try { System.out.println("生产者生产食物, 食物编号为:" + i); queue.put(" 食物 " + i++); Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("生产者被中断"); } } } } class Concumer implements Runnable { BlockingQueue<String> queue; public Concumer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { while (true) { try { System.out.println(Thread.currentThread().getName() + "消费:" + queue.take()); } catch (InterruptedException e) { System.out.println("消费者被中断"); } } } } 执行结果: 消费者 0 请求消费 消费者 2 请求消费 消费者 4 请求消费 消费者 6 请求消费 消费者 8 请求消费 消费者 5 请求消费 生产者生产食物, 食物编号为:0 消费者 0消费: 食物 0 消费者 1 请求消费 消费者 3 请求消费 消费者 7 请求消费 消费者 9 请求消费 消费者 0 请求消费 生产者生产食物, 食物编号为:1 消费者 2消费: 食物 1 消费者 2 请求消费 生产者生产食物, 食物编号为:2 消费者 4消费: 食物 2 消费者 4 请求消费 生产者生产食物, 食物编号为:3 消费者 6消费: 食物 3 消费者 6 请求消费 生产者生产食物, 食物编号为:4 消费者 8消费: 食物 4 消费者 8 请求消费 生产者生产食物, 食物编号为:5 消费者 5消费: 食物 5 消费者 5 请求消费 生产者生产食物, 食物编号为:6 消费者 1消费: 食物 6 消费者 1 请求消费 生产者生产食物, 食物编号为:7 消费者 3消费: 食物 7 消费者 3 请求消费 生产者生产食物, 食物编号为:8 消费者 7消费: 食物 8 消费者 7 请求消费 生产者生产食物, 食物编号为:9 消费者 9消费: 食物 9 消费者 9 请求消费
多个生产者,多个消费者
package com.taobao.concurrency; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class TestBlockingQueues { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<String>(20); for (int i = 0; i < 10; i++) { Thread pro = new Thread(new Producer(queue), "生产者" + i); pro.start(); } for (int i = 0; i < 10; i++) { Thread t = new Thread(new Concumer(queue), "消费者 " + i); t.start(); } } } class Producer implements Runnable { BlockingQueue<String> queue; public Producer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { int i = 0; while (true) { try { System.out.println(Thread.currentThread().getName() + "生产食物, 食物编号为:" + Thread.currentThread().getName() + i); queue.put(" 食物 " + Thread.currentThread().getName() + i++); Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("生产者被中断"); } } } } class Concumer implements Runnable { BlockingQueue<String> queue; public Concumer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " 请求消费"); try { System.out.println(Thread.currentThread().getName() + "消费:" + queue.take()); Thread.sleep(100); } catch (InterruptedException e) { System.out.println("消费者被中断"); } } } } 生产者0生产食物, 食物编号为:生产者00 生产者2生产食物, 食物编号为:生产者20 生产者1生产食物, 食物编号为:生产者10 生产者3生产食物, 食物编号为:生产者30 生产者4生产食物, 食物编号为:生产者40 生产者6生产食物, 食物编号为:生产者60 生产者8生产食物, 食物编号为:生产者80 生产者5生产食物, 食物编号为:生产者50 生产者7生产食物, 食物编号为:生产者70 生产者9生产食物, 食物编号为:生产者90 消费者 0 请求消费 消费者 0消费: 食物 生产者00 消费者 2 请求消费 消费者 2消费: 食物 生产者20 消费者 1 请求消费 消费者 1消费: 食物 生产者10 消费者 4 请求消费 消费者 4消费: 食物 生产者30 消费者 3 请求消费 消费者 6 请求消费 消费者 6消费: 食物 生产者40 消费者 3消费: 食物 生产者60 消费者 8 请求消费 消费者 8消费: 食物 生产者80 消费者 5 请求消费 消费者 5消费: 食物 生产者50 消费者 7 请求消费 消费者 7消费: 食物 生产者70 消费者 9 请求消费 消费者 9消费: 食物 生产者90 消费者 0 请求消费 消费者 1 请求消费 消费者 2 请求消费 消费者 4 请求消费 消费者 3 请求消费 消费者 5 请求消费 消费者 7 请求消费 消费者 9 请求消费 消费者 6 请求消费 消费者 8 请求消费 生产者0生产食物, 食物编号为:生产者01 消费者 0消费: 食物 生产者01 生产者2生产食物, 食物编号为:生产者21 生产者4生产食物, 食物编号为:生产者41 消费者 1消费: 食物 生产者21 生产者1生产食物, 食物编号为:生产者11 消费者 2消费: 食物 生产者41 消费者 4消费: 食物 生产者11 生产者3生产食物, 食物编号为:生产者31
条件队列解释:
Condition queuesare like the "toast is ready" bell on your toaster. If you are list ening for it, you are notified promptly when your toast is ready and can drop what you are doing (or not, maybe you want to finish the newspaper first) and get your toast. If you are not listening for it (perhaps you went outside to get the newspaper), you could miss the notification, but on return to the kitchen you can observe the state of the toaster and either retrieve the toast if it is finished or start listening for the bell again if it is not.
基于条件的:多线程情况下,某个条件在某个时刻为假,不代表一直为假,可能到某个时刻就好了!
Lock 使用的默认 为非公平锁;condition对象继承了与之相关的锁的共平性特性,如果是公平的锁,线程会依照FIFO的顺序从Condition.wait中被释放;ArrayBlockingQueue中有一个比较不好的地方,生产者每次生产完之后,都要通知消费者,至于有没有性能损失TODO
【相关推荐】
4. JavaScript中的object转换函数toString()与valueOf()介绍_javascript技巧
以上是詳解java中線程記憶體模型的實例教程的詳細內容。更多資訊請關注PHP中文網其他相關文章!