摘要:一個進程如果有多條執行路徑,則稱為多線程程序。這可能拋出在當前線程中。考慮多線程的數據安全問題是否是多線程環境。當前線程必須擁有此對象監視器。此方法導致當前線程稱之為將其自身放置在對象的等待集中,然后放棄此對象上的所有同步要求。
這是劉意老師的JAVA基礎教程的筆記
講的賊好,附上傳送門
傳智風清揚-超全面的Java基礎
一、線程的引入 1.多線程概述進程
a.正在運行的程序,是系統進行資源分類和調用的獨立單位。
b.每個進程都有它自己的內存空間和系統資源。
線程
a.是進程中的單個順序控制流,是一條執行路徑。
b.一個進程如果只有一條路徑,則稱為單線程程序。
c.一個進程如果有多條執行路徑,則稱為多線程程序。
小結
線程多的進程搶到CPU執行權的概率大,但是仍具有隨機性。
Java命令會啟動Java虛擬機,啟動JVM,等于啟動了一個應用程序,也就是啟動了一個進程。該進程會自動啟動一個“主線程”,然后主線程去調用某個類的main方法。
3.并發和并行并發
物理上的同時發生,并發是在同一個時間點上同時發生。
并行
邏輯上的同時發生,并行是在同一個時間段上同時發生。
創建新執行線程有兩種方法。一種方法是將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。接下來可以分配并啟動該子類的實例。start方法是先啟動線程,然后由JVM調用線程的run方法。
public class Demo { public static void main(String[] args) { // TODO Auto-generated method stub MyThread my1=new MyThread(); MyThread my2=new MyThread(); my1.start();//啟動一個進程 my2.start(); } } class MyThread extends Thread{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<1000;i++){ System.out.println(i); } } }2.線程的基本使用
獲取名稱
public class Demo { public static void main(String[] args) { // TODO Auto-generated method stub MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); my1.start(); my2.start(); } } class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 1000; i++) { System.out.println(getName() + ":" + i); } } }
public class Demo { public static void main(String[] args) { // TODO Auto-generated method stub Thread main_thread = Thread.currentThread(); System.out.println(main_thread.getName()); } }
設置線程名稱
可以通過構造函數,也可以通過setName方法
public class Demo { public static void main(String[] args) { // TODO Auto-generated method stub // MyThread my1 = new MyThread(); // MyThread my2 = new MyThread(); // my1.setName("my1"); // my2.setName("my2"); MyThread my1 = new MyThread("my1"); MyThread my2 = new MyThread("my2"); my1.start(); my2.start(); } } class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 1000; i++) { System.out.println(getName() + ":" + i); } } public MyThread(){ super(); } public MyThread(String name){ super(name); } }
線程調度
設置優先級,但是線程優先級也只是標識線程獲得CPU控制權的幾率高。
public class Demo { public static void main(String[] args) { // TODO Auto-generated method stub MyThread my1 = new MyThread("my1"); MyThread my2 = new MyThread("my2"); System.out.println(my1.getName()+"---"+my1.getPriority()); System.out.println(my2.getName()+"---"+my2.getPriority()); my1.setPriority(1); my2.setPriority(10); my1.start(); my2.start(); } } class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 1000; i++) { System.out.println(getName() + ":" + i); } } public MyThread(String name){ super(name); }
線程休眠
public class Demo { public static void main(String[] args) { // TODO Auto-generated method stub MyThread my1 = new MyThread("my1"); MyThread my2 = new MyThread("my2"); my1.start(); my2.start(); } } class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 1000; i++) { System.out.println(getName() + ":" + i); try { Thread.sleep(1000);//這個異常只能try-catch處理,因為父類的run方法沒有拋出異常,子類重寫方法也不能拋出異常 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public MyThread(String name){ super(name); } }
線程加入
指的是調用join方法的線程執行完了,下面的程序才能執行。
public static void main(String[] args) { // TODO Auto-generated method stub MyThread my1 = new MyThread("my1"); MyThread my2 = new MyThread("my2"); MyThread my3 = new MyThread("my3"); my1.start(); try { my1.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } my2.start(); try { my2.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } my3.start(); }
join的用法可以參考:java多線程 join方法以及優先級方法,Java多線程中join方法的理解。
(太長不看版= =:join方法就是要等這個自己這個線程走完了,調用啟動自己這個線程的主線程才能繼續往下走,其他線程不管,具體的可以試試下面的代碼)
public class Demo { public static void main(String[] args) { MyThread my1 = new MyThread("my1"); MyThread my2 = new MyThread("my2"); MyThread my3 = new MyThread("my3"); my1.start(); my2.start(); try { my1.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } for(int i=0;i<10000;i++){ System.out.println("main"); } my3.start(); } }
大概就這個意思
線程禮讓
只能讓線程的執行更加和諧,但不能保證絕對謙讓。
public class Demo { public static void main(String[] args) { MyThread my1 = new MyThread("my1"); MyThread my2 = new MyThread("my2"); MyThread my3 = new MyThread("my3"); my1.start(); my2.start(); // my3.start(); } } class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 1000; i++) { System.out.println(getName() + ":" + i); Thread.yield(); } } public MyThread(String name) { super(name); } }
守護線程
將該線程標記為守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。該方法必須在啟動線程前調用。該方法首先調用該線程的 checkAccess 方法,且不帶任何參數。這可能拋出 SecurityException(在當前線程中)。
拋出:IllegalThreadStateException - 如果該線程處于活動狀態。
SecurityException - 如果當前線程無法修改該線程。
public class Demo { public static void main(String[] args) { MyThread my1 = new MyThread("my1"); MyThread my2 = new MyThread("my2"); MyThread my3 = new MyThread("my3"); my1.setDaemon(true); my2.setDaemon(true); my3.setDaemon(true); my1.start(); my2.start(); my3.start(); for(int i=0;i<5;i++){ System.out.println("main"); } } }
中斷線程
stop方法已經不建議使用了。然而Interupt方法我也沒太搞懂= =
import java.util.Date; public class Demo { public static void main(String[] args) { MyThread my1 = new MyThread("my1"); my1.start(); try { Thread.sleep(3000); my1.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("線程終止"); } // my1.stop(); } } class MyThread extends Thread { @Override public void run() { System.out.println("開始執行:" + new Date()); try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("線程終止"); } System.out.println("結束執行:" + new Date()); } public MyThread(String name) { super(name); } }三、線程的狀態 四、線程的使用(第二種方式) 1.多線程實現舉例
創建線程的另一種方法是聲明實現 Runnable 接口的類。該類然后實現 run 方法。然后可以分配該類的實例,在創建 Thread 時作為一個參數來傳遞并啟動。
public class Demo { public static void main(String[] args) { MyRunnable my=new MyRunnable(); Thread t1=new Thread(my); Thread t2=new Thread(my); t1.start(); t2.start(); } } class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }2.那為什么還要有第二種方法呢?
避免了java單繼承帶來的局限性。簡單來說就是我已經有一個父類了,那我想實現多線程就不能在繼承另一個父類了。
適合多個相同程序的代碼去處理同一個資源的情況,線程是線程,程序代碼是程序代碼,數據有效分離開,降低耦合度。體現面向對象的思想。
五、線程的同步性 1.線程同步性概念的引入還是在模擬賣票的情景中,三個窗口賣一百張票,這三個窗口要共享這一百張票的資源。如果運用如下程序,則兩個線程各自從100輸出到1,一共輸出了200行。
public class Demo1 { public static void main(String[] args) { MyThread my1=new MyThread(); MyThread my2=new MyThread(); my1.start(); my2.start(); } } class MyThread extends Thread{ static int x=100;//加靜態修飾符 @Override public void run() { // TODO Auto-generated method stub for(;x>0;x--){ //標記點1 System.out.println(getName()+":"+x); //標記點2 } } }
然而我們發現,上面的代碼結果確實好了一些,但是還是會偶爾出現“賣同一張票”(就是一個數字輸出多次),甚至會出現輸出負數0。這是為什么呢?原因其實都是:程序的執行具有原子性,并且線程的執行是具有隨機性的。當線程1到達標記點1的時候,可能線程2也到達了標記點1,然后當線程1到達標記點2的時候,線程1已經輸出了一個x(比如說是100),但是還沒來得及進行x--操作,這時候cpu資源假如被線程2搶走,執行了輸出語句,這時候由于x--還沒有執行,因此輸出的還是100,所以即使是static了,也有可能輸出兩個一樣的數字,甚至x--也是兩個原子操作,先返回一個x,然后進行x=x-1,可能在線程1執行返回一個i之后,線程2把資源搶跑了,這時候i的值還是沒有變化。出現負數的愿意也是同理,假如現在兩個線程都到了標記1并且x現在等于1,然后線程1開始執行,假設已經執行完了x--操作,那么這個時候x就是0了,然后線程2開始走,這時候線程2輸出的就0了。
上面的代碼即使運用接口的方法創建線程,也有可能出現相同的情況。
考慮多線程的數據安全問題:1.是否是多線程環境。2.是否有共享數據。3.是否有多條數據操作共享數據。
2.1.synchronized
2.1.1.synchronized代碼塊
synchronized修飾的代碼,在一個線程進行的時候,發現這段代碼已經上了鎖,其他線程不能執行。
public class Demo { public static void main(String[] args) { MyRunnable my = new MyRunnable(); Thread t1 = new Thread(my); Thread t2 = new Thread(my); t1.start(); t2.start(); } } class MyRunnable implements Runnable { int x = 100; Object obj = new Object(); @Override public void run() { synchronized (obj) { for (; x > 0; x--) { System.out.println(Thread.currentThread().getName() + ":" + x); } } } }
同步代碼塊的傳入對象可以是任意對象。
public class Demo { public static void main(String[] args) { MyRunnable my = new MyRunnable(); Thread t1 = new Thread(my); Thread t2 = new Thread(my); t1.start(); t2.start(); } } class MyRunnable implements Runnable { int x = 1000; Object obj1 = new Object(); Object obj2 = new Object(); int a = 0; @Override public void run() { if (a % 2 == 0) { synchronized (obj1) { for (; x > 0; x--) { System.out.println(Thread.currentThread().getName() + ":" + x); } } }else{ synchronized (obj2) { for (; x > 0; x--) { System.out.println(Thread.currentThread().getName() + ":" + x); } } } a++; } }
上面這塊代碼也沒出問題,但是我沒明白,明明不是同一個鎖啊,為什么還沒出問題?
2.1.2.synchronized方法
同步方法的默認鎖對象是this。靜態方法的默認鎖是當前對象的類對象(.class對象)。
@Override public synchronized void run() { for (; x > 0; x--) { System.out.println(Thread.currentThread().getName() + ":" + x); } }
2.2.Lock鎖
Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。
public class Demo { public static void main(String[] args) { MyRunnable st = new MyRunnable(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); Thread t3 = new Thread(st); t1.start(); t2.start(); t3.start(); } } class MyRunnable implements Runnable { int tickets = 100; Lock lock = new ReentrantLock(); @Override public void run() { lock.lock(); try {//解決出現如果異常則鎖放不開的問題 for (; tickets > 0;) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + tickets--); } } finally { lock.unlock(); } } }
2.3 死鎖問題
指兩個或者兩個以上的線程在執行過程中因爭奪資源而產生的一種互相等待的現象。
public class Demo { public static void main(String[] args) { Thread t1 = new DeadLock(true); Thread t2 = new DeadLock(false); t1.start(); t2.start(); } } class MyLock{ public static final Object objA=new Object(); public static final Object objB=new Object(); } class DeadLock extends Thread{ boolean flag; public DeadLock(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 objA"); synchronized(MyLock.objA){ System.out.println("else objB"); } } } } }
2.4.等待喚醒機制
public final void wait(long timeout) throws InterruptedException
在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量前,導致當前線程等待。
當前線程必須擁有此對象監視器。
此方法導致當前線程(稱之為 T)將其自身放置在對象的等待集中,然后放棄此對象上的所有同步要求。出于線程調度目的,在發生以下四種情況之一前,線程 T 被禁用,且處于休眠狀態:
其他某個線程調用此對象的 notify 方法,并且線程 T 碰巧被任選為被喚醒的線程。
其他某個線程調用此對象的 notifyAll 方法。
其他某個線程中斷線程 T。
大約已經到達指定的實際時間。但是,如果 timeout 為零,則不考慮實際時間,在獲得通知前該線程將一直等待。
然后,從對象的等待集中刪除線程 T,并重新進行線程調度。然后,該線程以常規方式與其他線程競爭,以獲得在該對象上同步的權利;一旦獲得對該對象的控制權,該對象上的所有其同步聲明都將被恢復到以前的狀態,這就是調用 wait 方法時的情況。然后,線程 T 從 wait 方法的調用中返回。所以,從 wait 方法返回時,該對象和線程 T 的同步狀態與調用 wait 方法時的情況完全相同。
在沒有被通知、中斷或超時的情況下,線程還可以喚醒一個所謂的虛假喚醒 (spurious wakeup)。雖然這種情況在實踐中很少發生,但是應用程序必須通過以下方式防止其發生,即對應該導致該線程被提醒的條件進行測試,如果不滿足該條件,則繼續等待。換句話說,等待應總是發生在循環中。
public final void notify()
喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。
例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。
此方法只應由作為此對象監視器的所有者的線程來調用。通過以下三種方法之一,線程可以成為此對象監視器的所有者:
通過執行此對象的同步實例方法。
通過執行在此對象上進行同步的 synchronized 語句的正文。
對于 Class 類型的對象,可以通過執行該類的同步靜態方法。
public class Demo { public static void main(String[] args) { Student s = new Student(); SetStudent st = new SetStudent(s); GetStudent gt = new GetStudent(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } } class Student { int age; String name; boolean flag; } class SetStudent implements Runnable { Student s; int x; SetStudent(Student s) { this.s = s; } @Override public void run() { for (;; x++) { synchronized (s) { if (s.flag) { try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if (x % 2 == 0) { s.name = "zzz"; s.age = 23; } else { s.name = "xxx"; s.age = 55; } s.flag = true; s.notify();// 喚醒t2,但是喚醒不一定立即執行,還要進行CPU執行權的爭奪。 } } } } class GetStudent implements Runnable { Student s; int x; GetStudent(Student s) { this.s = s; } @Override public void run() { for (;;) { synchronized (s) { if (!s.flag) { try { s.wait();// t2等待了, 立即釋放鎖,醒過來的時候從這里醒來。 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name + ":" + s.age); s.flag = false; s.notify(); } } } }六、線程組 1.線程組的概述和使用
public class Demo { public static void main(String[] args) { method1(); method2(); } static void method1(){ MyRunnable my=new MyRunnable(); Thread t1=new Thread(my); Thread t2=new Thread(my); ThreadGroup tg1=t1.getThreadGroup(); ThreadGroup tg2=t2.getThreadGroup(); System.out.println(tg1.getName()+"---"+tg2.getName()); } static void method2(){ MyRunnable my=new MyRunnable(); ThreadGroup tg=new ThreadGroup("這是一個新的組"); Thread t1=new Thread(tg,my); Thread t2=new Thread(tg,my); System.out.println(t1.getThreadGroup().getName()+"---"+t2.getThreadGroup().getName()); } } class MyRunnable implements Runnable{ @Override public void run() { for(int x=0;x<100;x++){ System.out.println(Thread.currentThread().getName()+":"+x); } } }七、線程池 1.概述和使用
程序啟動一個新線程成本是比較高的,因為它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。
線程池里的每一個線程代碼結束后,并不會死亡,而是再次回到線程池中成為空閑狀態,等待下一個對象來使用。
在JDK5之前,我們必須手動實現自己的線程池,從JDK5開始,Java內置支持線程池。
public class Demo { public static void main(String[] args) { ExecutorService pool=Executors.newFixedThreadPool(2); pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.shutdown(); } } class MyRunnable implements Runnable{ @Override public void run() { for(int x=0;x<100;x++){ System.out.println(Thread.currentThread()+":"+x); } } }2.線程的實現(第三種方法)
public interface Callable
返回結果并且可能拋出異常的任務。實現者定義了一個不帶任何參數的叫做 call 的方法。Callable 接口類似于 Runnable,兩者都是為那些其實例可能被另一個線程執行的類設計的。但是 Runnable 不會返回結果,并且無法拋出經過檢查的異常。
public interface Future
Future 表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,并獲取計算的結果。計算完成后只能使用 get 方法來獲取結果,如有必要,計算完成前可以阻塞此方法。取消則由 cancel 方法來執行。還提供了其他方法,以確定任務是正常完成還是被取消了。一旦計算完成,就不能再取消計算。如果為了可取消性而使用 Future 但又不提供可用的結果,則可以聲明 Future> 形式類型、并返回 null 作為底層任務的結果。
public class Demo { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService pool=Executors.newFixedThreadPool(2); Futuref1=pool.submit(new MyCallable(100)); Future f2=pool.submit(new MyCallable(200)); Integer i1=f1.get(); Integer i2=f2.get(); System.out.println(i1+":"+i2); pool.shutdown(); } } class MyCallable implements Callable { int number; public MyCallable(int number) { this.number=number; } @Override public Integer call() throws Exception { int sum=0; for(int x=0;x 八、匿名內部類實現多線程 public class Demo { public static void main(String[] args){ new Thread(){ @Override public void run() { for(int x=0;x<100;x++){ System.out.println(Thread.currentThread().getName()+"---"+x); } } }.start(); 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(){ @Override public void run() { for(int x=0;x<100;x++){ System.out.println("hello---"+x); } } }){ @Override public void run() { for(int x=0;x<100;x++){ System.out.println("world---"+x); } } }.start(); } }九、定時器 1.概述和使用定時器是一個應用十分廣泛的線程工具,可用于調度多個定時任務以后臺線程的方式執行。在Java中,可以通過Timer和TimerTask類來實現定義調度的功能。
Timer
一種工具,線程用其安排以后在后臺線程中執行的任務。可安排任務執行一次,或者定期重復執行。
與每個 Timer 對象相對應的是單個后臺線程,用于順序地執行所有計時器任務。計時器任務應該迅速完成。如果完成某個計時器任務的時間太長,那么它會“獨占”計時器的任務執行線程。因此,這就可能延遲后續任務的執行,而這些任務就可能“堆在一起”,并且在上述不友好的任務最終完成時才能夠被快速連續地執行。TimerTask
由 Timer 安排為一次執行或重復執行的任務。
public class Demo{ public static void main(String[] args){ Timer t=new Timer(); t.schedule(new MyTask(), 3000); t.schedule(new MyTask(t), 6000); t.schedule(new MyTask(), 9000,200); } } class MyTask extends TimerTask{ Timer t; public MyTask() { } public MyTask(Timer t){ this.t=t; } @Override public void run() { System.out.println("BANG!"); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67511.html
摘要:在設計模式中,所有的設計模式都遵循這一原則。其實就是說在應用程序中,所有的類如果使用或依賴于其他的類,則應該依賴這些其他類的抽象類,而不是這些其他類的具體類。使用設計模式是為了可重用代碼讓代碼更容易被他人理解保證代碼可靠性。 這是劉意老師的JAVA基礎教程的筆記講的賊好,附上傳送門 傳智風清揚-超全面的Java基礎 一、面向對象思想設計原則 1.單一職責原則 其實就是開發人員經常說的高...
摘要:分類根類加載器也被稱為引導類加載器,負責核心類的加載比如等。要想解剖一個類必須先要獲取到該類的字節碼文件對象。 這是劉意老師的JAVA基礎教程的筆記講的賊好,附上傳送門 傳智風清揚-超全面的Java基礎 一、類的加載 1.類初始化的時機 創建類的實例訪問類的靜態變量,或者為靜態變量賦值調用類的靜態方法使用反射方式來強制創建某個類或接口對應的java.lang.Class對象初始化某個類...
摘要:我的學習筆記匯總標簽筆記分為兩大部分和筆記內容主要是對一些基礎特性和編程細節進行總結整理,適合了解基礎語法,想進一步深入學習的人如果覺得不錯,請給,這也是對我的鼓勵,有什么意見歡迎留言反饋目錄基礎鞏固筆記反射基礎鞏固筆記泛型基礎鞏 我的java&javaweb學習筆記(匯總) 標簽: java [TOC] 筆記分為兩大部分:javase和javaweb javase javawe...
摘要:開頭正式開啟我入職的里程,現在已是工作了一個星期了,這個星期算是我入職的過渡期,算是知道了學校生活和工作的差距了,總之,盡快習慣這種生活吧。當時是看的廖雪峰的博客自己也用做爬蟲寫過幾篇博客,不過有些是在前人的基礎上寫的。 showImg(https://segmentfault.com/img/remote/1460000010867984); 開頭 2017.08.21 正式開啟我...
摘要:運行時數據區域的學習,是學習以及機制的基礎,也是深入理解對象創建及運行過程的前提。了解內存區域劃分,是學習概念的前提。 Java 運行時數據區域的學習,是學習 jvm 以及 GC 機制的基礎,也是深入理解 java 對象創建及運行過程的前提。廢話不多說,直接進入正題: 一張圖總結 showImg(https://segmentfault.com/img/bVOMAn?w=685&h=5...
閱讀 1553·2023-04-26 01:36
閱讀 2717·2021-10-08 10:05
閱讀 2774·2021-08-05 09:57
閱讀 1536·2019-08-30 15:52
閱讀 1192·2019-08-30 14:12
閱讀 1310·2019-08-30 11:17
閱讀 3096·2019-08-29 13:07
閱讀 2415·2019-08-29 12:35