一硬梁、線程創(chuàng)建的兩種方式
1.1繼承Thread
class MyThread extends Thread{
@Override
public void run() {
}
}
//創(chuàng)建線程
MyThread myThread = new MyThread();
//啟動線程
myThread.start();
1.2實現(xiàn)Runnable接口
class MyThread implements Runnable{
@Override
public void run() {
}
}
MyThread mt = new MyThread();
Thread td = new Thread(mt);
td.start();
1.3比較
- Runnable方式可以避免Thread方式由于Java單繼承特性帶來的缺陷
- Runnable的代碼可以被多個線程(Thread實例)共享味廊,適合于多個線程處理同一資源的情況
二编饺、賣火車票
2.1 Thread實現(xiàn)
public class TicketsThread {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("一號窗口");
MyThread myThread2 = new MyThread("二號窗口");
MyThread myThread3 = new MyThread("三號窗口");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
class MyThread extends Thread{
private int ticketsCont = 5 ;
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
while (ticketsCont > 0) {
//有票就賣一張
ticketsCont--;
System.out.println(name+"賣了一張票,剩余票數(shù)為"+ ticketsCont);
}
}
}
運行結(jié)果
二號窗口賣了一張票艘儒,剩余票數(shù)為4
二號窗口賣了一張票,剩余票數(shù)為3
二號窗口賣了一張票,剩余票數(shù)為2
二號窗口賣了一張票咱枉,剩余票數(shù)為1
二號窗口賣了一張票,剩余票數(shù)為0
三號窗口賣了一張票徒恋,剩余票數(shù)為4
三號窗口賣了一張票蚕断,剩余票數(shù)為3
三號窗口賣了一張票,剩余票數(shù)為2
三號窗口賣了一張票入挣,剩余票數(shù)為1
三號窗口賣了一張票亿乳,剩余票數(shù)為0
一號窗口賣了一張票,剩余票數(shù)為4
一號窗口賣了一張票径筏,剩余票數(shù)為3
一號窗口賣了一張票葛假,剩余票數(shù)為2
一號窗口賣了一張票,剩余票數(shù)為1
一號窗口賣了一張票滋恬,剩余票數(shù)為0
結(jié)果并不能滿意聊训。沒有對票數(shù)這個多線程共同訪問的數(shù)據(jù)進行同步,使得每一個線程都有自己的一個數(shù)據(jù)源恢氯。
2.2Runnable實現(xiàn)
public class TicketsRunnable {
public static void main(String[] args) {
MyThread mThread = new MyThread();
Thread thread1 = new Thread(mThread,"窗口1");
Thread thread2 = new Thread(mThread,"窗口2");
Thread thread3 = new Thread(mThread,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable{
private int ticketsCont = 5;
@Override
public void run() {
while (ticketsCont > 0) {
//有票就賣一張
ticketsCont--;
System.out.println(Thread.currentThread().getName()+"賣了一張票带斑,剩余票數(shù)為"+ ticketsCont);
}
}
}
運行結(jié)果
窗口1賣了一張票,剩余票數(shù)為4
窗口1賣了一張票勋拟,剩余票數(shù)為3
窗口3賣了一張票勋磕,剩余票數(shù)為1
窗口2賣了一張票,剩余票數(shù)為0
窗口1賣了一張票敢靡,剩余票數(shù)為2
仍然不能滿足要求挂滓,這種不符合常理的結(jié)果,沒有達到預想中的4 3 2 1 0
這就是線程的交互執(zhí)行導致的;舉個例子:
線程1先執(zhí)行賣了1張票,也即是票--1,現(xiàn)在票為4,但是這個線程還沒沒有來得及在控制臺打印出剩余多少票,線程又搶到了CPU資源執(zhí)行,線程2又把票--1;此時票為3,線程2輸出票就為3,線程2執(zhí)行完了后,線程1又再次獲得CPU資源,繼續(xù)把剛剛沒有輸出的話輸出,但是此時票已經(jīng)為3了,于是又輸出了3。
看過多線程的小伙伴應該不難理解,這就是線程不安全,想要保證輸出結(jié)果,可以使用synchronized關鍵字來解決
2.3 實現(xiàn)
public class TicketsRunnable {
public static void main(String[] args) {
MyThread mThread = new MyThread();
Thread thread1 = new Thread(mThread,"窗口1");
Thread thread2 = new Thread(mThread,"窗口2");
Thread thread3 = new Thread(mThread,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable {
private int ticketsCont = 10;
@Override
public void run() {
while (ticketsCont > 0) {
//有票就賣一張
synchronized (this) {
if (ticketsCont > 0) {
ticketsCont--;
System.out.println(Thread.currentThread().getName()+"賣了一張票醋安,剩余票數(shù)為"+ ticketsCont);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
用同步鎖來實現(xiàn)杂彭,注意在鎖里也加入判斷語句,不然可能會出現(xiàn)線程1進入了while循環(huán)吓揪,然后被搶去了線程亲怠,最后賣出了51張票。
運行結(jié)果
窗口1賣了一張票柠辞,剩余票數(shù)為9
窗口3賣了一張票团秽,剩余票數(shù)為8
窗口3賣了一張票,剩余票數(shù)為7
窗口2賣了一張票,剩余票數(shù)為6
窗口2賣了一張票习勤,剩余票數(shù)為5
窗口2賣了一張票踪栋,剩余票數(shù)為4
窗口3賣了一張票,剩余票數(shù)為3
窗口3賣了一張票图毕,剩余票數(shù)為2
窗口1賣了一張票夷都,剩余票數(shù)為1
窗口3賣了一張票,剩余票數(shù)為0
三予颤、線程的生命周期
3.1 創(chuàng)建
Thread thd = new Thread();
3.2 就緒
start()被調(diào)用即進入就緒狀態(tài)囤官,線程被加入到了線程隊列中,等待CPU服務蛤虐,具備了運行的條件党饮,但不一定已經(jīng)開始運行了
3.3 運行
獲取到了CPU服務,執(zhí)行run()邏輯
3.4 阻塞
受到阻塞事件的影響驳庭,由于某種原因刑顺,讓出cpu資源,如sleep()方法
3.5 終止
run()執(zhí)行完畢
四饲常、守護線程
4.1 線程分類
- 用戶線程:
看得到的蹲堂,主線程、連接網(wǎng)絡的子線程等不皆。 - 守護線程:
運行在后臺贯城,為用戶線程服務熊楼。
特點:一旦所有用戶線程結(jié)束運行霹娄,守護線程會隨著JVM一起結(jié)束工作。
應用:數(shù)據(jù)庫連接池中的監(jiān)測線程 & JVM虛擬機啟動后的監(jiān)測線程 & 垃圾回收線程
4.2注意
- 在start()前調(diào)用方法 setDaemon(true)
- 守護線程中產(chǎn)生的新線程也是守護線程
- 不是所有的任務都可以分配給守護線程來執(zhí)行鲫骗,比如讀寫操作犬耻、邏輯運算。
4.3守護線程中進行讀寫操作
public class DaemonThreadDemo {
public static void main(String[] args) {
System.out.println("進入主線程" + Thread.currentThread().getName());
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
Scanner scanner = new Scanner(System.in);
scanner.next();
System.out.println("退出主線程" + Thread.currentThread().getName());
}
}
class DaemonThread implements Runnable {
public void run() {
System.out.println("程序進入了守護線程" + Thread.currentThread().getName());
try {
writeToFile();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序退出了守護線程" + Thread.currentThread().getName());
}
private void writeToFile() throws Exception {
File file = new File("G:"+ File.separator+ "demo.txt");
//true 追加操作执泰,不是覆蓋操作
OutputStream os = new FileOutputStream(file,true);
int count = 0;
while (count < 999) {
os.write(("\r\nword"+count).getBytes());
System.out.println("守護線程"+ Thread.currentThread().getName()+"向文件中寫入了word"+ count++);
Thread.sleep(1000);
}
os.close();
}
}
運行結(jié)果
進入主線程main
程序進入了守護線程Thread-0
守護線程Thread-0向文件中寫入了word0
守護線程Thread-0向文件中寫入了word1
守護線程Thread-0向文件中寫入了word2
8
退出主線程main
守護線程未正常退出