前言
本期任務:畢向東老師Java視頻教程學習筆記(共計25天)
- 原視頻鏈接:黑馬程序員_畢向東_Java基礎視頻教程
- day01:編寫HelloWorld程序
- day02:操作符與條件選擇語句
- day03:循環語句與函數
- day04:數組
- day07:繼承、抽象類與接口
- day08:多態
- day09:異常處理
- day11:多線程
- day12:線程安全與同步機制
- day13:String類
- day14:集合(ArrayList,LinkedList,HashSet)
- day15:集合(TreeSet)和泛型)
- day16:集合(HashMap、TreeMap)
- day17:集合框架的工具類(Arrays、Collections)
- day18:IO流(字符流讀寫)
- day19:IO流(字節流、轉換流讀寫)
- day20:IO流(File對象)
代碼
/*
java多線程的幾種實現方式
*/
public class ThreadTest {
public static void main(String[] args) {
// 新寫法
new Thread(() -> {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}).start();
/*
創建線程的第一種方式是:繼承Thread類。原因:要覆蓋run方法,定義線程要運行的代碼。
步驟:
1,繼承Thread類。
2,覆蓋run方法。將線程要運行的代碼定義其中。
3,創建Thread類的子類對象,其實就是在創建線程,調用start方法。
*/
// 舊寫法
new Test1().start();
// 匿名內部類
new Thread() {
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
}.start();
// 實現Runable接口
/*
創建線程的第二種方式:實現Runnable接口。
步驟:
1,定義了實現Runnable接口。
2,覆蓋接口的run方法。將多線程要運行的代碼存入其中。
3,創建Thread類的對象(創建線程),並將Runnable接口的子類對象作爲參數傳遞給Thread的構造函數。
爲什麼要傳遞?因爲線程要運行的代碼都在Runnable子類的run方法中存儲。所以要將該run方法所屬的對象
傳遞給Thread。讓Thread線程去使用該對象調用其run方法。
4,調用Thread對象的start方法。開啓線程。
*/
Runnable r = new Runnable() {
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
};
new Thread(r).start();
// 主線程運行
for (int x = 0; x < 1000; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
}
class Test1 extends Thread {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
}
/*
需求:簡單的賣票程序
多個窗口同時賣票
創建線程的第一種方式:繼承Thread類
步驟:
1. 定義類繼承Thread
2. 複寫Thread類中的run方法
3. 調用線程的start方法
該方法兩個作用,啓動線程,調用run方法
創建線程的第二種方式:實現Runable接口
步驟:
1. 定義類實現Runable接口
2. 覆蓋Runable接口中的run方法
將線程要運行的代碼放在該run方法中
3. 通過Thread類建立線程對象
4. 將Runable接口的子類對象作爲實際參數傳遞給Thread類的構造函數
爲什麼要將Runable接口的子類對象傳遞給Thread的構造函數:
因爲,自定義的run方法所屬的對象是Runable接口的子類對象
所以要讓線程去指定隊形的run方法,就必須明確該run方法所屬對象
5. 調用Thread類的start方法開啓線程並調用Runable接口子類的run方法
實現方式與繼承方式有什麼區別呢?
實現方式好處:避免了單繼承的侷限性
在定義線程時,建議使用實現方式
兩種方式區別:
繼承Thread:線程代碼存放在Thread子類的run方法中
實現Runable:線程代碼存放在接口的子類的run方法中
使用Thread.sleep()製造線程不安全,使用這個方法,會拋出InterruptException異常
由於存在線程10ms的休眠時間,使得出現tick爲0,甚至-1、-2的錯票情形,多線程運行存在安全問題
問題原因:
當多條語句在操作同一個線程共享數據的時候,一個線程對多條語句只執行了一部分,還沒執行完,
另一個線程參與進來執行,導致共享數據出現錯誤。
解決方法:
對多條操作共享數據的語句,只能讓一個線程都執行完。在執行過程中,其他線程不可以參與執行。
Java對於多線程的安全問題提供了專業的解決方式
就是同步代碼塊
synchronized(對象){
需要被同步的代碼
}
對象如同鎖,持有鎖的線程可以在同步中執行。
沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因爲沒有獲取鎖
火車上的衛生間 --- 經典。
同步的前提:
1. 必須要有兩個或兩個以上的線程。
2. 必須是多個線程使用同一個鎖
3. 必須保證同步中之惡能有一個線程在運行。
好處:解決了線程安全問題
弊端:多個線程需要判斷鎖,較爲消耗資源
同步函數也是可以解決線程安全的,直接在函數中加入synchronized限制符
同步函數用的是哪個鎖呢?
函數需要被對象調用,那麼函數都有一個所屬對象引用,就是this
所以同步函數使用的鎖是this
通過程序進行驗證
使用兩個線程來買票
一個線程在同步代碼塊中
一個線程在同步函數中
都在執行買票動作
如果同步函數被靜態修飾後,使用的鎖是什麼呢?
通過驗證,發現不是this,因爲靜態方法中不可以定義this
靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。
類名.class 該對象的類型是class
靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象:類名.class
*/
// 實現Runable
class Ticket implements Runnable {
private static int tick = 1000;
Object obj = new Object();
// 同步函數
public synchronized void show() {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ":" + tick--);
}
}
public void run1() {
while (true)
show();
}
// 同步代碼塊
public void run() {
while (true) {
synchronized (this) {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ":" + tick--);
}
}
}
}
}
// 繼承Thread
class Ticket1 extends Thread {
private static int tick = 100;
public void run() {
while (tick > 0) {
System.out.println(Thread.currentThread().getName() + ":" + tick--);
}
}
}
public class TicketDemo {
public static void main(String[] args) {
// 實現Runable
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
/*
// 繼承Thread
Ticket1 t1 = new Ticket1();
Ticket1 t2 = new Ticket1();
Ticket1 t3 = new Ticket1();
Ticket1 t4 = new Ticket1();
t1.start();
t2.start();
t3.start();
t4.start();
*/
}
}
/*
單例設計模式
1. 餓漢式
class Single{
private static final Single s = new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}
2. 懶漢式
延遲加載,
多線程訪問時會出現線程安全問題,
可使用同步鎖解決, 同步函數和同步代碼塊都可,
使用雙重判斷可解決效率問題,
加同步的使用,使用的鎖是該類所屬的字節碼文件對象
*/
class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s == null) {
s = new Single();
}
}
}
return s;
}
}
/*
死鎖
同步中嵌套同步。
以下爲死鎖的一個實例
*/
class Test implements Runnable {
private boolean flag;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {
System.out.println("if lockb");
}
}
} else {
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {
System.out.println("else locka");
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
public class DeadLockDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}