先從線程的創建說起.線程的創建一共有兩種形式:
--------------------------------------------------------------------------------
一種是繼承自Thread類.Thread 類是一個具體的類,即不是抽象類,該類封裝了線程的行為.要創建一個線程,程序員必須創建一個從 Thread 類導出的新類.程序員通過覆蓋 Thread 的 run() 函數來完成有用的工作.用戶并不直接調用此函數;而是通過調用 Thread 的 start() 函數,該函數再調用 run(). 例如:
public class Test extends Thread{ public Test(){ } public static void main(String args[]){ Test t1 = new Test(); Test t2 = new Test(); t1.start(); t2.start(); } public void run(){ //do thread's things } }
--------------------------------------------------------------------------------
另一種是實現Runnable接口,此接口只有一個函數,run(),此函數必須由實現了此接口的類實現. 例如:
public class Test implements Runnable{ Thread thread1; Thread thread2; public Test(){ thread1 = new Thread(this,"1"); thread2 = new Thread(this,"2"); } public static void main(String args[]){ Test t = new Test(); t.startThreads(); } public void run(){ //do thread's things } public void startThreads(){ thread1.start(); thread2.start(); } }
兩種創建方式看起來差別不大,但是弄不清楚的話,也許會將你的程序弄得一團糟.兩者區別有以下幾點:
1.當你想繼承某一其它類時,你只能用后一種方式.
2.第一種因為繼承自Thread,只創建了自身對象,但是在數量上,需要幾個線程,就得創建幾個自身對象;第二種只創建一個自身對象,卻創建幾個Thread對象.而兩種方法重大的區別就在于此,請你考慮:如果你在第一種里創建數個自身對象并且start()后,你會發現好像synchronized不起作用了,已經加鎖的代碼塊或者方法居然同時可以有幾個線程進去,而且同樣一個變量,居然可以有好幾個線程同時可以去更改它.(例如下面的代碼)這是因為,在這個程序中,雖然你起了數個線程,可是你也創建了數個對象,而且,每個線程對應了每個對象也就是說,每個線程更改和占有的對象都不一樣,所以就出現了同時有幾個線程進入一個方法的現象,其實,那也不是一個方法,而是不同對象的相同的方法.所以,這時候你要加鎖的話,只能將方法或者變量聲明為靜態,將static加上后,你就會發現,線程又能管住方法了,同時不可能有兩個線程進入同樣一個方法,那是因為,現在不是每個對象都擁有一個方法了,而是所有的對象共同擁有一個方法,這個方法就是靜態方法.
而你如果用第二種方法使用線程的話,就不會有上述的情況,因為此時,你只創建了一個自身對象,所以,自身對象的屬性和方法對于線程來說是共有的.
因此,我建議,最好用后一種方法來使用線程.
public class mainThread extends Thread{ int i=0; public static void main(String args[]){ mainThread m1 = new mainThread(); mainThread m2 = new mainThread(); mainThread m3 = new mainThread(); mainThread m4 = new mainThread(); mainThread m5 = new mainThread(); mainThread m6 = new mainThread(); m1.start(); m2.start(); m3.start(); m4.start(); m5.start(); m6.start(); } public synchronized void t1(){ i=++i; try{
|
Thread.sleep(500);
}
catch(Exception e){}
//每個線程都進入各自的t1()方法,分別打印各自的i
System.out.println(Thread.currentThread().getName()+" "+i);
}
public void run(){
synchronized(this){
while (true) {
t1();
}
}
}
}
--------------------------------------------------------------------------------
下面我們來講synchronized的4種用法吧:
1.方法聲明時使用,放在范圍操作符(public等)之后,返回類型聲明(void等)之前.即一次只能有一個線程進入該方法,其他線程要想在此時調用該方法,只能排隊等候,當前線程(就是在synchronized方法內部的線程)執行完該方法后,別的線程才能進入.
例如:
public synchronized void synMethod() {
//方法體
}
2.對某一代碼塊使用,synchronized后跟括號,括號里是變量,這樣,一次只有一個線程進入該代碼塊.例如:
public int synMethod(int a1){
synchronized(a1) {
//一次只能有一個線程進入
}
}
3.synchronized后面括號里是一對象,此時,線程獲得的是對象鎖.例如:
public class MyThread implements Runnable {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt, "t1");
Thread t2 = new Thread(mt, "t2");
Thread t3 = new Thread(mt, "t3");
Thread t4 = new Thread(mt, "t4");
Thread t5 = new Thread(mt, "t5");
Thread t6 = new Thread(mt, "t6");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName());
}
}
}
對于3,如果線程進入,則得到對象鎖,那么別的線程在該類所有對象上的任何操作都不能進行.在對象級使用鎖通常是一種比較粗糙的方法.為什么要將整個對象都上鎖,而不允許其他線程短暫地使用對象中其他同步方法來訪問共享資源?如果一個對象擁有多個資源,就不需要只為了讓一個線程使用其中一部分資源,就將所有線程都鎖在外面.由于每個對象都有鎖,可以如下所示使用虛擬對象來上鎖:
class FineGrainLock {
MyMemberClass x, y;
Object xlock = new Object(), ylock = new Object();
public void foo() {
synchronized(xlock) {
//access x here
}
//do something here - but don't use shared resources
synchronized(ylock) {
//access y here
}
}
public void bar() {
synchronized(this) {
//access both x and y here
}
//do something here - but don't use shared resources
}
}
4.synchronized后面括號里是類.例如:
class ArrayWithLockOrder{
private static long num_locks = 0;
private long lock_order;
private int[] arr;
public ArrayWithLockOrder(int[] a)
{
arr = a;
synchronized(ArrayWithLockOrder.class) {//-----------------------------------------這里
num_locks++; // 鎖數加 1.
lock_order = num_locks; // 為此對象實例設置唯一的 lock_order.
}
}
public long lockOrder()
{
return lock_order;
}
public int[] array()
{
return arr;
}
}
class SomeClass implements Runnable
{
public int sumArrays(ArrayWithLockOrder a1,
ArrayWithLockOrder a2)
{
int value = 0;
ArrayWithLockOrder first = a1; // 保留數組引用的一個
ArrayWithLockOrder last = a2; // 本地副本.
int size = a1.array().length;
if (size == a2.array().length)
{
if (a1.lockOrder() > a2.lockOrder()) // 確定并設置對象的鎖定
{ // 順序.
first = a2;
last = a1;
}
synchronized(first) { // 按正確的順序鎖定對象.
synchronized(last) {
int[] arr1 = a1.array();
int[] arr2 = a2.array();
for (int i=0; i<size; i++)
value += arr1[i] + arr2[i];
}
}
}
return value;
}
public void run() {
//...
}
}
對于4,如果線程進入,則線程在該類中所有操作不能進行,包括靜態變量和靜態方法,實際上,對于含有靜態方法和靜態變量的代碼塊的同步,我們通常用4來加鎖.
以上4種之間的關系:
鎖是和對象相關聯的,每個對象有一把鎖,為了執行synchronized語句,線程必須能夠獲得synchronized語句中表達式指定的對象的鎖,一個對象只有一把鎖,被一個線程獲得之后它就不再擁有這把鎖,線程在執行完synchronized語句后,將獲得鎖交還給對象.
在方法前面加上synchronized修飾符即可以將一個方法聲明為同步化方法.同步化方法在執行之前獲得一個鎖.如果這是一個類方法,那么獲得的鎖是和聲明方法的類相關的Class類對象的鎖.如果這是一個實例方法,那么此鎖是this對象的鎖.
--------------------------------------------------------------------------------
下面談一談一些常用的方法:
wait(),wait(long),notify(),notifyAll()等方法是當前類的實例方法,
wait()是使持有對象鎖的線程釋放鎖;
wait(long)是使持有對象鎖的線程釋放鎖時間為long(毫秒)后,再次獲得鎖,wait()和wait(0)等價;
notify()是喚醒一個正在等待該對象鎖的線程,如果等待的線程不止一個,那么被喚醒的線程由jvm確定;
notifyAll是喚醒所有正在等待該對象鎖的線程.
在這里我也重申一下,我們應該優先使用notifyAll()方法,因為喚醒所有線程比喚醒一個線程更容易讓jvm找到最適合被喚醒的線程.
對于上述方法,只有在當前線程中才能使用,否則報運行時錯誤java.lang.IllegalMonitorStateException: current thread not owner.
--------------------------------------------------------------------------------
下面,我談一下synchronized和wait(),notify()等的關系:
1.有synchronized的地方不一定有wait,notify
2.有wait,notify的地方必有synchronized.這是因為wait和notify不是屬于線程類,而是每一個對象都具有的方法,而且,這兩個方法都和對象鎖有關,有鎖的地方,必有synchronized.
另外,請注意一點:如果要把notify和wait方法放在一起用的話,必須先調用notify后調用wait,因為如果調用完wait,該線程就已經不是current thread了.如下例:
/**
* Title: Jdeveloper's Java Projdect
* Description: n/a
* Copyright: Copyright ? 2001
* Company: soho http://www.ChinaJavaWorld.com
* @author jdeveloper@21cn.com
* @version 1.0
*/
import java.lang.Runnable;
import java.lang.Thread;
public class DemoThread
implements Runnable {
public DemoThread() {
TestThread testthread1 = new TestThread(this, "1");
TestThread testthread2 = new TestThread(this, "2");
testthread2.start();
testthread1.start();
}
public static void main(String[] args) {
DemoThread demoThread1 = new DemoThread();
}
public void run() {
TestThread t = (TestThread) Thread.currentThread();
try {
if (!t.getName().equalsIgnoreCase("1")) {
synchronized (this) {
wait();
}
}
while (true) {
System.out.println("@time in thread" + t.getName() + "=" +
t.increaseTime());
if (t.getTime() % 10 == 0) {
synchronized (this) {
System.out.println("****************************************");
notify();
if (t.getTime() == 100)
break;
wait();
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
class TestThread
extends Thread {
private int time = 0;
public TestThread(Runnable r, String name) {
super(r, name);
}
public int getTime() {
return time;
}
public int increaseTime() {
return++time;
}
}
下面我們用生產者/消費者這個例子來說明他們之間的關系:
public class test {
public static void main(String args[]) {
Semaphore s = new Semaphore(1);
Thread t1 = new Thread(s, "producer1");
Thread t2 = new Thread(s, "producer2");
Thread t3 = new Thread(s, "producer3");
Thread t4 = new Thread(s, "consumer1");
Thread t5 = new Thread(s, "consumer2");
Thread t6 = new Thread(s, "consumer3");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class Semaphore
implements Runnable {
private int count;
public Semaphore(int n) {
this.count = n;
}
public synchronized void acquire() {
while (count == 0) {
try {
wait();
}
catch (InterruptedException e) {
//keep trying
}
}
count--;
}
public synchronized void release() {
while (count == 10) {
try {
wait();
}
catch (InterruptedException e) {
//keep trying
}
}
count++;
notifyAll(); //alert a thread that's blocking on this semaphore
}
public void run() {
while (true) {
if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("consumer")) {
acquire();
}
else if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("producer")) {
release();
}
System.out.println(Thread.currentThread().getName() + " " + count);
}
}
}
生產者生產,消費者消費,一般沒有沖突,但當庫存為0時,消費者要消費是不行的,但當庫存為上限(這里是10)時,生產者也不能生產.請好好研讀上面的程序,你一定會比以前進步很多.
上面的代碼說明了synchronized和wait,notify沒有絕對的關系,在synchronized聲明的方法,代碼塊中,你完全可以不用wait,notify等方法,但是,如果當線程對某一資源存在某種爭用的情況下,你必須適時得將線程放入等待或者喚醒.
------------------------
在java中,每個對象只有一個相應的monitor,一個mutex,而每一個monitor都可以有多個“doors”可以進入,即,同一個monitor中被守護的代碼可以在不同的地方,因為同一個對象可以出現在不同的代碼段,只要mutex鎖定的對象是同一個,每個入口都用Synchronized關鍵字表明,當一個線程通過了Synchronized關鍵字,它就所住了該monitor所有的doors。因此是mutex定義了monitor而不是代碼。
另外,wait和notify、notifyAll都是Object的方法,使用wait必須是The current thread must own this object's monitor
wait
public final void wait()
throws InterruptedExceptionCauses current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
InterruptedException - if another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
See Also:
notify(), notifyAll()
A thread becomes the owner of the object's monitor in one of three ways:
By executing a synchronized instance method of that object.
By executing the body of a synchronized statement that synchronizes on the object.
For objects of type Class, by executing a synchronized static method of that class.
Only one thread at a time can own an object's monitor.