線程是程序運行的基本執行單元,一個進程中可以包含多個線程,這些線程共享這個進程中的內存空間。但是進程和進程之間是不共享內存的,都有自己的獨立的運行空間。
建立線程的兩種方法
1,一種方法是類去繼承 Thread 類,其實是Thread自己實現了Runnable
2,用接口的方法,去實現 Runnable (用這個比較好)
創建步驟:
1,定義自己的類實現Runnable接口
2,覆蓋Runnable接口中的run方法
3,通過Thread類建立線程對象
4,將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數
5,調用Thread類的start方法開啓線程並調用Runnable接口子類的run方法
例子:賣票窗口,3個窗口賣100張票
兩種方法的區別?
java 是單繼承 如果繼承 Thread 則不能在繼承其它的類 ,實現接口的話還可以繼承其它類,接口可以多實現的
多線程會出現安全問題
一:解決方法是加synchronized ,單線程不存在這個問題
好處:解決了安全問題
弊端:多個線程需要判斷鎖,較爲消耗資源
1,
synchronized()
{
共享代碼塊
}
例:
Object obj=new Object();
public void run()
{
while(true)
{
synchronized(obj)//加鎖安全 這個鎖的是obj
{
if(tick>0) //這句在多線程來說是有安全問題的,如果兩個tick=1了,
//多個進程同時進到這裏來執行,就會出現問題
System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
}
}
}
2,可以把共享代碼封裝在一個方法中,在調用其方法就行, 在方法上加上synchronized
例:
public synchronized void run() //加鎖 這個鎖的是this
{
while(true)
{
if(tick>0)
System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
}
}
或:
public void method()
{
synchronized(this) //相當於鎖的是 method() 方法
{
共享代碼塊
}
}
3,或直接把synchronized 放在方法上,鎖的是this,如果方法被static靜態了,那麼鎖的就不是this了。(靜態中也不可能有this),這個時候鎖的是所在類的字節碼文件對象。<類名.class>
不同鎖的建立:
建立鎖:
class Loak
{
static Object loak1=new Object();
static Object loak2=new Object();//兩種鎖
}
加入鎖:
synchronized(Loak.loak1)
{
}
二:JDK1.5中提供了多線程升級解決方案
將同步synchronized替換成現實lock操作
要爲特定 Lock 實例獲得 Condition 實例,使用其 newCondition() 方法。
Condition 實例實質上被綁定到一個鎖上。
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,
以便通過將這些對象與任意 Lock 實現組合使用
採用 lock 鎖來鎖定代碼
final Condition nl = lock.newCondition();
final Condition n2 = lock.newCondition();
用 n1.signal n2.signal 來喚醒一個指定等待線程
用 n1.signalAll n2.signalAll 來喚醒所有指定等待線程
例:
class ziyuan //資源
{
//定義鎖
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
public void set(String name) throws InterruptedException
{
lock.lock(); 代碼上鎖
try
{
while(!flg)
{
c1.await(); // p1 等待
}
flg = false;
c2.signal(); // v2 喚醒
}
finally
{
lock.unlock(); 最後一定要解鎖
}
}
如果在一個類中有多處使用加鎖,要處理同一共享數據時。要加鎖同一個。不然就要出錯
注意: 使用這個不要出現 死鎖 ,當類中存在多個不同的鎖時,而這些鎖存在交叉,那麼就可能會出現死鎖
如:
1,鎖A--11
{鎖B--22}
2,鎖B--33
{鎖A--44}
像上面這樣出現交叉的鎖。如11 33 同時執行後 要運行22 44就鎖住了。22被下面B鎖了,44又被上面11鎖住了。那麼就執行不下去了
怎麼讓線程停下來呢?
stop已過時,只有一種方法,讓 run 方法停止。
開啓多線程運行,運行代碼通常是循環結構,只要控制住循環,就可以讓run 方法結束,也就是線程結束。
用 while 循環來判斷標識。
原因:讓被喚醒的線程再一次判斷標記
例:
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 ThreadStopclass
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++==60)
{
st.changeFlag(); //修改標記讓線程停下
break;
}
System.out.println(Thread.currentThread().getName()+"..........");
}
System.out.println("主函數運行完。。。");
}
}
特殊情況:
當線程處於了凍結狀態(就是等待狀態),就不會讀取到標記,那麼線程就不會結束。
這個時候怎麼結束呢?
這時要清除凍結狀態,強制讓線程恢復到運行狀態中來,這樣就可以操作標記讓線程結束。
Thread類中提供了該方法 interrupt() ,中斷線程.
例:
public synchronized void run() //在這加了鎖這種情況,所以下面等待就不會讓線程停下 {
while(flag)
{
try
{
wait(); //在這裏讓線程等待了。所以必須先喚醒線程才能讓其停止。
}
catch (Exception e)
{
System.out.println(Thread.currentThread().getName()+"................Exception");
}
ystem.out.println(Thread.currentThread().getName()+"..........run");
}
}
while(true)
{
if(num++==60)
{
st.changeFlag(); //修改標記讓線程停下
t1.interrupt(); // 中斷線程
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"..........");
}
System.out.println("主函數運行完。。。");
線程帶來的好處?
1,充分利用CPU資源
2,簡化編程模型
如果要使程序完成多項任務,這時用單線程的話,就得作出判斷以及什麼時候執行。而且不好操作,使用多線程來完成呢,就方便的多。
3,使GUI更有效率。
4,節約成本。
使用多進程,提高了程序的執行效率。
5,簡化異步事件的處理。
線程的生命週期:開始、運行、掛起、停止 四種不同的狀態。
join方法的使用
程序在返回數據的過程中,而這個數據又是在線程執行過程中給賦予的,這時如果線程沒有執行結束,或還沒有給我們所調用的信息賦值,那麼這時,返回的數據就是錯誤的信息了。
這時,我們可以採用讓調用者先等待一會,在去調用,但是等多長時間又是一個問題,所以在這裏出現了join()方法。調用了join()方法,就是讓這個線程執行完,才執行下面的代碼。
例:
// 有 10 個線程
for(int i=0; i<10; i++)
{
threads[i].start();
}
//讓每一個線程執行完後,再往下執行
for(int i=0; i<10; i++)
{
threads[i].join();
}
//等待線程執行完纔會執行後面的代碼
System.out.println();
注意:其實在這裏調用join()方法,和調用其它任意一個方法,是一樣的,只要在這裏調用了一下,就意味着線程執行完在往下執行。
向線程傳遞數據的三種方法:
1,通過構造函數
2,通過方法和變量
3,通過回調函數
回調函數就是事件函數。
就是把自己給別人,讓別人通過方法來給自己傳值。
例:
下面我們給Data 類中value 賦值
class Data{
public int value = 0;
}
class Work{
public void process(Data data,int a)
{
data.value = a;
}
}
public static void min(String [] args)
{
Data data = new Data();
Work work = new Work();
work.process( data , 3 ); //通過回調函數給value 賦值
}
volatile 關鍵字
用於聲明簡單類型,如 int float boolean 等。
如:
public volatile int n = 9;
聲明後,對它們的操作就會變成原子級別的。 每次使用它都到主存中進行讀取。多線程在操作的時候操作的是同一個數據,所以保證了數據的同步。
注意:當變量值由自身的上一個值決定時,如 n=n+1 n++ 等,volatile關鍵字將失效。
心得:
使用線程就得注意數據同步的問題,使用join方法非常好,在線程中還有一個問題,就是在給代碼加上鎖的時候注意別產生死鎖。