線程安全問題通常是發生再多線程環境,當多個線程同時共享一個全局變量或靜態變量作寫的操做時候,可能會發生數據衝突問題,也就是線程安全問題,在讀的操做不會發生數據衝突問題 下面看個簡單的買票例子 案例:需求如今有100張火車票,有兩個窗口同時搶火車票,請使用多線程模擬搶票效果。 代碼: public class ThreadTrain1 implements Runnable { private int count = 100;java
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
sale();
}
}
public void sale() {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
}
複製代碼
}程序員
public class ThreadDemo { public static void main(String[] args) { ThreadTrain1 threadTrain1 = new ThreadTrain1(); Thread t1 = new Thread(threadTrain1, "①號窗口"); Thread t2 = new Thread(threadTrain1, "②號窗口"); t1.start(); t2.start(); } } 運行結果編程
Synchronized(/'sɪŋkrənaɪzd/) ------至關於自動擋 Lock(/lɒk/ )---jdk1.5併發包才又 ------至關於手動擋緩存
3.一、什麼是同步代碼塊? 答:就是將可能會發生線程安全問題的代碼,給包括起來。 synchronized(同一個數據){ 可能會發生線程衝突問題 } 就是同步代碼塊 synchronized(對象) { //這個對象能夠爲任意對象 須要被同步的代碼 } 對象如同鎖,持有鎖的線程能夠在同步中執行 沒持有鎖的線程即便獲取CPU的執行權,也進不去 同步的前提:安全
class ThreadTrain2 implements Runnable { private int count = 100; public boolean flag = true; private static Object oj = new Object();多線程
@Override
public void run() {
if (flag) {
while (count > 0) {
synchronized (this) {
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
}
}
} else {
while (count > 0) {
sale();
}
}
}
public synchronized void sale() {
// 前提 多線程進行使用、多個線程只能拿到一把鎖。
// 保證只能讓一個線程 在執行 缺點效率下降
// synchronized (oj) {
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
// }
}
複製代碼
}併發
public class ThreadDemo2 { public static void main(String[] args) throws InterruptedException { ThreadTrain2 threadTrain1 = new ThreadTrain2(); Thread t1 = new Thread(threadTrain1, "①號窗口"); Thread t2 = new Thread(threadTrain1, "②號窗口"); t1.start(); Thread.sleep(40); threadTrain1.flag = false; t2.start(); } } 五、靜態同步函數 5.一、什麼是靜態同步函數?ide
class ThreadTrain6 implements Runnable { // 這是貨票總票數,多個線程會同時共享資源 private int trainCount = 100; public boolean flag = true; private Object mutex = new Object();函數
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
// 鎖(同步代碼塊)在何時釋放? 代碼執行完, 自動釋放鎖.
// 若是flag爲true 先拿到 obj鎖,在拿到this 鎖、 才能執行。
// 若是flag爲false先拿到this,在拿到obj鎖,才能執行。
// 死鎖解決辦法:不要在同步中嵌套同步。
sale();
}
}
} else {
while (true) {
sale();
}
}
}
/**
*
* @methodDesc: 功能描述:(出售火車票)
*/
public synchronized void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
複製代碼
}post
public class DeadlockThread {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個實例
Thread thread1 = new Thread(threadTrain, "一號窗口");
Thread thread2 = new Thread(threadTrain, "二號窗口");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
複製代碼
}
四、多線程有三大特性
4.一、什麼是原子性 即一個操做或者多個操做 要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。 一個很經典的例子就是銀行帳戶轉帳問題: 好比從帳戶A向帳戶B轉1000元,那麼必然包括2個操做:從帳戶A減去1000元,往帳戶B加上1000元。這2個操做必需要具有原子性才能保證不出現一些意外的問題。 咱們操做數據也是如此,好比i = i+1;其中就包括,讀取i的值,計算i,寫入i。這行代碼在Java中是不具有原子性的,則多線程運行確定會出問題,因此也須要咱們使用同步和lock這些東西來確保這個特性了。 原子性其實就是保證數據一致、線程安全一部分, 4.二、什麼是可見性 當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。 若兩個線程在不一樣的cpu,那麼線程1改變了i的值還沒刷新到主存,線程2又使用了i,那麼這個i值確定仍是以前的,線程1對變量的修改線程沒看到這就是可見性問題。 4.三、什麼是有序性 程序執行的順序按照代碼的前後順序執行。 通常來講處理器爲了提升程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行前後順序同代碼中的順序一致,可是它會保證程序最終執行結果和代碼順序執行的結果是一致的。以下: int a = 10; //語句1 int r = 2; //語句2 a = a + 3; //語句3 r = a*a; //語句4 則由於重排序,他還可能執行順序爲 2-1-3-4,1-3-2-4 但毫不可能 2-1-4-3,由於這打破了依賴關係。 顯然重排序對單線程運行是不會有任何問題,而多線程就不必定了,因此咱們在多線程編程時就得考慮這個問題了。
共享內存模型指的就是Java內存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化。
Volatile 關鍵字的做用是變量在多個線程之間可見,但不保證原子性,下面的文章連接講的很是好,這裏不詳細說了 juejin.im/post/5afd22…
AtomicInteger是一個提供原子操做的Integer類,經過線程安全的方式操做加減。下面是個例子能夠運行對比下count和atomicInteger的結果會發現不管運行多少次,atomicInteger的結果都是正確的 package com;
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileNoAtomic extends Thread { static int count = 0; private static AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//等同於i++
atomicInteger.incrementAndGet();
count++;
}
System.out.println(atomicInteger+","+count);
}
public static void main(String[] args) {
// 初始化10個線程
VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
for (int i = 0; i < 10; i++) {
// 建立
volatileNoAtomic[i] = new VolatileNoAtomic();
}
for (int i = 0; i < volatileNoAtomic.length; i++) {
volatileNoAtomic[i].start();
}
}
複製代碼
} 上面只是簡單的一個原子類的介紹,詳細的能夠看如下連接說的很好 blog.csdn.net/xiaoliuliu2…
僅靠volatile不能保證線程的安全性。(原子性) ①volatile輕量級,只能修飾變量。synchronized重量級,還可修飾方法 ②volatile只能保證數據的可見性,不能用來同步,由於多個線程併發訪問volatile修飾的變量不會阻塞。 synchronized不只保證可見性,並且還保證原子性,由於,只有得到了鎖的線程才能進入臨界區,從而保證臨界區中的全部語句都所有執行。多個線程爭搶synchronized鎖對象時,會出現阻塞。 線程安全性 線程安全性包括兩個方面,①可見性。②原子性。 從上面自增的例子中能夠看出:僅僅使用volatile並不能保證線程安全性。而synchronized則可實現線程的安全性。 8、ThreadLocal深度解析 www.imooc.com/article/255…