文檔版本 | 開發(fā)工具 | 測試平臺 | 工程名字 | 日期 | 作者 | 備注 |
---|---|---|---|---|---|---|
V1.0 | 2016.03.25 | lutianfei | none |
[TOC]
第十章 多線程
多線程概述
什么是進程?
- 進程:就是正在運行的程序苞笨。
- 進程是系統(tǒng)進行資源分配和調(diào)用的獨立單位腻贰。每一個進程都有它自己的內(nèi)存空間和系統(tǒng)資源。
多進程有什么意義呢?
- 可以在一個時間段內(nèi)執(zhí)行多個任務。
- 可以提高CPU的使用率。
什么是線程呢?
- 在同一個進程內(nèi)又可以執(zhí)行多個任務,而這每一個任務我就可以看出是一個
線程
。 -
線程
:是程序的執(zhí)行單元,執(zhí)行路徑若皱。是程序使用CPU的最基本單位。 -
單線程
:如果程序只有一條執(zhí)行路徑尘颓。 -
多線程
:如果程序有多條執(zhí)行路徑走触。
多線程有什么意義呢?
- 多線程的存在,不是提高程序的執(zhí)行速度泥耀。其實是為了提高應用程序的使用率饺汹。
- 程序的執(zhí)行其實都是在搶CPU的資源,CPU的執(zhí)行權痰催。
- 多個進程是在搶這個資源,而其中的某一個進程如果執(zhí)行路徑比較多迎瞧,就會有更高的幾率搶到CPU的執(zhí)行權夸溶。
- 我們是不敢保證哪一個線程能夠在哪個時刻搶到,所以線程的執(zhí)行有隨機性凶硅。
什么是并發(fā)呢缝裁?
- 大家注意兩個詞匯的區(qū)別:
并行
和并發(fā)
。 -
并行
是邏輯上同時發(fā)生足绅,指在某一個時間內(nèi)同時運行多個程序捷绑。 -
并發(fā)
是物理上同時發(fā)生,指在某一個時間點同時運行多個程序氢妈。 - 我們可以實現(xiàn)真正意義上的并發(fā),例如:多個CPU就可以實現(xiàn)粹污,不過得知道如何調(diào)度和控制它們。
Java程序運行原理
java 命令會啟動 java 虛擬機首量,啟動 JVM壮吩,等于啟動了一個應用程序进苍,也就是啟動了一個進程。該進程會自動啟動一個 “主線程” 鸭叙,然后主線程去調(diào)用某個類的 main 方法觉啊。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的沈贝。
-
思考:jvm虛擬機的啟動是單線程的還是多線程的杠人?
- JVM啟動至少啟動了垃圾回收線程和主線程,所以是多線程的宋下。
如何實現(xiàn)多線程
- 由于線程是依賴進程而存在的嗡善,所以我們應該先創(chuàng)建一個進程出來。進程是由系統(tǒng)創(chuàng)建的杨凑,所以我們應該去調(diào)用系統(tǒng)功能創(chuàng)建一個進程滤奈。
- Java是不能直接調(diào)用系統(tǒng)功能的,所以撩满,我們沒有辦法直接實現(xiàn)多線程程序蜒程。但是Java可以去調(diào)用C/C++寫好的程序來實現(xiàn)多線程程序。
- 由C/C++去調(diào)用系統(tǒng)功能創(chuàng)建進程伺帘,然后由Java去調(diào)用這樣的東西昭躺,然后提供一些類供我們使用。我們就可以實現(xiàn)多線程程序了伪嫁。
多線程的實現(xiàn)方案1
方式1:繼承Thread類领炫。
-
步驟:
- A:自定義類MyThread繼承Thread類。
- B:MyThread類里面重寫run()
- C:創(chuàng)建對象
- D:啟動線程
-
面試題:run()和start()的區(qū)別?
- run():僅僅是封裝被線程執(zhí)行的代碼张咳,直接調(diào)用是普通方法
- start():首先啟動了線程帝洪,然后再由jvm去調(diào)用該線程的run()方法。
-
面試題:為什么重寫run()方法?`
- 不是類中的所有代碼都需要被線程執(zhí)行的脚猾。為了區(qū)分哪些代碼能夠被線程執(zhí)行葱峡,java提供了Thread類中的run()用來包含那些被線程執(zhí)行的代碼。
/*
* 需求:我們要實現(xiàn)多線程的程序龙助。
*/
public class MyThreadDemo {
public static void main(String[] args) {
// 創(chuàng)建線程對象
// MyThread my = new MyThread();
// // 啟動線程
// my.run();
// my.run();
// 調(diào)用run()方法為什么是單線程的呢?
// 因為run()方法直接調(diào)用其實就相當于普通的方法調(diào)用,所以你看到的是單線程的效果
// 要想看到多線程的效果砰奕,就必須說說另一個方法:start()
// 面試題:run()和start()的區(qū)別?
// run():僅僅是封裝被線程執(zhí)行的代碼,直接調(diào)用是普通方法
// start():首先啟動了線程提鸟,然后再由jvm去調(diào)用該線程的run()方法军援。
// MyThread my = new MyThread();
// my.start();
// // IllegalThreadStateException:非法的線程狀態(tài)異常
// // 為什么呢?因為這個相當于是my線程被調(diào)用了兩次。而不是兩個線程啟動称勋。
// my.start();
// 創(chuàng)建兩個線程對象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
// 自己寫代碼
// System.out.println("好好學習胸哥,天天向上");
// 一般來說,被線程執(zhí)行的代碼肯定是比較耗時的铣缠。所以我們用循環(huán)改進
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
如何獲取和設置線程名稱
-
Thread類的基本獲取和設置方法
- public final String getName():獲取線程的名稱烘嘱。
- public final void setName(String name):設置線程的名稱
- 通過構造方法也可以給線程起名字
-
思考:
- 如何獲取main方法所在的線程名稱呢?
- public static Thread currentThread()
- 這樣就可以獲取任意方法所在的線程名稱
/*
* 針對不是Thread類的子類中如何獲取線程對象名稱呢?
* public static Thread currentThread():返回當前正在執(zhí)行的線程對象
* Thread.currentThread().getName()
*/
public class MyThreadDemo {
public static void main(String[] args) {
// 創(chuàng)建線程對象
//無參構造+setXxx()
// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
// //調(diào)用方法設置名稱
// my1.setName("林青霞");
// my2.setName("劉意");
// my1.start();
// my2.start();
//帶參構造方法給線程起名字
// MyThread my1 = new MyThread("林青霞");
// MyThread my2 = new MyThread("劉意");
// my1.start();
// my2.start();
//我要獲取main方法所在的線程對象的名稱昆禽,該怎么辦呢?
//遇到這種情況,Thread類提供了一個很好玩的方法:
//public static Thread currentThread():返回當前正在執(zhí)行的線程對象
System.out.println(Thread.currentThread().getName());
}
}
/*
名稱為什么是:Thread-? 編號
class Thread {
private char name[];
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//大部分代碼被省略了
this.name = name.toCharArray();
}
public final void setName(String name) {
this.name = name.toCharArray();
}
private static int threadInitNumber; //0,1,2
private static synchronized int nextThreadNum() {
return threadInitNumber++; //return 0,1
}
public final String getName() {
return String.valueOf(name);
}
}
class MyThread extends Thread {
public MyThread() {
super();
}
}
*/
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
線程調(diào)度
- 假如我們的計算機只有一個 CPU,那么 CPU 在某一個時刻只能執(zhí)行一條指令蝇庭,線程只有得到CPU時間片醉鳖,也就是使用權,才可以執(zhí)行指令哮内。那么Java是如何對線程進行調(diào)用的呢盗棵?
- 線程有兩種調(diào)度模型:
-
分時調(diào)度模型
所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間片 -
搶占式調(diào)度模型
優(yōu)先讓優(yōu)先級高的線程使用 CPU北发,如果線程的優(yōu)先級相同纹因,那么會隨機選擇一個,優(yōu)先級高的線程獲取的 CPU 時間片相對多一些琳拨。 - Java使用的是搶占式調(diào)度模型瞭恰。
- public final int getPriority():返回線程對象的優(yōu)先級
- public final void setPriority(int newPriority):更改線程的優(yōu)
- 線程默認優(yōu)先級是5。
- 線程優(yōu)先級的范圍是:1-10狱庇。
- 線程優(yōu)先級高僅僅表示線程獲取的 CPU時間片的幾率高惊畏,但是要在次數(shù)比較多,或者多次運行的時候才能看到比較好的效果密任。
-
public class ThreadPriority extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*
* 注意:
* IllegalArgumentException:非法參數(shù)異常颜启。
* 拋出的異常表明向方法傳遞了一個不合法或不正確的參數(shù)。
*
*/
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("東方不敗");
tp2.setName("岳不群");
tp3.setName("林平之");
// 獲取默認優(yōu)先級
// System.out.println(tp1.getPriority());
// System.out.println(tp2.getPriority());
// System.out.println(tp3.getPriority());
// 設置線程優(yōu)先級
// tp1.setPriority(100000);
//設置正確的線程優(yōu)先級
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
線程控制
我們已經(jīng)知道了線程的調(diào)度浪讳,接下來我們就可以使用如下方法對象線程進行控制
-
線程休眠
- public static void sleep(long millis)
-
線程加入
- public final void join()
-
線程禮讓
- public static void yield()
-
后臺線程
- public final void setDaemon(boolean on)
-
中斷線程
- public final void stop()
- public void interrupt()
案例:線程休眠
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
// 睡眠
// 困了缰盏,我稍微休息1秒鐘
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
* 線程休眠
* public static void sleep(long millis)
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("林青霞");
ts2.setName("林志玲");
ts3.setName("林志穎");
ts1.start();
ts2.start();
ts3.start();
}
}
- 案例:線程加入
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*
* public final void join():等待該線程終止。
*/
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("李淵");
tj2.setName("李世民");
tj3.setName("李元霸");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}
- 案例:線程禮讓
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.yield();
}
}
}
/*
* public static void yield():暫停當前正在執(zhí)行的線程對象淹遵,并執(zhí)行其他線程口猜。
* 讓多個線程的執(zhí)行更和諧,但是不能靠它保證一人一次透揣。
*/
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("林青霞");
ty2.setName("劉意");
ty1.start();
ty2.start();
}
}
- 案例:后臺線程
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*
* public final void setDaemon(boolean on):將該線程標記為守護線程或用戶線程暮的。
* 當正在運行的線程都是守護線程時,Java 虛擬機退出淌实。 該方法必須在啟動線程前調(diào)用。
*
* 游戲:坦克大戰(zhàn)猖腕。
*/
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("關羽");
td2.setName("張飛");
// 設置收獲線程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("劉備");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
- 案例:中斷線程
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("開始執(zhí)行:" + new Date());
// 我要休息10秒鐘拆祈,親,不要打擾我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("線程被終止了");
}
System.out.println("結束執(zhí)行:" + new Date());
}
}
/*
* public final void stop():讓線程停止倘感,過時了放坏,但是還可以使用。
* public void interrupt():中斷線程老玛。 把線程的狀態(tài)終止淤年,并拋出一個InterruptedException钧敞。
*/
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超過三秒不醒過來,我就干死你
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
線程的生命周期圖
多線程的實現(xiàn)方案2
- 實現(xiàn)Runnable接口
- 步驟:
- A:自定義類MyRunnable實現(xiàn)Runnable接口
- B:重寫run()方法
- C:創(chuàng)建MyRunnable類的對象
- D:創(chuàng)建Thread類的對象麸粮,并把C步驟的對象作為構造參數(shù)傳遞
- 步驟:
- 實現(xiàn)接口方式的好處
- 可以避免由于Java單繼承帶來的局限性溉苛。
- 適合多個相同程序的代碼去處理同一個資源的情況,把線程同程序的代碼弄诲,數(shù)據(jù)有效分離愚战,較好的體現(xiàn)了面向?qū)ο蟮脑O計思想。
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于實現(xiàn)接口的方式就不能直接使用Thread類的方法了,但是可以間接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
/*
* 方式2:實現(xiàn)Runnable接口
*/
public class MyRunnableDemo {
public static void main(String[] args) {
// 創(chuàng)建MyRunnable類的對象
MyRunnable my = new MyRunnable();
// 創(chuàng)建Thread類的對象齐遵,并把C步驟的對象作為構造參數(shù)傳遞
// Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// t1.setName("林青霞");
// t2.setName("劉意");
// Thread(Runnable target, String name)
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "劉意");
t1.start();
t2.start();
}
}
多線程程序練習
- 需求:
- 某電影院目前正在上映賀歲大片寂玲,共有100張票,而它有3個售票窗口售票梗摇,請設計一個程序模擬該電影院售票拓哟。
- 兩種方式實現(xiàn)
- 繼承Thread類
- 實現(xiàn)Runnable接口
- 方式一:
public class SellTicket extends Thread {
// 定義100張票
// private int tickets = 100;
// 為了讓多個線程對象共享這100張票,我們其實應該用靜態(tài)修飾
private static int tickets = 100;
@Override
public void run() {
// 定義100張票
// 每個線程進來都會走這里伶授,這樣的話断序,每個線程對象相當于買的是自己的那100張票,這不合理谎砾,所以應該定義到外面
// int tickets = 100;
// 是為了模擬一直有票
while (true) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
}
}
}
}
/*
* 某電影院目前正在上映賀歲大片(紅高粱,少林寺傳奇藏經(jīng)閣)逢倍,共有100張票,而它有3個售票窗口售票景图,請設計一個程序模擬該電影院售票较雕。
* 繼承Thread類來實現(xiàn)。
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 創(chuàng)建三個線程對象
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
// 給線程對象起名字
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
// 啟動線程
st1.start();
st2.start();
st3.start();
}
}
- 方式二:
public class SellTicket implements Runnable {
// 定義100張票
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "張票");
}
}
}
}
/*
* 實現(xiàn)Runnable接口的方式實現(xiàn)
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 創(chuàng)建資源對象
SellTicket st = new SellTicket();
// 創(chuàng)建三個線程對象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
關于電影院賣票程序的思考
- 我們前面講解過電影院售票程序挚币,從表面上看不出什么問題亮蒋,但是在真實生活中,售票時網(wǎng)絡是不能實時傳輸?shù)淖北希偸谴嬖谘舆t的情況慎玖,所以,在出售一張票以后笛粘,需要一點時間的延遲
- 改實現(xiàn)接口方式的賣票程序
- 每次賣票延遲100毫秒
- 改實現(xiàn)接口方式的賣票程序
改進后的電影院售票出現(xiàn)問題
- 問題
- 相同的票出現(xiàn)多次
- CPU的一次操作必須是原子性的
- 還出現(xiàn)了負數(shù)的票
- 隨機性和延遲導致的
- 相同的票出現(xiàn)多次
- 注意
- 線程安全問題在理想狀態(tài)下趁怔,不容易出現(xiàn),但一旦出現(xiàn)對軟件的影響是非常大的薪前。
public class SellTicket implements Runnable {
// 定義100張票
private int tickets = 100;
// @Override
// public void run() {
// while (true) {
// // t1,t2,t3三個線程
// // 這一次的tickets = 100;
// if (tickets > 0) {
// // 為了模擬更真實的場景润努,我們稍作休息
// try {
// Thread.sleep(100); // t1就稍作休息,t2就稍作休息
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// System.out.println(Thread.currentThread().getName() + "正在出售第"
// + (tickets--) + "張票");
// // 理想狀態(tài):
// // 窗口1正在出售第100張票
// // 窗口2正在出售第99張票
// // 但是呢?
// // CPU的每一次執(zhí)行必須是一個原子性(最簡單基本的)的操作。
// // 先記錄以前的值
// // 接著把ticket--
// // 然后輸出以前的值(t2來了)
// // ticket的值就變成了99
// // 窗口1正在出售第100張票
// // 窗口2正在出售第100張票
//
// }
// }
// }
@Override
public void run() {
while (true) {
// t1,t2,t3三個線程
// 這一次的tickets = 1;
if (tickets > 0) {
// 為了模擬更真實的場景示括,我們稍作休息
try {
Thread.sleep(100); //t1進來了并休息铺浇,t2進來了并休息,t3進來了并休息垛膝,
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "張票");
//窗口1正在出售第1張票,tickets=0
//窗口2正在出售第0張票,tickets=-1
//窗口3正在出售第-1張票,tickets=-2
}
}
}
}
解決線程安全問題的基本思想
- 首先想為什么出現(xiàn)問題?(也是我們判斷是否有問題的標準)
- 是否是多線程環(huán)境
- 是否有共享數(shù)據(jù)
- 是否有多條語句操作共享數(shù)據(jù)
- 如何解決多線程安全問題呢?
- 基本思想:讓程序沒有安全問題的環(huán)境鳍侣。
- 把多個語句操作共享數(shù)據(jù)的代碼給鎖起來丁稀,讓任意時刻只能有一個線程執(zhí)行即可。
- 基本思想:讓程序沒有安全問題的環(huán)境鳍侣。
同步的特點
- 同步的前提
- 多個線程
- 多個線程使用的是同一個鎖對象
- 同步的好處
- 同步的出現(xiàn)解決了多線程的安全問題倚聚。
- 同步的弊端
- 當線程相當多時线衫,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的秉沼,無形中會降低程序的運行效率桶雀。
解決線程安全問題實現(xiàn)1
- 同步代碼塊
- 格式:
- synchronized(對象){需要同步的代碼;}
- 同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能唬复。
- 同步代碼塊的對象可以是哪些呢?
public class SellTicket implements Runnable {
// 定義100張票
private int tickets = 100;
//創(chuàng)建鎖對象
private Object obj = new Object();
@Override
public void run() {
while (true) {
// t1,t2,t3都能走到這里
// 假設t1搶到CPU的執(zhí)行權矗积,t1就要進來
// 假設t2搶到CPU的執(zhí)行權,t2就要進來,發(fā)現(xiàn)門是關著的敞咧,進不去棘捣。所以就等著。
// 門(開,關)
synchronized (obj) { // 發(fā)現(xiàn)這里的代碼將來是會被鎖上的休建,所以t1進來后乍恐,就鎖了。(關)
if (tickets > 0) {
try {
Thread.sleep(100); // t1就睡眠了
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票 ");
//窗口1正在出售第100張票
}
} //t1就出來可测砂,然后就開門茵烈。(開)
}
}
}
/*
* 如何解決線程安全問題呢?
* 注意:
* 同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能砌些。
* 多個線程必須是同一把鎖呜投。
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 創(chuàng)建資源對象
SellTicket st = new SellTicket();
// 創(chuàng)建三個線程對象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
解決線程安全問題實現(xiàn)2:同步方法
- 就是把同步關鍵字加到方法上
- 同步方法的鎖對象是什么呢?
- this
- 如果是靜態(tài)方法,同步方法的鎖對象又是什么呢?
- 類的字節(jié)碼文件對象存璃。
- 那么仑荐,我們到底使用誰?
- 如果鎖對象是this,就可以考慮使用同步方法纵东。
- 否則能使用同步代碼塊的盡量使用同步代碼塊粘招。
同步解決線程安全問題總結
- A:同步代碼塊
- synchronized(對象) {
需要被同步的代碼;
} - 這里的鎖對象可以是
任意對象
。
- synchronized(對象) {
- B:同步方法
- 把同步關鍵字加在方法上偎球。
- 這里的鎖對象是
this
- C:靜態(tài)同步方法
- 把同步關鍵字加在方法上洒扎。
- 鎖對象是
類的字節(jié)碼文件對象
。(反射會講)
public class SellTicket implements Runnable {
// 定義100張票
private static int tickets = 100;
// 定義同一把鎖
private Object obj = new Object();
private Demo d = new Demo();
private int x = 0;
//同步代碼塊用obj做鎖
// @Override
// public void run() {
// while (true) {
// synchronized (obj) {
// if (tickets > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()
// + "正在出售第" + (tickets--) + "張票 ");
// }
// }
// }
// }
//同步代碼塊用任意對象做鎖
// @Override
// public void run() {
// while (true) {
// synchronized (d) {
// if (tickets > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()
// + "正在出售第" + (tickets--) + "張票 ");
// }
// }
// }
// }
@Override
public void run() {
while (true) {
if(x%2==0){
synchronized (SellTicket.class) {//靜態(tài)方法的安全鎖衰絮,若是普通同步方法則為:this
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票 ");
}
}
}else {
// synchronized (d) {
// if (tickets > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()
// + "正在出售第" + (tickets--) + "張票 ");
// }
// }
sellTicket();
}
x++;
}
}
//同步方法:
//如果一個方法一進去就看到了代碼被同步了逊笆,那么我就再想能不能把這個同步加在方法上呢?
// private synchronized void sellTicket() {
// if (tickets > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()
// + "正在出售第" + (tickets--) + "張票 ");
// }
// }
//靜態(tài)同步方法:
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票 ");
}
}
}
class Demo {
}
public class SellTicketDemo {
public static void main(String[] args) {
// 創(chuàng)建資源對象
SellTicket st = new SellTicket();
// 創(chuàng)建三個線程對象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
常見線程安全集合
public class ThreadDemo {
public static void main(String[] args) {
// 線程安全的類
StringBuffer sb = new StringBuffer();
Vector<String> v = new Vector<String>();
Hashtable<String, String> h = new Hashtable<String, String>();
// Vector是線程安全的時候才去考慮使用的,但是我還說過即使要安全岂傲,我也不用你
// 那么到底用誰呢?
// public static <T> List<T> synchronizedList(List<T> list)
List<String> list1 = new ArrayList<String>();// 線程不安全
List<String> list2 = Collections
.synchronizedList(new ArrayList<String>()); // 線程安全
}
}