併發和並行是即類似又有區別:(微觀)java
並行:指兩個或多個事件在同一時刻發生; 強調的是時間點.程序員
併發:指兩個或多個事件在同一時間段內發生; 強調的是時間段.windows
進程:有獨立的內存空間,進程中的數據存放空間(堆空間和棧空間)是獨立的,至少有一個線程。 線程:堆空間是共享的,棧空間是獨立的,線程消耗的資源也比進程小,相互之間能夠影響的,又稱爲輕型進程或進程元。 由於一個進程中的多個線程是併發運行的,那麼從微觀角度上考慮也是有前後順序的,那麼哪一個線程執行徹底取決於CPU調度器,程序員是控制不了的。咱們能夠把多線程併發性看做是多個線程在瞬間搶CPU資源,誰搶到資源誰就運行,這也造就了多線程的隨機性。安全
建立進程的方式有兩種,我以windows上的記事本爲例:網絡
package com.StadyJava.day14; import java.io.IOException; public class ProcessDemo { public static void main(String[] args) throws Exception { //方法1:使用Runtime Runtime runtime=Runtime.getRuntime(); runtime.exec("notepad"); //方法2:ProcessBuild ProcessBuilder processBuilder=new ProcessBuilder("notepad"); processBuilder.start(); } }
運行代碼,此時會生成兩個記事本。多線程
我也嘗試過啓動其餘的程序,可是計算器不認識,只有notepad這種計算機自帶的才認識。併發
若是想要啓動其餘的程序,只能寫上絕對路徑app
ProcessBuilder processBuilder=new ProcessBuilder("E:\\shuyunquan\\TIM\\Bin\\TIM.exe"); processBuilder.start();
這樣我測試了,是能夠的,可是沒意思,這樣你寫了程序發佈了也搞怪不了,別人電腦上的路徑和你不同。。。ide
接下來說解線程。性能
下圖是線程的一些經常使用的方法
建立進程的兩個方法已經知道了,接下來看看建立線程的兩個方法
package com.StadyJava.day14Thread; import java.lang.Thread; class Music extends Thread{ public void run() { for (int i = 0; i < 50; i++) { System.out.println("聽音樂"+i); } } } public class MusicThread { public static void main(String[] args) { for (int i = 0; i < 50; i++) { System.out.println("打遊戲"+i); if (i == 10) { Music music=new Music(); music.start(); } } } }
注意,繼承了Thread類以後要重寫run方法,並且在調用的時候,只能使用start方法,不能調用run方法,切記!
輸出的結果是在打遊戲10以後,下面的打遊戲和聽音樂都是隨機出現,由於主線程main和線程music在搶佔資源,誰搶到誰執行,因此輸出的結果是隨機的
(注意!我使用Idea輸出的結果是順序的,不是隨機的。我同窗和我同樣的代碼使用Eclipse結果是隨機的,我本身複製到Eclipse以後偶爾隨機,Idea是死都不隨機,這個我解決不了,但願之後知道緣由或者有人告訴我)
其實Thread類也是實現了Runnable接口的,因此這個方法我感受是本源??
package com.StadyJava.day14Thread; import java.lang.Runnable; class MusicRun implements Runnable{ public void run() { for (int i = 0; i < 50; i++) { System.out.println("聽音樂"+i); } } } public class MusicRunnable { public static void main(String[] args) { for (int i = 0; i < 50; i++) { System.out.println("打遊戲"+i); if (i == 10) { Runnable music=new MusicRun(); Thread thread=new Thread(music); thread.start(); } } } }
沒什麼特別的,就是生成一個runnable的對象,而後Thread對象加載進這個Runnable對象,最後start方法調用一下就完事了。這個結果個人Idea依然是顯示的不符合個人指望的。
上面兩種建立線程的方式是最經常使用的方式,通常也就足夠了,下面介紹一下不怎麼經常使用的
匿名內部類的格式:new 接口(){} 應該是這樣的,待補充
其實匿名內部類建立線程仍是使用上面的兩種方式,只不過那個Music類我不須要去定義了,這就是匿名內部類,看代碼吧
package com.StadyJava.day14Thread; import java.lang.Runnable; class MusicRun implements Runnable{ public void run() { for (int i = 0; i < 50; i++) { System.out.println("聽音樂"+i); } } } public class MusicRunnable { public static void main(String[] args) { for (int i = 0; i < 50; i++) { System.out.println("打遊戲"+i); if (i == 10) { //匿名內部類的形式1,使用接口 new Thread(new Runnable() { public void run() { for (int i = 0; i < 50; i++) { System.out.println("聽音樂"+i); } } }).start(); //匿名內部類的形式2,使用繼承類 new Thread(){ public void run() { for (int i = 0; i < 50; i++) { System.out.println("聽音樂"+i); } } }.start(); } } } }
這回,輸出的結果總算是符合個人預期了,多是線程變成了3個,搶佔資源激烈了些。。。
學了兩種常見的建立線程的方法以後,他們之間有什麼區別呢?
我寫個例子,吃蘋果大賽,3我的參加比賽,先使用繼承Thread類建立線程的方式,代碼以下:
package com.StadyJava.day14; class Person extends java.lang.Thread{ private int num=50; public Person(String name){ super(name); } public void run() { for (int i = 0; i < 50; i++) { if (num >0) { System.out.println(super.getName()+"吃了編號爲"+num--+"的蘋果"); } } } } public class EatAppleThread { public static void main(String[] args) { //建立3我的,去參加吃蘋果大賽 new Person("許嵩").start(); new Person("林俊杰").start(); new Person("蜀雲泉").start(); } }
這個輸出的結果,就是許嵩,林俊杰,蜀雲泉每一個人都吃了50 個🍎。緣由是由於我new了三個對象,沒個對象都有num變量,他們之間互不干擾,以下圖所示:
這樣很很差,個人吃蘋果大賽是總共50個🍎,大家3我的來吃就完事了。咱們看看實現Runnable接口建立線程的方式是怎麼樣的,代碼以下:
package com.StadyJava.day14; class Apple implements Runnable{ private int num=50; public void run() { for (int i = 0; i < 50; i++) { if (num >0) { //返回當前線程的引用Thread.currentThread(),再獲取名字 System.out.println(Thread.currentThread().getName()+"吃了編號爲"+num--+"的蘋果"); } } } } public class EatAppleRunnable { public static void main(String[] args) { //建立3我的,去參加吃蘋果大賽 Apple apple=new Apple(); new Thread(apple,"許嵩").start(); new Thread(apple,"林俊杰").start(); new Thread(apple,"蜀雲泉").start(); } }
此次由於我傳入的都是apple這個對象,這一個對象有50個🍎,建立了3個線程,此次輸出的結果是OK的,緣由以下圖所示,3個線程共用了一個蘋果對象。
從這個吃蘋果比賽的例子中能夠總結一下繼承Thread類建立線程的方式和實現Runnable接口建立線程的方式的區別:
綜合上面的區別對比,咱們的這個比賽。看來只能使用實現Runnable接口建立線程的方式來實現了。推薦之後建立線程,都使用實現Runnable接口的方式。
拿上面寫的實現接口建立線程的吃蘋果比賽爲例,這個是存在線程安全的,咱們能夠寫一個線程休眠來看看,這樣更容易觀察。
須要說明,Thread.sleep線程休眠,須要使用try catch來撲捉異常,不能使用throw拋出,由於Runnable接口裏面的run方法自己就沒有throw的寫法
package com.day14; class Apple implements Runnable{ private int num=50; public void run() { for (int i = 0; i < 50; i++) { if (num >0) { //線程休眠,必須使用try catch撲捉異常 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //返回當前線程的引用Thread.currentThread(),再獲取名字 System.out.println(Thread.currentThread().getName()+"吃了編號爲"+num--+"的蘋果"); } } } } public class EatAppleRunnable { public static void main(String[] args) { //建立3我的,去參加吃蘋果大賽 Apple apple=new Apple(); new Thread(apple,"許嵩").start(); new Thread(apple,"林俊杰").start(); new Thread(apple,"蜀雲泉").start(); } }
我就加了一個線程休眠,咱們如今來看看輸出的結果是什麼樣的
竟然有0,還有-1 不是已經寫了if(num>0)嗎?出現這種狀況的緣由是,許嵩,林俊杰,蜀雲泉這三個線程都在num>0的時候,例如num=1的時候,他們仨都拿到了資源,均可以去執行run方法
這個時候就會出現這種狀況。通俗一點講許嵩拿到了最後一個蘋果,可是沒吃,林俊杰和蜀雲泉來搶。因爲這個蘋果不具有獨佔性,因此最後一個蘋果被許嵩,林俊杰,蜀雲泉都咬了一口。他們都宣稱本身吃了蘋果,因此就會出現0和-1的狀況。這樣顯然是不容許的。這就是
多線程併發的訪問一個資源產生的安全問題
要想解決這個問題,就必需要保證蘋果的數量減小必須保證同步,許嵩拿了最後一個蘋果,這個時候蘋果數量同步爲0,剩下的人不能再搶了。
許嵩這個線程在操做的時候,林俊杰和蜀雲泉只能等着。等許嵩操做完了。許嵩,林俊杰和蜀雲泉纔有機會去從新搶資源。
意思是這樣,方法有3種
方法1.同步代碼塊
方法2.同步方法
方法3.鎖機制(Lock)
語法:
synchronized(同步鎖){
須要同步操做的代碼
}
同步鎖:爲了保證每一個線程都能單獨的執行操做,java線程同步的機制。同步鎖也叫
同步監聽對象/同步鎖/同步監聽器/互斥鎖
這些都是別名,就像茴香豆的「茴」字有幾種寫法同樣,都是別名。
Java程序中的任何對象均可以做爲同步監聽對象,可是咱們通常把多個線程同時訪問的共享資源做爲同步監聽對象。監聽其它的單獨的對象有啥意義。注意,在任什麼時候候,最多運行一個線程擁有同步鎖。
代碼以下:
package com.day14; class Apple implements Runnable{ private int num=50; public void run() { for (int i = 0; i < 50; i++) { //方法1:同步代碼塊 //因爲我多個線程共享的是Apple對象,因此同步鎖就是this,當前類的對象。不能使用num變量,由於num變量一直在變化 synchronized (this) { if (num > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //返回當前線程的引用Thread.currentThread(),再獲取名字 System.out.println(Thread.currentThread().getName() + "吃了編號爲" + num-- + "的蘋果"); } } } } } public class EatAppleRunnable { public static void main(String[] args) { //建立3我的,去參加吃蘋果大賽 Apple apple = new Apple(); new Thread(apple, "許嵩").start(); new Thread(apple, "林俊杰").start(); new Thread(apple, "蜀雲泉").start(); } }
運行結果以下:
這下不會出現搶蘋果事件了,也不會出現數量爲0和-1的狀況了。
synchronize修飾的方法就是同步方法,保證當前線程執行的時候,其它線程只能等待
語法:
synchronize public void Dowork(){
執行操做
}
同步鎖:對於非static方法,同步鎖就是this,對於靜態方法,同步鎖就是當前方法所在類的字節碼對象(類.class)
注意!不能使用synchronize修飾run方法。
package com.day14; class Apple implements Runnable{ private int num=50; public void run() { for (int i = 0; i < 50; i++) { eat(); } } //方法2:同步方法,直接用synchronized修飾方法 synchronized private void eat(){ if (num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //返回當前線程的引用Thread.currentThread(),再獲取名字 System.out.println(Thread.currentThread().getName() + "吃了編號爲" + num-- + "的蘋果"); } } } public class EatAppleRunnable { public static void main(String[] args) { //建立3我的,去參加吃蘋果大賽 Apple apple = new Apple(); new Thread(apple, "許嵩").start(); new Thread(apple, "林俊杰").start(); new Thread(apple, "蜀雲泉").start(); } }
運行結果也是OK的
或許有人會想,既然方法加了個synchronize就線程安全了,那我把全部的方法都加上synchronize不就得了。答案是不行滴
synchronize的優缺點:
優勢:保證了多線程併發訪問時的同步操做,避免了多線程操做的安全問題。
缺點:使用synchronize同步方法/同步代碼塊會致使性能下降。
鎖機制用到的是Lock這個接口,固然咱們在寫代碼的時候使用的是Lock接口的一個實現子類,叫ReentrantLock
語法:
private final Lock lock=new ReentrantLock(); try{ 線程操做代碼 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }
仍是上面的吃蘋果比賽,使用鎖機制的代碼以下:
package com.day14; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Apple implements Runnable{ //建立一個Lock接口的實現子類對象。ReentrantLock是Lock接口的一個子類的實現。 private final Lock lock=new ReentrantLock(); private int num=50; public void run() { for (int i = 0; i < 50; i++) { eat(); } } //方法3:同步鎖(Lock)的方式,這個方式和方法2的同步方式很相似。 private void eat(){ //進入方法首先上鎖 lock.lock(); if (num > 0) { try { //返回當前線程的引用Thread.currentThread(),再獲取名字 System.out.println(Thread.currentThread().getName() + "吃了編號爲" + num-- + "的蘋果"); Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } finally { //結束後,記得釋放鎖 lock.unlock(); } } } } public class EatAppleRunnable { public static void main(String[] args) { //建立3我的,去參加吃蘋果大賽 Apple apple = new Apple(); new Thread(apple, "許嵩").start(); new Thread(apple, "林俊杰").start(); new Thread(apple, "蜀雲泉").start(); } }
這個鎖機制是否是和synchronize同步方法很相像,只不過同步方法是synchronize修飾的方法,而鎖機制是在方法裏面上鎖,釋放鎖。
鎖機制和同步代碼塊/同步方法比,範圍更普遍。也就是說鎖機制包括了同步代碼塊和方法,並且範圍更大,更加面向對象。上鎖,釋放鎖都本身來寫,還有建立實現Lock接口的對象。
如今來說一個生產者和消費者的問題,講定生產者生產一些東西,放到分享池中,而後消費者去分享池中消費東西,大概就是下圖那樣的展現:
這就是咱們的生產者和消費者的模型,咱們要根據這個寫代碼。記得加上上面學習的同步鎖知識。
分享池代碼:
package com.day15; public class ShareResource { private String name; private String sex; private int num; private boolean isEmpty=true; synchronized public void push(String name,String sex,int num) { try { while (!isEmpty) {//若是不爲空的時候,生產者線程就等待 this.wait(); } //開始生產,生產事後,要isEmpty變成爲空,而後喚醒其它的線程 this.name=name; this.sex=sex; this.num=num; isEmpty=false; this.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void popup(){ try { while (isEmpty) { this.wait(); } System.out.println(this.name + this.sex + this.num); isEmpty=true; this.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } }
生產者代碼:
package com.day15; //生產者線程 public class Producer implements Runnable{ //分享池對象 public ShareResource shareResource=null; public Producer(ShareResource shareResource) { this.shareResource=shareResource; } @Override synchronized public void run() { for (int i = 0; i <50 ; i++) { if (i % 2==0) { shareResource.push("許嵩","男",i); } else{ shareResource.push("夢中人","女",i); } } } }
消費者代碼:
package com.day15; public class Consumer implements Runnable { //分享池對象 public ShareResource shareResource=null; public Consumer(ShareResource shareResource) { this.shareResource=shareResource; } @Override synchronized public void run() { for (int i = 0; i <50 ; i++) { shareResource.popup(); } } }
執行測試代碼:
package com.day15; //測試類 public class RunTest { public static void main(String[] args) { //建立生產者和消費者共同的資源對象 ShareResource resource=new ShareResource(); //啓動生產者線程 new Thread(new Producer(resource)).start(); new Thread(new Producer(resource)).start(); //啓動消費者線程 new Thread(new Consumer(resource)).start(); new Thread(new Consumer(resource)).start(); } }
代碼就是這些,我實行的是生產者生產一個東西,消費者就去消費,我這裏是直接打印出來了。生產者生產以後就去wait休息,等到東西沒了纔開始幹活。這裏咱們學到了兩個新的方法
1.wait()方法:線程休眠,除了被喚醒,不然就會一直睡覺休息,和睡美人是差很少了,沒有人喚醒是不會甦醒的。wait方法裏面能夠加參數,毫秒,就是沒人喚醒的話就本身醒(好慘啊...)
2.notifyAll()方法:喚醒除本身之外全部的線程,還有一個方法是notify(),喚醒隨機的一個線程
最後咱們看看輸出的結果:
經過上面同步方法synchronized和線程的wait方法,notify方法很好的完成了生產者和消費者的問題。這裏須要說明的是,wait方法和notify方法都必須須要同步鎖,那麼,我如今想用Lock鎖機制去完成生產者消費者問題,那該怎麼辦呢?
鎖機制Lock是沒法使用wait和notify方法的,那使用鎖機制的線程之間怎麼進行通訊呢?
從Java5開始就爲鎖機制的線程提供了Condition接口,用於線程直接的通訊,主要使用的方法有:
1.await()方法,至關於wait()方法,線程睡眠
2.signal()方法,至關於notify()方法,隨機的喚醒任意一個線程
3.signalAll()方法,至關於notifyAll()方法,喚醒除了本身之外的全部的線程
而後代碼其實就修改了分享池的代碼,放出來看一下:
package com.StadyJava.day15; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ShareResource { private String name; private String sex; private int num; private boolean isEmpty=true; private final Lock lock=new ReentrantLock(); //建立lock鎖機制的Condition對象 private Condition condition=lock.newCondition(); public void push(String name,String sex,int num) { lock.lock(); try { while (!isEmpty) { condition.await(); } //開始生產,生產事後,要isEmpty變成爲空,而後喚醒其它的線程 this.name=name; this.sex=sex; this.num=num; isEmpty=false; condition.signalAll(); } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } synchronized public void popup(){ lock.lock(); try { while (isEmpty) { condition.await(); } System.out.println(this.name + this.sex + this.num); isEmpty=true; condition.signalAll(); } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } }
其實沒什麼大變化,就是方法換了名字而已。
死鎖就是兩個線程互相在等待對方釋放鎖,死鎖的現象一旦出現,是解決不了的,因此死鎖現象只能避免,不能解決。
關於死鎖,有一個超級經典的例子,就是哲學家就餐問題
以下圖所示,Java線程有6種狀態,如今來介紹一下各類狀態。
線程有幾個很重要的方法須要講一下
這個方法是否是和上面講的wait方法很像?其實不同,wait睡覺以後,同步鎖就釋放了。sleep睡覺的時候,同步鎖是牢牢的抓住不鬆手的
這個方法大部分用來模擬網絡延遲,由於你刷新網頁的時候不是會轉圈圈嗎,能夠模擬這個。代碼中也有不少地方用這個來寫東西,例如我想模擬一個定時炸彈,我能夠這樣寫代碼:
package com.StadyJava.day15; public class ThreadDemo { public static void main(String[] args) { for (int i = 10; i > 0; i--) { System.out.println("離爆炸還有"+i+"秒"); } System.out.println("嘣,爆炸啦"); } }
這樣寫,沒問題吧,有問題的,我想定時,可是這個一運行,結果全出來了,不是想要的結果,因此咱們能夠加一個睡眠1秒來實現,代碼以下:
package com.StadyJava.day15; public class ThreadDemo { public static void main(String[] args) throws Exception { for (int i = 10; i > 0; i--) { System.out.println("離爆炸還有"+i+"秒"); Thread.sleep(1000); } System.out.println("嘣,爆炸啦"); } }
記得,sleep方法是要拋出異常的
這樣就能夠了,實現了倒計時的效果。還有幾個例子,例如坦克發射子彈,那這個子彈確定是不斷位移的,咱們設置好以後,子彈可能瞬間就打出去了,你根本看不到子彈的運行軌跡。爲了仔細的觀察,或者實現子彈慢速射擊的一個要求,咱們也能夠去sleep一下。還有NBA投籃遊戲也是,均可以去試試。
線程的join方法表示一個線程等待另外一個線程完成後才執行。就是說把當前線程和當前線程所在的線程聯合成一個線程。join方法被調用以後,線程對象處於阻塞狀態。
適用於A線程須要等到B線程執行完畢,再拿B線程的結果再繼續運行A線程。寫個代碼
package com.StadyJava.day15; class Join extends Thread{ public void run() { for (int i = 0; i < 50; i++) { System.out.println("join"+i); } } } public class ThreadDemo { public static void main(String[] args) throws Exception { Join joinThread=new Join(); for (int i = 0; i < 50; i++) { System.out.println("main"+i); if (i == 1) { joinThread.start(); } else if (i == 20) { joinThread.join(); } } } }
運行結果以下:
Idea副線程想和主線程搶資源真難。。。。運行了好幾回才搶到。。。咱們能夠看到在主線程等於1的時候,兩個線程開始搶佔資源打印輸出。在主線程爲20的時候,joinThread線程就調用了join方法,這個時候他們倆就變成了聯合線程,主線程main開始進入阻塞狀態,必須等到joinThread線程執行完畢才能夠執行。
在後臺運行,其目的是爲其餘線程提供服務,也稱爲「守護線程。
JVM的垃圾回收器就是典型的後臺線程。
特色:若全部的前臺線程都死亡,後臺線程自動死亡。
測試線程對象是否爲後臺線程:使用thread.isDaemon()。
前臺線程建立的線程默認是前臺線程,而且當且僅當建立線程是後臺線程時,新線程纔是後臺線程。
設置後臺線程:thread.setDaemon(true),該方法必須在start方法調用前,不然出現IllegalThreadStateException異常。
package com.StadyJava.day15; class Daemon extends Thread{ public void run() { for (int i = 0; i < 50; i++) { System.out.println(super.getName()+"-"+super.isDaemon()+i); } } } public class ThreadDemo { public static void main(String[] args) throws Exception { for (int i = 0; i < 50; i++) { System.out.println("main"+i); if (i == 1) { Daemon daemon=new Daemon(); daemon.setDaemon(true);//設置線程爲後臺線程,必須先設置後臺線程才能start開啓,先start開啓再設置會報錯 daemon.start(); Thread.sleep(10); } } } }
就一個判斷是不是後臺線程的方法 isDaemon() 和一個設置線程爲後臺線程的方法 setDaemon(true)
若是沒有了前臺線程,後臺線程會死亡。
優先級有兩個方法
1.setPriority() 獲取當前線程的優先級,main線程的優先級是5,默認的都是5
2.setPriority() 設置線程的優先級,不一樣的操做系統是不同的,Linux和Windows都不同。可是有3個數字是統一的,分別是1,5,10 1是最低,10是最高。因此用這3個數字就能夠了。
注意:並非優先級高的線程必定先執行,而是說這個線程有更多的機會去執行。執行的概率大了一些。
下面看一個代碼
package com.StadyJava.day15; class PriorityThread extends Thread{ public PriorityThread (String name){ super(name); } public void run() { for (int i = 0; i < 50; i++) { System.out.println("我是"+getName()+i); } } } public class Priority { public static void main(String[] args) { PriorityThread priorityThread1=new PriorityThread("優先級高的線程"); PriorityThread priorityThread2=new PriorityThread("優先級低的線程"); //priorityThread1.setPriority(Thread.MIN_PRIORITY);也可使用這個,可是數字更簡單 priorityThread1.setPriority(10); priorityThread2.setPriority(5); priorityThread2.start(); priorityThread1.start(); } }
看結果也知道,優先級高並非必定先執行。
古有孔融讓梨,那麼線程也有一個禮讓的方法叫 yield方法。這個方法比較特別,他把本身執行的機會讓給那些優先級高的線程,提出這個請求給調度器CPU,可是CPU能夠贊成這個請求也能夠無視這個請求
這個yield方法和sleep方法的區別以下:
1.均可以使得當前處於運行狀態的線程放棄執行的機會,讓給其它線程
2.sleep方法會讓給其它線程,隨機的讓。yield方法會讓給那些優先級高的線程。
3.調用sleep方法後,線程會進入計時等待狀態。調用yield方法後,線程會進入就緒狀態。
這個yield方法通常是不用的。不使用。。。。。在調試和測試線程的時候,可能會重現多線程的錯誤,可能。。。。因此仍是瞭解一下就好吧
定時器,就是定時去執行啦,直接看代碼
package com.StadyJava.day15; import java.util.Timer; import java.util.TimerTask; class Vae extends TimerTask { public void run() { System.out.println("你們好,我是Vae"); } } public class TimerDemo { public static void main(String[] args) { new Timer().schedule(new Vae(),3000,1000); } }
注意,個人Vae類是繼承的TimeTask類,不是Thread類。定時器的方法就是schedule方法,第一個參數就是TimeTask對象,第二個參數就是第幾秒出現執行,第三個參數就是間隔多少秒執行一次。
線程組,就是多個相同的線程在一個組裏面,就像老師講課,給A同窗講一遍,再給B同窗講一遍,再給C同窗講一遍。。。。這樣太麻煩。直接讓ABC三個同窗都過來,一塊兒聽就完事了。這就是線程組的意義
線程組的特色:
1.若是線程A建立了線程B,那麼B和A必定是一組的。
2.一個線程一旦加入了線程組,一生就是這個組的,一天是不良人,一生都是不良人。
當Java程序運行時,JVM會建立一個main線程組,默認全部的線程都是main線程組的
synchronized是一個修飾符,Lock是一個類。這是最本質的區別。
簡單的說,因爲wait,notify和notifyAll都是鎖級別的操做,因此把他們定義在Object類中,由於鎖屬於對象。