線程
。 線程
:是程序的執行單元,執行路徑。是程序使用CPU的最基本單位。 單線程
:如果程序只有一條執行路徑。 多線程
:如果程序有多條執行路徑。
並行
和並發
。 並行
是邏輯上同時發生,指在某一個時間內同時運行多個程序。 並發
是物理上同時發生,指在某一個時間點同時運行多個程序。 我們可以實現真正意義上的並發,例如:多個CPU就可以實現,不過得知道如何調度和控制它們。
java 命令會啟動 java 虛擬機,啟動 JVM,等於啟動了一個應用程序,也就是啟動了一個進程。該進程會自動啟動一個 “主線程” ,然後主線程去調用某個類的 main 方法。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。
思考:jvm虛擬機的啟動是單線程的還是多線程的?
JVM啟動至少啟動了垃圾回收線程和主線程,所以是多線程的。步驟:
A:自定義類MyThread繼承Thread類。 B:MyThread類裡面重寫run() C:創建對象D:啟動線程
面試題:run()和start()的區別? run():僅僅是封裝被線程執行的代碼,直接調用是普通方法start():首先啟動了線程,然後再由jvm去調用該線程的run()方法。
面試題:為什麼重寫run()方法?`
不是類中的所有代碼都需要被線程執行的。為了區分哪些代碼能夠被線程執行,java提供了Thread類中的run()用來包含那些被線程執行的代碼。
/*
* 需求:我們要實現多線程的程序。
*/
public class MyThreadDemo {
public static void main(String[] args) {
// 創建線程對象
// MyThread my = new MyThread();
// // 啟動線程
// my.run();
// my.run();
// 調用run()方法為什麼是單線程的呢?
// 因為run()方法直接調用其實就相當於普通的方法調用,所以你看到的是單線程的效果
// 要想看到多線程的效果,就必須說說另一個方法:start()
// 面試題:run()和start()的區別?
// run():僅僅是封裝被線程執行的代碼,直接調用是普通方法
// start():首先啟動了線程,然後再由jvm去調用該線程的run()方法。
// MyThread my = new MyThread();
// my.start();
// // IllegalThreadStateException:非法的線程狀態異常
// // 為什麼呢?因為這個相當於是my線程被調用了兩次。而不是兩個線程啟動。
// my.start();
// 創建兩個線程對象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
// 自己寫代碼
// System.out.println("好好學習,天天向上");
// 一般來說,被線程執行的代碼肯定是比較耗時的。所以我們用循環改進
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():返回當前正在執行的線程對象
* Thread.currentThread().getName()
*/
public class MyThreadDemo {
public static void main(String[] args) {
// 創建線程對象
//無參構造+setXxx()
// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
// //調用方法設置名稱
// 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():返回當前正在執行的線程對象
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);
}
}
}
分時調度模型
所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間片 搶占式調度模型
優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。 Java使用的是搶占式調度模型。
public class ThreadPriority extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*
* 注意:
* IllegalArgumentException:非法參數異常。
* 拋出的異常表明向方法傳遞了一個不合法或不正確的參數。
*
*/
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("林平之");
// 獲取默認優先級
// System.out.println(tp1.getPriority());
// System.out.println(tp2.getPriority());
// System.out.println(tp3.getPriority());
// 設置線程優先級
// tp1.setPriority(100000);
//設置正確的線程優先級
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
中斷線程
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():暫停當前正在執行的線程對象,並執行其他線程。
* 讓多個線程的執行更和諧,但是不能靠它保證一人一次。
*/
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 虛擬機退出。 該方法必須在啟動線程前調用。
*
* 游戲:坦克大戰。
*/
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("開始執行:" + new Date());
// 我要休息10秒鐘,親,不要打擾我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("線程被終止了");
}
System.out.println("結束執行:" + new Date());
}
}
/*
* public final void stop():讓線程停止,過時了,但是還可以使用。
* public void interrupt():中斷線程。 把線程的狀態終止,並拋出一個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();
}
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由於實現接口的方式就不能直接使用Thread類的方法了,但是可以間接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
/*
* 方式2:實現Runnable接口
*/
public class MyRunnableDemo {
public static void main(String[] args) {
// 創建MyRunnable類的對象
MyRunnable my = new MyRunnable();
// 創建Thread類的對象,並把C步驟的對象作為構造參數傳遞
// 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();
}
}
public class SellTicket extends Thread {
// 定義100張票
// private int tickets = 100;
// 為了讓多個線程對象共享這100張票,我們其實應該用靜態修飾
private static int tickets = 100;
@Override
public void run() {
// 定義100張票
// 每個線程進來都會走這裡,這樣的話,每個線程對象相當於買的是自己的那100張票,這不合理,所以應該定義到外面
// int tickets = 100;
// 是為了模擬一直有票
while (true) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
}
}
}
}
/*
* 某電影院目前正在上映賀歲大片(紅高粱,少林寺傳奇藏經閣),共有100張票,而它有3個售票窗口售票,請設計一個程序模擬該電影院售票。
* 繼承Thread類來實現。
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 創建三個線程對象
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--) + "張票");
}
}
}
}
/*
* 實現Runnable接口的方式實現
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個線程對象
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 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--) + "張票");
// // 理想狀態:
// // 窗口1正在出售第100張票
// // 窗口2正在出售第99張票
// // 但是呢?
// // CPU的每一次執行必須是一個原子性(最簡單基本的)的操作。
// // 先記錄以前的值
// // 接著把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
}
}
}
}
public class SellTicket implements Runnable {
// 定義100張票
private int tickets = 100;
//創建鎖對象
private Object obj = new Object();
@Override
public void run() {
while (true) {
// t1,t2,t3都能走到這裡
// 假設t1搶到CPU的執行權,t1就要進來
// 假設t2搶到CPU的執行權,t2就要進來,發現門是關著的,進不去。所以就等著。
// 門(開,關)
synchronized (obj) { // 發現這裡的代碼將來是會被鎖上的,所以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) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個線程對象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啟動線程
t1.start();
t2.start();
t3.start();
}
}
任意對象
。 B:同步方法this
C:靜態同步方法類的字節碼文件對象
。(反射會講)
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) {//靜態方法的安全鎖,若是普通同步方法則為: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--) + "張票 ");
// }
// }
//靜態同步方法:
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) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個線程對象
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 v = new Vector();
Hashtable h = new Hashtable();
// Vector是線程安全的時候才去考慮使用的,但是我還說過即使要安全,我也不用你
// 那麼到底用誰呢?
// public static List synchronizedList(List list)
List list1 = new ArrayList();// 線程不安全
List list2 = Collections
.synchronizedList(new ArrayList()); // 線程安全
}
}