多線程
一、概述:
1、線程是什麼
說到線程,我們就得先說說進程。所謂進程,就是一個正在執行(進行)中的程序,每一個進程執行都有一個執行順序。該順序是一個執行路徑,或者叫一個控制單元。如我們常用的QQ,打開運行它時它就是一個進程,在windows下我們通常都可以通過任務管理器中的進程來查看正在運行的進程有哪些。線程,就是進程中的一個獨立的控制單元,線程在控制着進程的執行,一個進程至少有一個線程。比方說,辦一批東西,搬東西整體就是一個進程,而搬東西的每個人就是一個線程。
2、java中的線程:
在java中,JVM虛擬機啓動時,會有一個進程爲java.exe,該程序中至少有一個線程負責java程序的執行;而且該程序運行的代碼存在於main方法中,該線程稱之爲主線程。其實,JVM啓動時不止有一個線程(主線程),還有負責垃圾回收機制的線程。
3、多線程的意義:
簡單的說,就是提高程序的執行效率。
二、自定義線程:
第一種:創建線程的第一種方式:繼承Thread類。
1、步驟:
1)定義類繼承Thread。
2)複寫Thread類中的run方法。目的:將自定義代碼存儲在run方法。讓線程運行。
3)調用線程的start方法,該方法兩個作用:啓動線程,調用run方法。
class Demo extends Thread
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo run----"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
//for(int x=0; x<4000; x++)
//System.out.println("Hello World!");
Demo d = new Demo();//創建好一個線程。
d.start();//開啓線程並執行該線程的run方法。
//d.run();//僅僅是對象調用方法。而線程創建了,並沒有運行。
for(int x=0; x<60; x++)
System.out.println("Hello World!--"+x);
}
}
通過結果發現運行結果每一次都不同。因爲多個線程都獲取cpu的執行權。cpu執行到誰,誰就運行。明確一點,在某一個時刻,只能有一個程序在運行。(多核除外)cpu在做着快速的切換,以達到看上去是同時運行的效果。我們可以形象把多線程的運行行爲在互相搶奪cpu的執行權。這就是多線程的一個特性:隨機性。誰搶到誰執行,至於執行多長,cpu說的算。
2、爲什麼要覆蓋run方法呢?
Thread類用於描述線程,該類就定義了一個功能,用於存儲線程要運行的代碼。該存儲功能就是run方法。也就是說Thread類中的run方法,用於存儲線程要運行的代碼。
3、線程的狀態:
1)被創建:線程被創建,進入阻塞狀態,cpu執行它的時候他才正式進入運行狀態
2)運行:運行狀態,線程被運行
3)凍結(放棄了執行資格):凍結分爲sleep和wait,sleep(time)時間結束後進入阻塞狀態,當cpu執行它的時候它才正式進入運行狀態。還有一種是wait,當wait的時候必須被notify()喚醒之後才能具備執行資格,也就是喚醒後就進入阻塞狀態
4)阻塞(也稱爲臨時狀態,這個狀態是具有執行資格,但是沒有執行權):當線程具備運行資格,但是沒有執行權的時候就出於阻塞狀態
5)消亡:stop和run方法結束,線程就消亡了。
第二種:實現Runnable接口
1、步驟:
第一、定義類實現Runnable接口。
第二、覆蓋Runnable接口中的run方法。
第三、通過Thread類建立線程對象。要運行幾個線程,就創建幾個對象。
第四、將Runnable接口的子類對象作爲參數傳遞給Thread類的構造函數。爲什麼要將Runnable接口的子類對象傳遞給Thread的構造函數。因爲,自定義的run方法所屬的對象是Runnable接口的子類對象。所以要讓線程去執行指定對象的run方法。就必須明確該run方法所屬對象。
第五、調用Thread類的start方法開啓線程,並調用Runnable接口子類的run方法。
class Ticket implements Runnable//extends Thread
{
private int tick = 100;//多窗口共賣100張票
public void run()
{
while(true)//讓循環多執行,目的看出執行的效果,這樣線程就必須手動停止,可以加條件讓它停止
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);//創建了一個線程;
Thread t2 = new Thread(t);//創建了一個線程;
Thread t3 = new Thread(t);//創建了一個線程;
Thread t4 = new Thread(t);//創建了一個線程;
t1.start();
t2.start();
t3.start();
t4.start();
/*
Ticket t1 = new Ticket();
//Ticket t2 = new Ticket();
//Ticket t3 = new Ticket();
//Ticket t4 = new Ticket();
t1.start();
t1.start();
t1.start();
t1.start();
*/
}
}
2、實現方式和繼承方式有什麼區別呢?1)實現方式好處:避免了單繼承的侷限性。在定義線程時,建議使用實現方式。
2)兩種方式區別:
繼承Thread:線程代碼存放Thread子類run方法中。
實現Runnable,線程代碼存在接口的子類的run方法。
需要注意的是:局部變量在每一個線程中都獨有一份。
三、多線程安全問題:
1、通過上面賣票的例子運行結果出現了0,-1,-2等錯票。
2、問題的原因:當多條語句在操作同一個線程共享數據是,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行,導致共享數據的錯誤。
3、解決辦法:對多條操作共享數據的語句,只能讓一個線程都執行完後才讓其他線程執行,這樣就不會造成共享數據的錯亂了。、、
4、Java對於多線程的安全問題提供了專業的解決方式。就是同步代碼塊。
synchronized(對象)
{
需要被同步的代碼(操作共享數據的代碼)
}
對象如持有鎖,持有鎖的線程可以在同步代碼塊中執行。沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因爲沒有獲取鎖。
5、同步的前提:
1)必須有兩個或兩個以上的線程
2)必須是多個線程使用同一個鎖
6、同步作用:保證同步中只能有一個線程在運行。
7、同步的好處:解決了多線程的安全問題。
8、同步的弊端:多個線程需要判斷鎖,較爲消耗資源。
9、同步代碼塊解決安全問題
synchronized(對象)//對象稱爲鎖旗標
{
需要被同步的代碼
}
賣票的實例:
class Ticket implements Runnable
{
private int tick = 1000;
Object obj = new Object();
public void run()
{
while(true)//讓循環多執行,目的看出執行的效果,這樣線程就必須手動停止,可以加條件讓它停止
{
synchronized(obj)//任意對象
{
if(tick>0)
{
//try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
10、同步函數
同步函數就是將修飾符synchronized放在返回類型的前面,一般函數內代碼較少,下面通過同步函數給出多線程安全問題的具體解決方案:
/*
* 需求: 銀行有一個金庫。 有兩個儲戶分別存300員,每次存100,存3次。
*
* 目的:該程序是否有安全問題,如果有,如何解決?
*
*
* 如何找問題: 1,明確哪些代碼是多線程運行代碼。 2,明確共享數據。 3,明確多線程運行代碼中哪些語句是操作共享數據的。
*/
class Bank {
private int sum;
// Object obj = new Object();
public synchronized void add(int n)// 同步函數
{
// synchronized(obj)//同步代碼塊
// {
sum = sum + n;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum=" + sum);
// }
}
}
class Cus implements Runnable {
private Bank b = new Bank();
public void run() {
for (int x = 0; x < 3; x++) {
b.add(100);
}
}
}
class BankDemo {
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
11、同步中的鎖:
1)非靜態同步函數中的鎖---> this
函數需要被對象調用。那麼函數都有一個所屬對象引用。就是this。所以同步函數使用的鎖是this。
鎖的驗證,原理只有使用的是同一個鎖才能進行同步:
class Ticket implements Runnable
{
private int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(this)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
}
}
}
}
else
while(true)
show();
}
public synchronized void show()//this
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
}
}
}
class ThisLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
// Thread t3 = new Thread(t);
// Thread t4 = new Thread(t);
// t3.start();
// t4.start();
}
}
通過驗證,發現不在是this。因爲靜態方法中也不可以定義this。靜態進內存是,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。因爲,static是隨着類的加載而加載的,類在加載的時候會差生一個字節碼class對象。類名.class 該對象的類型是Class
鎖的驗證,原理只有使用的是同一個鎖才能進行同步:
class Ticket implements Runnable
{
private static int tick = 100;
//Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Ticket.class)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
}
}
}
}
else
while(true)
show();
}
public static synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
}
}
}
class StaticMethodDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
12、單利設計模式:
1)解決問題:解決一個類在內存中只存在一個對象的問題
2)如何保證對象的唯一性:
①、爲避免建立過多的該類對象,應首先禁止其他應用程序創建該類對象。
②、爲讓其他應用程序訪問到該對象,在本類中自定義一個對象,爲避免直接訪問該對象,要對其進行私有化。
③、提供訪問方式,便於其他程序對自定義對象的訪問,提供的訪問方法是公有的。
3)具體實現:
class Single1 {
private static Single1 s1 = new Single1();//餓漢式,上來就創建對象
private Single1() {
}
public static Single1 getInstance() {
return s1;
}
}
class Single2 {
private static Single2 s2 = null;//懶漢式,需要的時候才創建對象,稱爲對象的延遲加載
private Single2() {
}
public static Single2 getInstance() {
if (s2 == null) {
synchronized (Single2.class) {
if (s2 == null) {
s2 = new Single2();
}
}
}
return s2;
}
}
懶漢式因爲操作共享數據的代碼有多條,所以在多線程時存在安全隱患。所以給出了以上雙重循環的方式,這樣做也提高了代碼的執行效率。
比如說,當A調用時,當讀到if(s1==null) 時,可能就停在這了,然後cpu再調用B,B也讀到if(s1==null)這停下了,cpu再切換到A,接着創建一個對象,A就執行完了;之後B也向下執行,又創建一個對象;此時,對象就不唯一了,就破壞了對象的唯一性的初衷。那麼解決方案是這樣的:
這利用了鎖的機制。synchronized是java語言的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多隻有一個線程執行該段代碼。這涉及到了多線程的問題。在這個例子中,比如說,當A調用時,當讀到第二個if(s1==null) 時,可能就停在這了,然後cpu再調用B,B讀到第一個if(s1==null)這停下了,因爲加上synchronized後,A進去就相當於將其他的調用鎖在外面的語句上了,要先執行完A,那麼A執行完後,就已經創建了一個對象;當B再讀到第二個if(s1==null)的時候不符合就直接結束了。如果再有其他C或D等調用的時候,就直接不符合第一個(s1==null)的條件,所以直接返回s。
四、死鎖:
1、造成原因:同步中嵌套同步。而且鎖不相同
實例一:
class Ticket implements Runnable
{
private int tick = 1000;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(obj)
{
show();
}
}
}
else
while(true)
show();
}
public synchronized void show()//this
{
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
}
}
}
}
class DeadLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
實例二:
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"...if locka ");
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..if lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..else lockb");
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+".....else locka");
}
}
}
}
}
}
class MyLock
{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
死鎖是在開發中應該避免的地方,首先知道了它,我們就可以去避免它的出現啦!
五、多線程間的通信:
1、概述:
線程間通訊:其實就是多個線程在操作同一個資源,但是操作的動作不同。
看看下面的實例:
class Res
{
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable
{
private Res r ;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
synchronized(r)
{
if(r.flag)
try{r.wait();}catch(Exception e){}//等待的線程進入線程池,而且在線程池中是有序存放的
if(x==0)
{
r.name="mike";
r.sex="man";
}
else
{
r.name="麗麗";
r.sex = "女女女女女";
}
x = (x+1)%2;
r.flag = true;
r.notify();//通常喚醒的是線程池中最先等待的線程
}
}
}
}
class Output implements Runnable
{
private Res r ;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r)
{
if(!r.flag)
try{r.wait();}catch(Exception e){}
System.out.println(r.name+"...."+r.sex);
r.flag = false;
r.notify();
}
}
}
}
class InputOutputDemo
{
public static void main(String[] args)
{
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
因爲上面的代碼是兩個線程對共享數據的不同操作,而他們的執行權卻是不一定的,也就是說可能input執行了多次,每一次執行都會覆蓋掉上一次的值。輪到output執行的時候也可能是執行多次,所以就會出現同一個值被打印多次的現象。所以在上面就用到了等待喚醒機制來解決這個問題。
2、等待喚醒機制:
等待wait,是讓一個線程出於被凍結的狀態,就是放棄了執行權,出於被掛起的狀態,出於這種狀態的線程需要被notify方法喚醒。notify喚醒的都是最先在線程池中被掛起的線程,而如果需要喚醒對方的線程就需要用到notifyAll方法,當然這個方法連自己放的線程也喚醒了。
看下下面的實例:
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*
對於多個生產者和消費者。
爲什麼要定義while判斷標記。
原因:讓被喚醒的線程再一次判斷標記。
爲什麼定義notifyAll,
因爲需要喚醒對方線程。
因爲只用notify,容易出現只喚醒本方線程的情況。導致程序中的所有線程都等待。
*/
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)//while,每次notify後都會回來判斷條件,而if不會,它會直接往下執行。
try{this.wait();}catch(Exception e){}//t1(放棄資格) t2(獲取資格)
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生產者.."+this.name);
flag = true;
this.notifyAll();//喚醒所有的,這樣就避免只喚醒自己一方,造成全部凍結的情況
}
public synchronized void out()
{
while(!flag)
try{wait();}catch(Exception e){}//t3(放棄資格) t4(放棄資格)
System.out.println(Thread.currentThread().getName()+"...消費者........."+this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.set("+商品+");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
在上面這個例子中就使用到了notifyAll方法避免了多線程時notify出現只喚醒自己一方的線程,但是notifyAll也喚醒了自己一方的其他線程,這並不是我們想要的,那麼怎麼才能到達只喚醒對方的線程呢?
JDK1.5 中提供了多線程升級解決方案。
將同步Synchronized替換成顯示Lock操作。
將Object中的wait,notify notifyAll,替換成了Condition對象。
該對象可以Lock鎖 進行獲取。
該示例中,實現了本方只喚醒對方操作。
Lock:替代了Synchronized
lock 鎖
unlock 解鎖
newCondition() 創建一個condition對象
Condition:替代了Object wait notify notifyAll
await();等待,線程被掛起
signal();喚醒
signalAll();喚醒全部
看實例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumer2 {
public static void main(String[] args) {
Resource2 res = new Resource2();
new Thread(new Producer2(res)).start();
new Thread(new Producer2(res)).start();
new Thread(new Consumer2(res)).start();
new Thread(new Consumer2(res)).start();
}
}
class Resource2 {
private String name;
private int count = 1;
private boolean flag;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name) {
lock.lock();
try {
while (flag) {
condition_pro.await();
}
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName() + "...生產者..."
+ this.name);
flag = true;
condition_con.signal();// 只喚醒消費者
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void out() {
lock.lock();
try {
while (!flag) {
condition_con.await();
}
System.out.println(Thread.currentThread().getName() + ".消費者"
+ this.name);
flag = false;
condition_pro.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
class Producer2 implements Runnable {
private Resource2 res;
public Producer2(Resource2 res) {
this.res = res;
}
@Override
public void run() {
while (true)
res.set("-------生產奶--------");
}
}
class Consumer2 implements Runnable {
private Resource2 res;
Consumer2(Resource2 res) {
this.res = res;
}
@Override
public void run() {
while (true)
res.out();
}
}
這裏使用的是signal方法,而不是signalAll方法。是因爲通過Condition的兩個對象,分別喚醒對方,這就體現了Lock鎖機制的靈活性。可以通過Contidition對象調用Lock接口中的方法,就可以保證多線程間通信的流暢性了。六、Thread類中的方法簡介:
1、停止線程:
在java 1.5之後,就不再使用stop方法停止線程了。那麼該如何停止線程呢?只有一種方法,就是讓run方法結束。開啓多線程運行,運行代碼通常爲循環結構,只要控制住循環,就可以讓run方法結束,也就可以使線程結束。
注:特殊情況:當線程處於凍結狀態,就不會讀取標記,那麼線程就不會結束。如下:
class StopThread implements Runnable
{
private boolean flag =true;
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
//t1.setDaemon(true);//守護線程,在啓動前調用,也就是後臺線程,前臺線程結束,後臺線程也就結束了,不用刻意去控制結束
//t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
Thread類提供該方法 interrupt()讓凍結的線程恢復到運行狀態是,這時需要對凍結進行清除。強制讓線程恢復到運行狀態中來。這樣就可以操作標記讓線程結束。
2、守護線程:---setDaemon()
可將一個線程標記爲守護線程,直接調用這個方法。此方法需要在啓動前調用守護線程在這個線程結束後,會自動結束,則Jvm虛擬機也結束運行。
//守護線程(後臺線程),在啓動前調用。後臺線程自動結束
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
3、臨時加入線程:--join()
特點:當A線程執行到B線程join()方法時,A線程就會等待,B線程都執行完,A纔會執行。join可用來臨時加入線程執行。
class Demo implements Runnable{
public void run(){
for(int x=0;x<90;x++){
System.out.println(Thread.currentThread().getName() + "----run" + x); //Thread.currentThread().getName()獲取當前線程名字
}
}
}
class JoinDemo{
public static void main(String[] args)throws Exception{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
t1.join();//等t1執行完了,主線程才從凍結狀態恢復,和t2搶執行權。t2執不執行完都無所謂。
int n = 0;
for(int x=0;x<80;x++){
System.out.println(Thread.currentThread().getName() + "----main" + x);
}
System.out.println("Over");
}
}
4、優先級:
在Thread中,存在着1~10這十個執行級別,最高的是 MAX_PRIORITY 爲10,最低是 MIN_PRIORITY 爲1,默認優先級是 NORM_PRIORITY 爲5;但是並不是優先級越高,就會一直執行這個線程,只是說會優先執行到這個線程,此後還是有其他線程會和此線程搶奪cpu執行權的。
優先級是可以設定的,可通過setPriority()設定,如:setPriority(Thread.MAX_PRIORITY)設優先級爲最大。
yield():此方法可暫停當前線程,而執行其他線程。通過這個方法,可稍微減少線程執行頻率,達到線程都有機會平均被執行的效果。如下:
class Demo implements Runnable{
public void run(){
for(int x=0;x<90;x++){
System.out.println(Thread.currentThread().toString() + "----run" + x);
Thread.yield();//稍微減少線程執行頻率。可達到線程都有機會達到平均運行的效果
}
}
}
class YieldDemo{
public static void main(String[] args)throws Exception{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.setPriority(Thread.MAX_PRIORITY);//設置線程優先級最大
t2.start();
System.out.println("Over");
}