| 文檔版本 | 開發(fā)工具 | 測試平臺 | 工程名字 | 日期 | 作者 | 備注 |
|---|---|---|---|---|---|---|
| V1.0 | 2016.03.31 | lutianfei | none |
JDK5中Lock鎖的使用
- 雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock。
-
Lock:- void lock(): 獲取鎖。
- void unlock():釋放鎖。
- ReentrantLock是Lock的實現(xiàn)類。
public class SellTicket implements Runnable {
// 定義票
private int tickets = 100;
// 定義鎖對象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加鎖
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票");
}
} finally {
// 釋放鎖
lock.unlock();
}
}
}
}
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)了同步嵌套,就容易產(chǎn)生死鎖問題
死鎖問題及其代碼
是指兩個或者兩個以上的線程在執(zhí)行的過程中,因爭奪資源產(chǎn)生的一種互相等待現(xiàn)象
同步代碼塊的嵌套案例
/*
* 舉例:
* 中國人,美國人吃飯案例。
* 正常情況:
* 中國人:筷子兩支
* 美國人:刀和叉
* 現(xiàn)在:
* 中國人:筷子1支,刀一把
* 美國人:筷子1支,叉一把
*/
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
public class MyLock {
// 創(chuàng)建兩把鎖對象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
線程間通信
-
針對同一個資源的操作有不同種類的線程
- 舉例:賣票有進的,也有出的。
通過設置線程(生產(chǎn)者)和獲取線程(消費者)針對同一個學生對象進行操作
基本版本
/*
* 分析:
* 資源類:Student
* 設置學生數(shù)據(jù):SetThread(生產(chǎn)者)
* 獲取學生數(shù)據(jù):GetThread(消費者)
* 測試類:StudentDemo
*
* 問題1:按照思路寫代碼,發(fā)現(xiàn)數(shù)據(jù)每次都是:null---0
* 原因:我們在每個線程中都創(chuàng)建了新的資源,而我們要求的時候設置和獲取線程的資源應該是同一個
* 如何實現(xiàn)呢?
* 在外界把這個數(shù)據(jù)創(chuàng)建出來,通過構造方法傳遞給其他的類。
*
*/
public class StudentDemo {
public static void main(String[] args) {
//創(chuàng)建資源
Student s = new Student();
//設置和獲取的類
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//線程類
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//啟動線程
t1.start();
t2.start();
}
}
public class SetThread implements Runnable {
private Student s;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
s.name = "林青霞";
s.age = 27;
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
System.out.println(s.name + "---" + s.age);
}
}
public class Student {
String name;
int age;
}
- 改進版,給出了不同的數(shù)據(jù),并加入同步機制
/*
* 分析:
* 資源類:Student
* 設置學生數(shù)據(jù):SetThread(生產(chǎn)者)
* 獲取學生數(shù)據(jù):GetThread(消費者)
* 測試類:StudentDemo
*
* 問題2:為了數(shù)據(jù)的效果好一些,我加入了循環(huán)和判斷,給出不同的值,這個時候產(chǎn)生了新的問題
* A:同一個數(shù)據(jù)出現(xiàn)多次
* B:姓名和年齡不匹配
* 原因:
* A:同一個數(shù)據(jù)出現(xiàn)多次
* CPU的一點點時間片的執(zhí)行權,就足夠你執(zhí)行很多次。
* B:姓名和年齡不匹配
* 線程運行的隨機性
* 線程安全問題:
* A:是否是多線程環(huán)境 是
* B:是否有共享數(shù)據(jù) 是
* C:是否有多條語句操作共享數(shù)據(jù) 是
* 解決方案:
* 加鎖。
* 注意:
* A:不同種類的線程都要加鎖。
* B:不同種類的線程加的鎖必須是同一把。
*/
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
System.out.println(s.name + "---" + s.age);
}
}
}
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (x % 2 == 0) {
s.name = "林青霞";//剛走到這里,就被別人搶到了執(zhí)行權
s.age = 27;
} else {
s.name = "劉意"; //剛走到這里,就被別人搶到了執(zhí)行權
s.age = 30;
}
x++;
}
}
}
}
等待喚醒機制

