一,多線程的引入
- 1.什麼是線程
- 線程是程序執行的一條路徑, 一個進程中可以包含多條線程
- 多線程併發執行可以提高程序的效率, 可以同時完成多項工作
- 2.多線程的應用場景
- 紅蜘蛛同時共享屏幕給多個電腦
- 迅雷開啓多條線程一起下載
- QQ同時和多個人一起視頻
- 服務器同時處理多個客戶端請求
二,多線程並行和併發的區別
- 並行就是兩個任務同時運行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
- 併發是指兩個任務都請求運行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在運行。
- 比如我跟兩個網友聊天,左手操作一個電腦跟甲聊,同時右手用另一臺電腦跟乙聊天,這就叫並行。
- 如果用一臺電腦我先給甲發個消息,然後立刻再給乙發消息,然後再跟甲聊,再跟乙聊。這就叫併發。
三,Java程序運行原理和JVM的啓動是多線程的嗎
-
A:Java程序運行原理
- Java命令會啓動java虛擬機,啓動JVM,等於啓動了一個應用程序,也就是啓動了一個進程。該進程會自動啓動一個 “主線程” ,然後主線程去調用某個類的 main 方法。
-
B:JVM的啓動是多線程的嗎
- JVM啓動至少啓動了垃圾回收線程和主線程,所以是多線程的。
四,多線程程序實現的方式1
-
1.繼承Thread
- 定義類繼承Thread
- 重寫run方法
- 把新線程要做的事寫在run方法中
- 創建線程對象
- 開啓新線程, 內部會自動執行run方法
public class Demo2_Thread { /** * @param args */ public static void main(String[] args) { MyThread mt = new MyThread(); //4,創建自定義類的對象 mt.start(); //5,開啓線程 for(int i = 0; i < 3000; i++) { System.out.println("bb"); } } } class MyThread extends Thread { //1,定義類繼承Thread public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執行的代碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } } }
五,多線程程序實現的方式2
-
2.實現Runnable
- 定義類實現Runnable接口
- 實現run方法
- 把新線程要做的事寫在run方法中
- 創建自定義的Runnable的子類對象
- 創建Thread對象, 傳入Runnable
-
調用start()開啓新線程, 內部會自動調用Runnable的run()方法
-
public class Demo3_Runnable { /** * @param args */ public static void main(String[] args) { MyRunnable mr = new MyRunnable(); //4,創建自定義類對象 //Runnable target = new MyRunnable(); Thread t = new Thread(mr); //5,將其當作參數傳遞給Thread的構造函數 t.start(); //6,開啓線程 for(int i = 0; i < 3000; i++) { System.out.println("bb"); } } } class MyRunnable implements Runnable { //1,自定義類實現Runnable接口 @Override public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執行的代碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } }
六,實現Runnable的原理
- 查看源碼
- 1,看Thread類的構造函數,傳遞了Runnable接口的引用
- 2,通過init()方法找到傳遞的target給成員變量的target賦值
- 3,查看run方法,發現run方法中有判斷,如果target不爲null就會調用Runnable接口子類對象的run方法
七,兩種方式的區別(掌握)
-
查看源碼的區別:
- a.繼承Thread : 由於子類重寫了Thread類的run(), 當調用start()時, 直接找子類的run()方法
- b.實現Runnable : 構造函數中傳入了Runnable的引用, 成員變量記住了它, start()調用run()方法時內部判斷成員變量Runnable的引用是否爲空, 不爲空編譯時看的是Runnable的run(),運行時執行的是子類的run()方法
-
繼承Thread
- 好處是:可以直接使用Thread類中的方法,代碼簡單
- 弊端是:如果已經有了父類,就不能用這種方法
- 實現Runnable接口
- 好處是:即使自己定義的線程類有了父類也沒關係,因爲有了父類也可以實現接口,而且接口是可以多實現的
- 弊端是:不能直接使用Thread中的方法需要先獲取到線程對象後,才能得到Thread的方法,代碼複雜
八,匿名內部類實現線程的兩種方式
-
繼承Thread類
new Thread() { //1,new 類(){}繼承這個類
public void run() { //2,重寫run方法
for(int i = 0; i < 3000; i++) { //3,將要執行的代碼,寫在run方法中
System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}
}.start();
實現Runnable接口
new Thread(new Runnable(){ //1,new 接口(){}實現這個接口
public void run() { //2,重寫run方法
for(int i = 0; i < 3000; i++) { //3,將要執行的代碼,寫在run方法中
System.out.println("bb");
}
}
}).start();
九,獲取名字和設置名字(掌握)
- 1.獲取名字
- 通過getName()方法獲取線程對象的名字
-
2.設置名字
- 通過構造函數可以傳入String類型的名字
new Thread("xxx") { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa"); } } }.start(); new Thread("yyy") { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(this.getName() + "....bb"); } } }.start();
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....bb");
}
}
};
t1.setName("芙蓉姐姐");
t2.setName("鳳姐");
t1.start();
t2.start();
十,獲取當前線程的對象
-
Thread.currentThread(), 主線程也可以獲取
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "...aaaaaaaaaaaaaaaaaaaaa");
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "...bb");
}
}
}).start();
Thread.currentThread().setName("我是主線程"); //獲取主函數線程的引用,並改名字
System.out.println(Thread.currentThread().getName()); //獲取主函數線程的引用,並獲取名字
十一,休眠線程Thread.sleep(毫秒,納秒), 控制當前線程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000納秒 1000000000
new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(getName() + "...bb");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
-
setDaemon(), 設置一個線程爲守護線程, 該線程不會單獨執行, 當其他非守護線程都執行結束後, 自動退出
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(getName() + "...bb");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.setDaemon(true); //將t1設置爲守護線程
t1.start();
t2.start();
十三,加入線程
- join(), 當前線程暫停, 等待指定的線程執行結束後, 當前線程再繼續
-
join(int), 可以等待指定的毫秒之後繼續
-
final Thread t1 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { if(i == 2) { try { //t1.join(); //插隊,加入 t1.join(30); //加入,有固定的時間,過了固定時間,繼續交替執行 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "...bb"); } } }; t1.start(); t2.start();
- yield讓出cpu
- setPriority()設置線程的優先級
- 1.什麼情況下需要同步
- 當多線程併發, 有多段代碼同時執行時, 我們希望某一段代碼執行的過程中CPU不要切換到其他線程工作. 這時就需要同步.
- 如果兩段代碼是同步的, 那麼同一時間只能執行一段, 在一段代碼沒執行結束之前, 不會執行另外一段代碼.
-
2.同步代碼塊
- 使用synchronized關鍵字加上一個鎖對象來定義一段代碼, 這就叫同步代碼塊
-
多個同步代碼塊如果使用相同的鎖對象, 那麼他們就是同步的
-
class Printer { Demo d = new Demo(); public static void print1() { synchronized(d){ //鎖對象可以是任意對象,但是被鎖的代碼需要保證是同一把鎖,不能用匿名對象 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } } public static void print2() { synchronized(d){ System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } } }
-
使用synchronized關鍵字修飾一個方法, 該方法中所有的代碼都是同步的
-
class Printer { public static void print1() { synchronized(Printer.class){ //鎖對象可以是任意對象,但是被鎖的代碼需要保證是同一把鎖,不能用匿名對象 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } } /* * 非靜態同步函數的鎖是:this * 靜態的同步函數的鎖是:字節碼對象 */ public static synchronized void print2() { System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } }
- 多線程併發操作同一數據時, 就有可能出現線程安全問題
-
使用同步技術可以解決這種問題, 把操作數據的代碼進行同步, 不要多個線程一起操作
-
public class Demo2_Synchronized { /** * @param args * 需求:鐵路售票,一共100張,通過四個窗口賣完. */ public static void main(String[] args) { TicketsSeller t1 = new TicketsSeller(); TicketsSeller t2 = new TicketsSeller(); TicketsSeller t3 = new TicketsSeller(); TicketsSeller t4 = new TicketsSeller(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t4.setName("窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); } } class TicketsSeller extends Thread { private static int tickets = 100; static Object obj = new Object(); public TicketsSeller() { super(); } public TicketsSeller(String name) { super(name); } public void run() { while(true) { synchronized(obj) { if(tickets <= 0) break; try { Thread.sleep(10);//線程1睡,線程2睡,線程3睡,線程4睡 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "...這是第" + tickets-- + "號票"); } } } }
-
多線程同步的時候, 如果同步代碼嵌套, 使用相同鎖, 就有可能出現死鎖
-
儘量不要嵌套使用
-
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread() {
public void run() {
while(true) {
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "開吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "開吃");
}
}
}
}
}.start();
}
二十一,以前的線程安全的類回顧
- A:回顧以前說過的線程安全問題
- 看源碼:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
- Vector是線程安全的,ArrayList是線程不安全的
- StringBuffer是線程安全的,StringBuilder是線程不安全的
- Hashtable是線程安全的,HashMap是線程不安全的