摘要小節:
1.同步機制
2.wait和notify
3.守護進程
4.stack棧
5.Timer類和//Runtime類的簡單講解
(一)同步synchronized機制
爲防止多線程的時候因爲爭搶資源而同時訪問某個變量導致數據的不一致,需要引入java 的同步機制
java用關鍵字synchronized(同步)實現
無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖——而且同步方法很可能還會被其他線程的對象訪問。
每個對象只有一個鎖(lock) 與之相關聯。實現同步是要很大的系統開銷作爲代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。
當我們明確知道要對某個對象加鎖,而非整個類的實例加鎖時,應該使用synchronized (ob ject),注意不能對基本數據類型直接加Synchronized關鍵字。同時當我們的成員變量需要加鎖時,應該將該變量設置爲private,並且提供Synchronized修改的get方法,以保證該成員遵循同步機制。
實例:銀行存錢取錢
(同步函數)
package bNineFirst;
public class Bank { //個人賬戶
private double money = 0; //賬戶餘額
public synchronized void addMoney(double count){ //存錢
money += count;
System.out.println("存入"+count+"元,當前餘額爲"+money+"元");
}
public synchronized void subMoney(double count){ //取錢
if(count > money){
System.out.println("對不起,餘額不足");
}else{
money -= count;
System.out.println("取出"+count+"元,當前餘額爲"+money+"元");
}
}
public void lookMoney(){ //查看餘額
System.out.println("當前餘額爲"+money+"元");
}
}
package bNineFirst;
public class AddAndSubMoney {
Bank bank = new Bank();
class Add extends Thread{
public void run(){
while(true){
bank.addMoney(100);
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Sub extends Thread{
public void run(){
while(true){
bank.subMoney(100);
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void execute(){
Add a = new Add();
Sub s = new Sub();
a.start();
s.start();
}
public static void main(String [] args){
AddAndSubMoney asm = new AddAndSubMoney();
asm.execute();
}
}
(同步代碼塊)
package bNineFirst;
public class Bank { //個人賬戶
private double money = 0; //賬戶餘額
public void addMoney(double count){ //存錢
synchronized(this){ //對象本身
money += count; //只需要這塊代碼實現同步即可
}
System.out.println("存入"+count+"元,當前餘額爲"+money+"元");
}
public void subMoney(double count){ //取錢
synchronized(this){
if(count > money){
System.out.println("對不起,餘額不足");
}else{
money -= count;
System.out.println("取出"+count+"元,當前餘額爲"+money+"元");
}
}
}
public void lookMoney(){ //查看餘額
System.out.println("當前餘額爲"+money+"元");
}
}
其餘的之前的一樣:
同步代碼塊和同步函數實現的效果是一樣的,區別在於,同步代碼塊只是設置需要同步的那塊代碼,同步是需要消耗系統性能的,同步函數會對函數內的所有內容同步,所以,如果希望代碼既實現線程安全又希望性能較好,應該使用同步代碼塊。
像是數據結構中的vector實現了同步機制,是線性安全的,他的每個函數都加了synchronized,其他操作和arraylist一樣,但速度不比arraylist
在平常,因爲我的程序都是單線程的,所以使用arraylist較多(線程不安全,性能好),但是工作中一般使用vector
(二)wait和notify
wait ()必須在synchronized方法或代碼塊內部使用,wait() 會讓已經獲得synchronized方法或代碼塊控制權的Thread暫時休息,並且喪失控制權。此時,由於該線程喪失控制權並且進入等待狀態,所以其他線程就能取得控制權,並且在適當情況下調用notifyAll()來喚醒wait()的線程。
需要注意的是,被喚醒的線程由於已經喪失了控制權,所以需要等待喚醒它的線程結束操作,從而才能重新獲得控制權。notifyAll ()並不是讓當前線程馬上讓出控制權,而只是讓其他wait ()當中的線程喚醒而已。
和sleep()區別:
sleep()設置了時間,時間過後,會自動喚醒
wait()如果不用方法去喚醒,他就永遠在等待
調用某個對象的wait()方法能讓當前線程阻塞,並且當前線程必須擁有此對象的monitor(即鎖)
爲何這wait()、notify()、notifyAll()不是Thread類聲明中的方法,而是Object類中聲明的方法(當然由於Thread類繼承了Object類,所以Thread也可以調用者三個方法)?其實這個問題很簡單,由於每個對象都擁有monitor(即鎖),所以讓當前線程等待某個對象的鎖,當然應該通過這個對象來操作了。而不是用當前線程來操作,因爲當前線程可能會等待多個線程的鎖,如果通過線程來操作,就非常複雜了。
上面已經提到,如果調用某個對象的wait()方法,當前線程必須擁有這個對象的monitor(即鎖),因此調用wait()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。
調用某個對象的wait()方法,相當於讓當前線程交出此對象的monitor,然後進入等待狀態,等待後續再次獲得此對象的鎖(Thread類中的sleep方法使當前線程暫停執行一段時間,從而讓其他線程有機會繼續執行,但它並不釋放對象鎖);
notify()方法能夠喚醒一個正在等待該對象的monitor的線程,當有多個線程都在等待該對象的monitor的話,則只能喚醒其中一個線程,具體喚醒哪個線程則不得而知。
同樣地,調用某個對象的notify()方法,當前線程也必須擁有這個對象的monitor,因此調用notify()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。
假如有三個線程Thread1、Thread2和Thread3都在等待對象objectA的monitor,此時Thread4擁有對象objectA的monitor,當在Thread4中調用objectA.notify()方法之後,Thread1、Thread2和Thread3只有一個能被喚醒。
實例:生產與消費
package bNineFirst;
public class WorkShop {
private int count = 0; //產品數量
public synchronized void Produce(){
if(count < 20){ //防止產品過剩
count++;
System.out.println("開始生產第"+count+"件產品");
notify(); //喚醒消費線程
}else{ //如果產品生產過多,就停一下
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void Buy(){
if(count > 0){ //確保有產品的情況下才能消費
System.out.println("開始消費第"+count+"件產品");
count--;
notify(); //喚醒生產線程
}else{ //如果沒有產品了,就停一下,等待生產
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package bNineFirst;
public class ProduceAndBuyNotify {
WorkShop ws = new WorkShop();
class Pro extends Thread {
public void run() {
while (true) {
ws.Produce();
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Buy extends Thread {
public void run() {
while (true) {
ws.Buy();
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void exetute() {
Pro p = new Pro();
Buy b = new Buy();
p.start();
b.start();
}
public static void main(String[] args) {
ProduceAndBuyNotify pb = new ProduceAndBuyNotify();
pb.exetute();
}
}
(三)守護進程
java中提供了兩種線程,用戶線程和守護線程
用戶線程是高優先級的線程。JVM虛擬機在結束一個用戶線程之前,會先等待該用戶線程完成它的task。
守護線程是低優先級的線程,它的作用僅僅是爲用戶線程提供服務。正是由於守護線程是爲用戶線程提供服務的,僅僅在用戶線程處於運行狀態時才需要守護線程。另外,一旦所有的用戶線程都運行完畢,那麼守護線程是無法阻止JVM退出的。這也是存在於守護線程中的無限循環不會產生問題的原因,因爲包括finally 塊的任何代碼都不會被執行,一旦所有的用戶線程結束運行之後。
當程序中所有的其他線程都結束時,即便守護線程中還有沒有運行的代碼(垃圾回收機制,只要JVM啓動,始終在運行),也會直接結束。JVM退出執行。
package bNineFirst;
public class ShouHuJingCheng {
class One extends Thread{
public void run(){
System.out.println(Thread.currentThread().getName()+" 開始");
try {
sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 結束");
}
}
public void execute(){
One one = new One();
one.setName("one");
one.setDaemon(true); //在進程啓動之前將進程設置爲守護進程
one.start();
}
public static void main(String [] args){
System.out.println("主線程開始");
ShouHuJingCheng sh = new ShouHuJingCheng();
sh.execute();
System.out.println("主線程結束");
}
}
用戶進程,沒有將進程設置爲守護進程時的輸出請況
守護進程
(主線程結束了,所以one線程不會等三秒讓他執行完,就直接結束了)
Main線程結束,其他線程一樣可以正常運行。
主線程,只是個普通的非守護線程,用來啓動應用程序,不能設置成守護線程;除此之外,它跟其他非守護線程沒有什麼不同。主線程執行結束,其他線程一樣可以正常執行。
按照操作系統的理論,進程是資源分配的基本單位,線程是CPU調度的基本單位。對於CPU來說,實並不在java的主線程和子線程之分,都只是個普通的線程。進程的資源是線程共享的,只要進程還在,線程就可以正常執行,換句話說線程是強依賴於進程的。也就是說,線程其實並不存在互相依賴的關係,一個線程的死亡從理論上來說,不會對其他線程有什麼影響。
(四)stack棧
是用vector實現的,所以也是線程安全的
主要的操作有:
push(Object obj):壓棧
Object obj = pop():出棧
Object obj = peek():查看棧頂元素
boolean bol = empty():判斷棧是否爲空
int num = search(Object obj):返回元素在棧中的位置,棧頂爲1,棧頂爲棧的長度
實例:
package bNineFirst;
import java.util.Stack;
public class StackTest {
public static void main(String [] args){
Stack<String > s = new Stack<String>();
s.push("a");
System.out.println("one:"+s.size());
s.push("b");
System.out.println("two:"+s.size());
String str1 = s.pop();
System.out.println("three:"+str1 +s.size());
String str2 = s.peek();
System.out.println("four:"+str1+str2 +s.size());
boolean bol = s.empty();
System.out.println("five:"+bol +s.size());
Stack s1 = new Stack();
boolean bol2= s1.empty();
System.out.println("six:"+bol2 +s1.size());
s.push("x");
s.push("y");
int num = s.search("a");
System.out.println("seven:"+ num +s.size());
}
}
執行效果:
(五)Timer類和Runtime類
Timer
簡單的調度類
按照一定的時間規律來循環執行代碼
package bNineFirst;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String [] args){
Timer timer = new Timer();
// void java.util.Timer.schedule(TimerTask task, long delay, long period)
// 任務 延遲多少時間開始 每次的時間間隔
timer.schedule(new TimerTask(){ //任務匿名類
public void run(){ //線程的run方法
System.out.println("懶豬,起牀啦!");
}
}, 1000, 2000);
}
}
執行效果:
(執行後,隔1s中開始執行,接下來每隔2s開始打印)
Runtime類
代表Java程序的運行環境,是一個單例模式類。
package bNineFirst;
import java.io.IOException;
public class RuntimeTest {
public static void main(String [] args){
Runtime r = Runtime.getRuntime(); //單例模式,創建類的對象
try {
r.exec("D:\\studySoftware\\myvmware\\vmware.exe");//打開vmware
} catch (IOException e) {
e.printStackTrace();
}
}
}
java程序中爲了防止將‘\’變成轉義符,所以需要兩個'\',或者是'/'
(1)D:\\studySoftware\\myvmware\\vmware.exe
(2)D:/studySoftware/myvmware/vmware.exe
【只有裏面方法是同步的,我們才能說它是線程安全的】
【當阻塞方法收到中斷請求的時候就會拋出InterruptedException異常(所有該異常都和多線程有關係)】