- Object類中提供了三個方法:
- wait():等待
- notify():喚醒單個線程
- notifyAll():喚醒所有線程
- 為什么這些方法不定義在Thread類中呢?
- 這些方法的調(diào)用必須通過鎖對象調(diào)用,而我們剛才使用的鎖對象是任意鎖對象。所以,這些方法必須定義在Object類中。
- 線程間通信的代碼改進,通過等待喚醒機制實現(xiàn)數(shù)據(jù)依次出現(xiàn)
/*
* 問題3:雖然數(shù)據(jù)安全了,但是呢,一次一大片不好看,我就想依次的一次一個輸出。
* 如何實現(xiàn)呢?
* 通過Java提供的等待喚醒機制解決。
*
*/
public class Student {
String name;
int age;
boolean flag; // 默認情況是沒有數(shù)據(jù),如果是true,說明有數(shù)據(jù)
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
//判斷有沒有
if(s.flag){
try {
s.wait(); //t1等著,釋放鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "林青霞";
s.age = 27;
} else {
s.name = "劉意";
s.age = 30;
}
x++; //x=1
//修改標記
s.flag = true;
//喚醒線程
s.notify(); //喚醒t2,喚醒并不表示你立馬可以執(zhí)行,必須還得搶CPU的執(zhí)行權。
}
//t1有,或者t2有
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait(); //t2就等待了。立即釋放鎖。將來醒過來的時候,是從這里醒過來的時候
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//林青霞---27
//劉意---30
//修改標記
s.flag = false;
//喚醒線程
s.notify(); //喚醒t1
}
}
}
}
- 生產(chǎn)者消費者之等待喚醒機制代碼優(yōu)化
/*
* 最終版代碼中:
* 把Student的成員變量給私有的了。
* 把設置和獲取的操作給封裝成了功能,并加了同步。
* 設置或者獲取的線程里面只需要調(diào)用方法即可。
*/
public class StudentDemo {
public static void main(String[] args) {
//創(chuàng)建資源
Student s = new Student();
//設置和獲取的類
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//線程類
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//啟動線程
t1.start();
t2.start();
}
}
public class Student {
private String name;
private int age;
private boolean flag; // 默認情況是沒有數(shù)據(jù),如果是true,說明有數(shù)據(jù)
public synchronized void set(String name, int age) {
// 如果有數(shù)據(jù),就等待
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 設置數(shù)據(jù)
this.name = name;
this.age = age;
// 修改標記
this.flag = true;
this.notify();
}
public synchronized void get() {
// 如果沒有數(shù)據(jù),就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 獲取數(shù)據(jù)
System.out.println(this.name + "---" + this.age);
// 修改標記
this.flag = false;
this.notify();
}
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
s.set("林青霞", 27);
} else {
s.set("劉意", 30);
}
x++;
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
s.get();
}
}
}
線程的狀態(tài)轉(zhuǎn)換圖


線程組
- Java中使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。
- 默認情況下,所有的線程都屬于主線程組。
- public final ThreadGroup getThreadGroup()
- 給線程設置分組
- Thread(ThreadGroup group, Runnable target, String name)
public class ThreadGroupDemo {
public static void main(String[] args) {
// method1();
// 我們?nèi)绾涡薷木€程所在的組呢?
// 創(chuàng)建一個線程組
// 創(chuàng)建其他線程的時候,把其他線程的組指定為我們自己新建線程組
method2();
// t1.start();
// t2.start();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("這是一個新的組");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "劉意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通過組名稱設置后臺線程,表示該組的線程都是后臺線程
tg.setDaemon(true);
}
private static void method1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "劉意");
// 我不知道他們屬于那個線程組,我想知道,怎么辦
// 線程類里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 線程組里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通過結果我們知道了:線程默認情況下屬于main線程組
// 通過下面的測試,你應該能夠看到,默任情況下,所有的線程都屬于同一個組
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
線程池
-
程序啟動一個新線程成本是比較高的,因為它涉及到要與操作系統(tǒng)進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創(chuàng)建大量生存期很短的線程時,更應該考慮使用線程池。
- 線程池里的每一個線程代碼結束后,并不會死亡,而是再次回到線程池中成為空閑狀態(tài),等待下一個對象來使用。
- 在JDK5之前,我們必須手動實現(xiàn)自己的線程池,從JDK5開始,Java內(nèi)置支持線程池
-
JDK5新增了一個Executors工廠類來產(chǎn)生線程池,有如下幾個方法
- public static ExecutorService newCachedThreadPool()
- public static ExecutorService newFixedThreadPool(int nThreads)
- public static ExecutorService newSingleThreadExecutor()
- 這些方法的返回值是
ExecutorService對象,該對象表示一個線程池,可以執(zhí)行Runnable對象或者Callable對象代表的線程。它提供了如下方法Future<?> submit(Runnable task)<T> Future<T> submit(Callable<T> task)
實現(xiàn)線程池的步驟
-
A:創(chuàng)建一個線程池對象,控制要創(chuàng)建幾個線程對象。
- public static ExecutorService newFixedThreadPool(int nThreads)
-
B:創(chuàng)建Runnable實例
- 可以執(zhí)行Runnable對象或者Callable對象代表的線程
- 做一個類實現(xiàn)Runnable接口。
-
C:提交Runnable實例
Future<?> submit(Runnable task)<T> Future<T> submit(Callable<T> task)
-
D:關閉線程池
- shutdown()
例子:
public class ExecutorsDemo {
public static void main(String[] args) {
// 創(chuàng)建一個線程池對象,控制要創(chuàng)建幾個線程對象。
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執(zhí)行Runnable對象或者Callable對象代表的線程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//結束線程池
pool.shutdown();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
多線程程序?qū)崿F(xiàn)方案3:創(chuàng)建線程池方式
-
實現(xiàn)Callable接口
- 好處:
- 可以有返回值
- 可以拋出異常
- 弊端:
- 代碼比較復雜,所以一般不用
- 好處:
多線程求和案例
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創(chuàng)建線程池對象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執(zhí)行Runnable對象或者Callable對象代表的線程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 結束
pool.shutdown();
}
}
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
匿名內(nèi)部類方式使用多線程
- new Thread(){代碼…}.start();
- New Thread(new Runnable(){代碼…}).start();
/*
* 匿名內(nèi)部類的格式:
* new 類名或者接口名() {
* 重寫方法;
* };
* 本質(zhì):是該類或者接口的子類對象。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 繼承Thread類來實現(xiàn)多線程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}.start();
// 實現(xiàn)Runnable接口來實現(xiàn)多線程
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}) {
}.start();
// 更有難度的
new Thread(new Runnable() { //Runnable 接口的run方法
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + ":" + x);
}
}
}) {
public void run() { //子類對象本身的run方法,默認走的是此方法。
for (int x = 0; x < 100; x++) {
System.out.println("world" + ":" + x);
}
}
}.start();
}
}
定時器
- 定時器是一個應用十分廣泛的線程工具,可用于調(diào)度多個定時任務以后臺線程的方式執(zhí)行。在Java中,可以通過Timer和TimerTask類來實現(xiàn)定義調(diào)度的功能
- Timer
- public Timer()
- public void schedule(TimerTask task, long delay)
- public void schedule(TimerTask task,long delay,long period)
- TimerTask
- public abstract void run()
- public boolean cancel()
- 開發(fā)中
- Quartz是一個完全由java編寫的開源調(diào)度框架。
- 單次定時案例
public class TimerDemo {
public static void main(String[] args) {
// 創(chuàng)建定時器對象
Timer t = new Timer();
// 3秒后執(zhí)行爆炸任務
// t.schedule(new MyTask(), 3000);
//結束任務
t.schedule(new MyTask(t), 3000);
}
}
// 做一個任務
class MyTask extends TimerTask {
private Timer t;
public MyTask(){}
public MyTask(Timer t){
this.t = t;
}
@Override
public void run() {
System.out.println("beng,爆炸了");
t.cancel();
}
}
- 案例:定時刪目錄
/*
* 需求:在指定的時間刪除我們的指定目錄(你可以指定c盤,但是我不建議,我使用項目路徑下的demo)
*/
class DeleteFolder extends TimerTask {
@Override
public void run() {
File srcFolder = new File("demo");
deleteFolder(srcFolder);
}
// 遞歸刪除目錄
public void deleteFolder(File srcFolder) {
File[] fileArray = srcFolder.listFiles();
if (fileArray != null) {
for (File file : fileArray) {
if (file.isDirectory()) {
deleteFolder(file);
} else {
System.out.println(file.getName() + ":" + file.delete());
}
}
System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
}
}
}
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer t = new Timer();
String s = "2014-11-27 15:45:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(s);
t.schedule(new DeleteFolder(), d);
}
}
多線程面試題
- 1:多線程有幾種實現(xiàn)方案,分別是哪幾種?
- 兩種。
- 繼承Thread類
- 實現(xiàn)Runnable接口
擴展一種:實現(xiàn)Callable接口。這個得和線程池結合。
- 2:同步有幾種方式,分別是什么?
- 兩種。
- 同步代碼塊
- 同步方法
-
3:啟動一個線程是run()還是start()?它們的區(qū)別?
- 啟動用:start();
- run():封裝了被線程執(zhí)行的代碼,直接調(diào)用僅僅是普通方法的調(diào)用
- start():啟動線程,并由JVM自動調(diào)用run()方法
-
4:sleep()和wait()方法的區(qū)別
- sleep():必須指時間;不釋放鎖。
- wait():可以不指定時間,也可以指定時間;釋放鎖。
-
5:為什么wait(),notify(),notifyAll()等方法都定義在Object類中
- 因為這些方法的調(diào)用是依賴于鎖對象的,而同步代碼塊的鎖對象是任意鎖。而Object代碼是任意的對象,所以,定義在這里面。
-
6:線程的生命周期圖
- 新建 -- 就緒 -- 運行 -- 死亡
- 新建 -- 就緒 -- 運行 -- 阻塞 -- 就緒 -- 運行 -- 死亡