工作需要,簡單查看,很多東西日后鉆研!先放在這
==================================================
synchronized 的語法:
synchronized 可以作為一個method的修飾符,也可以一段代碼里出現,先說在代碼里出:
其語法是
synchronized(xx) {
//your code
}
這個xx需要是一個Object,只要是一個Object就行,如:
String s="haha";
synchronized(s) {
//your code
}
不是Object不行,如:
int n=3;
synchronized(n) {
//your code
}
是不可以的,有autoboxing也不行。
如果你理解this的含義,那么
synchronized(this) {
//your code
}
也很好理解,它需要一個Object,而this是一個特殊的Object,當然可以這樣用。
再說synchronized 修飾 method的情況,如果synchronized修飾一個非static的method,
如:
public synchronized void aMethod() {
//some code
}
相當于:
public void aMethod() {
synchronized(this) {
//some code
}
}
修飾一個 static的method,
如:
public static synchronized void aMethod() {
//some code
}
相當于:
public static synchronized void aMethod() {
synchronized(XX.class) {
//some code
}
}
XX是這個方法所在的類,XX.class 也是一個Object,類型是Class而已,在一個
ClassLoader里,它是唯一的,就是獨一無二的object
總之synchronized的語法可以統一為: synchronized(a var) { do something }
synchronized 的語義:
這是我自己的理解,
synchronized(xx) {
//your code
}
的語義是,在xx這個Object的“授權”、“名義”、 “面子”下,執行 your code。要注
意的是,xx只能授權給一個人(線程),當xx授權給某個人執行后,就不能再授權給別人了
。 當那個人執行完那段代碼后,xx才能繼續授權給其它人執行,可以理解為,別人在xx的
授權下,執行完這段代碼后,把這個權利又還給xx了。 當xx不能授權給一個人時,這個人
必須等在這里,知道xx可以授權給它。 (上面說的人都是線程)
synchronized 的作用:
synchronized是用在多線程環境中的,作用簡單的說,就是不允許 “某些” 線程 同時執
行到一段代碼里。 這個 “某些”線程 怎么界定? 是由那個xx object決定的,就是當兩
個線程執行到 synchronized的時候,需要同一個Object授權時,這兩個線程不能同時執行
到需要授權的代碼。
極端情況是 系統你所有的線程都不能執行到這段代碼里,那么你就選一個極端唯一的
object作為xx,一般選Class object,如:
synchronized(String.class) {
}
==================對synchronized(this)的一些理解======================
一、當兩個并發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
五、以上規則對其它對象鎖同樣適用.
舉例說明:
一、當兩個并發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。
package ths;
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
結果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
package ths;
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt2.m4t1();
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt2.m4t2();
}
}, "t2"
);
t1.start();
t2.start();
}
}
結果:
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
//修改Thread2.m4t2()方法:
public void m4t2() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
結果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
//修改Thread2.m4t2()方法如下:
public synchronized void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
結果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
五、以上規則對其它對象鎖同樣適用:
package ths;
public class Thread3 {
class Inner {
private void m4t1() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
private void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
}
private void m4t1(Inner inner) {
synchronized(inner) { //使用對象鎖
inner.m4t1();
}
}
private void m4t2(Inner inner) {
inner.m4t2();
}
public static void main(String[] args) {
final Thread3 myt3 = new Thread3();
final Inner inner = myt3.new Inner();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt3.m4t1(inner);
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt3.m4t2(inner);
}
}, "t2"
);
t1.start();
t2.start();
}
}
結果:
盡管線程t1獲得了對Inner的對象鎖,但由于線程t2訪問的是同一個Inner中的非同步部分。所以兩個線程互不干擾。
t1 : Inner.m4t1()=4
t2 : Inner.m4t2()=4
t1 : Inner.m4t1()=3
t2 : Inner.m4t2()=3
t1 : Inner.m4t1()=2
t2 : Inner.m4t2()=2
t1 : Inner.m4t1()=1
t2 : Inner.m4t2()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=0
現在在Inner.m4t2()前面加上synchronized:
private synchronized void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
結果:
盡管線程t1與t2訪問了同一個Inner對象中兩個毫不相關的部分,但因為t1先獲得了對Inner的對象鎖,所以t2對Inner.m4t2()的訪問也被阻塞,因為m4t2()是Inner中的一個同步方法。
t1 : Inner.m4t1()=4
t1 : Inner.m4t1()=3
t1 : Inner.m4t1()=2
t1 : Inner.m4t1()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=4
t2 : Inner.m4t2()=3
t2 : Inner.m4t2()=2
t2 : Inner.m4t2()=1
t2 : Inner.m4t2()=0
==============
關于java中的synchronized關鍵字的理解==============
搞清楚synchronized鎖定的是哪個對象,就能幫助我們設計更安全的多線程程序
●首先是synchronized關鍵字的作用域可以有兩種:
?某個對象實例內
synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法(如果一個對象有多個synchronized方法,只要一個線 程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法)
?某個類的范圍
synchronized static aStaticMethod{}防止多個線程同時訪問這個類中的synchronized static 方法
●synchronized關鍵字是不能繼承的
●要注意的事項
?無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖――而且同步方法很可能還會被其他線程的對象訪問
?每個對象只有一個鎖(lock)與之相關聯
?實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制
●對共享資源的同步訪問更加安全的技巧
?定義private 的instance變量+它的 get方法,而不要定義public/protected的instance變量。如果將變量定義為public,對象在外界可以繞過同步方法的控制而直接取得它,并改動它。
?如果instance變量是一個對象,如數組或ArrayList什么的,那上述方法仍然不安全,因為當外界對象通過get方法拿到這個instance對象的引用后,又將其指向另一個對象,那么這個private變量也就變了,豈不是很危險。 這個時候就需要將get方法也加上synchronized同步,并且,只返回這個private對象的clone()――這樣,調用端得到的就是對象副本的引用了。
=====================
Java線程及同步(synchronized)樣例代碼=======================
import java.io.*;
import java.util.*;
import java.text.SimpleDateFormat;
public class TestThread extends Thread
{
private static Integer threadCounterLock; //用于同步,防止數據被寫亂
private static int threadCount; //本類的線程計數器
static
{
threadCount = 0;
threadCounterLock = new Integer(0);
}
public TestThread()
{
super();
}
public synchronized static void incThreadCount()
{
threadCount++;
System.out.println("thread count after enter: " + threadCount);
}
public synchronized static void decThreadCount()
{
threadCount--;
System.out.println("thread count after leave: " + threadCount);
}
public void run()
{
synchronized(threadCounterLock) //同步
{
threadCount++;
System.out.println("thread count after enter: " + threadCount);
}
//incThreadCount(); //和上面的語句塊是等價的
final long nSleepMilliSecs = 1000; //循環中的休眠時間
long nCurrentTime = System.currentTimeMillis();
long nEndTime = nCurrentTime + 30000; //運行截止時間
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try
{
while (nCurrentTime < nEndTime)
{
nCurrentTime = System.currentTimeMillis();
System.out.println("Thread " + this.hashCode() + ", current time: " + simpleDateFormat.format(new Date(nCurrentTime)));
try
{
sleep(nSleepMilliSecs);
}
catch(InterruptedException ex)
{
ex.printStackTrace();
}
}
}
finally
{
synchronized(threadCounterLock) //同步
{
threadCount--;
System.out.println("thread count after leave: " + threadCount);
}
//decThreadCount(); //和上面的語句塊是等價的
}
}
public static void main(String[] args)
{
TestThread[] testThread = new TestThread[2];
for (int i=0; i<testThread.length; i++)
{
testThread[i] = new TestThread();
testThread[i].start();
}
}
}
同步就是簡單的說我用的時候你不能用,大家用的要是一樣的就這樣!
比如說有只蘋果很好吃,我拉起來咬一口,放下,你再拉起咬一口,這就同步了,要是大家一起咬,呵呵,那就結婚吧,婚禮上常能看到這個,也不怕咬著嘴,嘻嘻嘻!
舉個例子,現在有個類,類中有一個私有成員一個蘋果,兩個方法一個看,一個吃。
現在不同步,我一“看”,哈哈一個蘋果,我“吃”四分之一了
你一“看”,哈哈一個蘋果,也“吃”四分之一了。
可能的情況就是都是看到一個蘋果但我的吃方法用在你的之前,所以可能你只能吃到3/4的1/4也就是3/16個而不是1/4個蘋果了。
現在加入同步鎖,我在吃的時候你看被鎖,等吃完了,你再看,啊3/4個蘋果,吃1/3好了了,就這樣!
===========================synchronized的一個簡單例子================================
public class TextThread
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自動生成方法存根
TxtThread tt = new TxtThread();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
synchronized(str)
{
if (num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.getMessage();
}
System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
}
}
}
}
}
上面的例子中為了制造一個時間差,也就是出錯的機會,使用了Thread.sleep(10)
Java對多線程的支持與同步機制深受大家的喜愛,似乎看起來使用了synchronized關鍵字就可以輕松地解決多線程共享數據同步問題。到底如何?――還得對synchronized關鍵字的作用進行深入了解才可定論。
總的說來,synchronized關鍵字可以作為函數的修飾符,也可作為函數內的語句,也就是平時說的同步方法和同步語句塊。如果再細的分類,synchronized可作用于instance變量、object reference(對象引用)、static函數和class literals(類名稱字面常量)身上。
在進一步闡述之前,我們需要明確幾點:
A.無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖――而且同步方法很可能還會被其他線程的對象訪問。
B.每個對象只有一個鎖(lock)與之相關聯。
C.實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
接著來討論synchronized用到不同地方對代碼產生的影響:
假設P1、P2是同一個類的不同對象,這個類中定義了以下幾種情況的同步塊或同步方法,P1、P2就都可以調用它們。
1. 把synchronized當作函數修飾符時,示例代碼如下:
Public synchronized void methodAAA()
{
//….
}
這也就是同步方法,那這時synchronized鎖定的是哪個對象呢?它鎖定的是調用這個同步方法對象。也就是說,當一個對象P1在不同的線程中執行這個同步方法時,它們之間會形成互斥,達到同步的效果。但是這個對象所屬的Class所產生的另一對象P2卻可以任意調用這個被加了synchronized關鍵字的方法。
上邊的示例代碼等同于如下代碼:
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)處的this指的是什么呢?它指的就是調用這個方法的對象,如P1。可見同步方法實質是將synchronized作用于object reference。――那個拿到了P1對象鎖的線程,才可以調用P1的同步方法,而對P2而言,P1這個鎖與它毫不相干,程序也可能在這種情形下擺脫同步機制的控制,造成數據混亂:(
2.同步塊,示例代碼如下:
public void method3(SomeObject so)
{
synchronized(so)
{
//…..
}
}
這時,鎖就是so這個對象,誰拿到這個鎖誰就可以運行它所控制的那段代碼。當有一個明確的對象作為鎖時,就可以這樣寫程序,但當沒有明確的對象作為鎖,只是想讓一段代碼同步時,可以創建一個特殊的instance變量(它得是一個對象)來充當鎖:
class Foo implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance變量
Public void methodA()
{
synchronized(lock) { //… }
}
//…..
}
注:零長度的byte數組對象創建起來將比任何對象都經濟――查看編譯后的字節碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。
3.將synchronized作用于static 函數,示例代碼如下:
Class Foo
{
public synchronized static void methodAAA() // 同步的static 函數
{
//….
}
public void methodBBB()
{
synchronized(Foo.class) // class literal(類名稱字面常量)
}
}
代碼中的methodBBB()方法是把class literal作為鎖的情況,它和同步的static函數產生的效果是一樣的,取得的鎖很特別,是當前調用這個方法的對象所屬的類(Class,而不再是由這個Class產生的某個具體對象了)。
記得在《Effective Java》一書中看到過將 Foo.class和 P1.getClass()用于作同步鎖還不一樣,不能用P1.getClass()來達到鎖這個Class的目的。P1指的是由Foo類產生的對象。
可以推斷:如果一個類中定義了一個synchronized的static函數A,也定義了一個synchronized 的instance函數B,那么這個類的同一對象Obj在多線程中分別訪問A和B兩個方法時,不會構成同步,因為它們的鎖都不一樣。A方法的鎖是Obj這個對象,而B的鎖是Obj所屬的那個Class。
小結如下:
搞清楚synchronized鎖定的是哪個對象,就能幫助我們設計更安全的多線程程序。
還有一些技巧可以讓我們對共享資源的同步訪問更加安全:
1. 定義private 的instance變量+它的 get方法,而不要定義public/protected的instance變量。如果將變量定義為public,對象在外界可以繞過同步方法的控制而直接取得它,并改動它。這也是JavaBean的標準實現方式之一。
2. 如果instance變量是一個對象,如數組或ArrayList什么的,那上述方法仍然不安全,因為當外界對象通過get方法拿到這個instance對象的引用后,又將其指向另一個對象,那么這個private變量也就變了,豈不是很危險。這個時候就需要將get方法也加上synchronized同步,并且,只返回這個private對象的clone()――這樣,調用端得到的就是對象副本的引用了。
==================
搞懂java中的synchronized關鍵字====================
2006年12月27日 上午 10:00 | 作者:littlebat
實際上,我關于java的基礎知識的90%以上都來自Thinking in Java。對于其中的synchronized關鍵字,當時就是瀏覽一下,大概知道意思,也沒有細看。后來一直沒有用過這個關鍵字。昨天看Thinking in Patterns with Java中的Observer模式,看了其中的Observable類的源碼,發現里面幾乎所有的方法都用了synchronized關鍵字(不是全部),其中個別用了synchronized(this){}的區塊。于是,我發覺有必要好好理解一下這個關鍵字了。
我再次看了侯捷譯的Thinking in Java第二版中文版,得到有關synchronized的如下信息:
1、synchronized關鍵字的作用域有二種:
1)是某個對象實例內,synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法(如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法)。這時,不同的對象實例的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象實例中的synchronized方法;
2)是某個類的范圍,synchronized static aStaticMethod{}防止多個線程同時訪問這個類中的synchronized static 方法。它可以對類的所有對象實例起作用。
2、除了方法前用synchronized關鍵字,synchronized關鍵字還可以用于方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。用法是: synchronized(this){/*區塊*/},它的作用域是當前對象;
3、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中并不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法為synchronized方法;
4、疑問:
我知道了有static區塊(其中的加載時機好像也不清楚,需要學習一下,原來看Thinking in Java好像是說: static區塊加載的時機是類的一個對象實例創建或類的一個static方法被訪問,但是我今天早上看了javaeye.com上面的一個關于“<static塊到底什么時候執行?-eway -JavaEye技術社區>”的文章后又有點迷糊了:),也許,需要認真學習一下JVM的內在機制才是理解這類問題最根本的途徑),也有synchronized區塊,那么,有synchronized static 區塊嗎?意思是問:有沒有一個類作用域內的synchronized區塊?
==============ThreadLocal與synchronized ====================
Java良好的支持多線程。使用java,我們可以很輕松的編程一個多線程程序。但是使用多線程可能會引起并發訪問的問題。synchronized和ThreadLocal都是用來解決多線程并發訪問的問題。大家可能對synchronized較為熟悉,而對ThreadLocal就要陌生得多了。
并發問題。當一個對象被兩個線程同時訪問時,可能有一個線程會得到不可預期的結果。
一個簡單的java類Studnet
代碼
- public class Student {
- private int age=0;
-
- public int getAge() {
- return this.age;
-
- }
-
- public void setAge(int age) {
- this.age = age;
- }
- }
一個多線程類ThreadDemo.
這個類有一個Student的私有變量,在run方法中,它隨機產生一個整數。然后設置到student變量中,從student中讀取設置后的值。然后睡眠5秒鐘,最后再次讀student的age值。
代碼
- public class ThreadDemo implements Runnable{
- Student student = new Student();
- public static void main(String[] agrs) {
- ThreadDemo td = new ThreadDemo();
- Thread t1 = new Thread(td,"a");
- Thread t2 = new Thread(td,"b");
- t1.start();
- t2.start();
-
- }
-
-
-
- public void run() {
- accessStudent();
- }
-
- public void accessStudent() {
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
-
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
-
- }
-
- }
運行這個程序,屏幕輸出如下:
a is running!
b is running!
thread b set age to:33
thread b first read age is:33
thread a set age to:81
thread a first read age is:81
thread b second read age is:81
thread a second read age is:81
需要注意的是,線程a在同一個方法中,第一次讀取student的age值與第二次讀取值不一致。這就是出現了并發問題。
synchronized
上面的例子,我們模似了一個并發問題。Java提供了同步機制來解決并發問題。synchonzied關鍵字可以用來同步變量,方法,甚至同步一個代碼塊。
使用了同步后,一個線程正在訪問同步對象時,另外一個線程必須等待。
Synchronized同步方法
現在我們可以對accessStudent方法實施同步。
public synchronized void accessStudent()
再次運行程序,屏幕輸出如下:
a is running!
thread a set age to:49
thread a first read age is:49
thread a second read age is:49
b is running!
thread b set age to:17
thread b first read age is:17
thread b second read age is:17
加上了同步后,線程b必須等待線程a執行完畢后,線程b才開始執行。
對方法進行同步的代價是非常昂貴的。特別是當被同步的方法執行一個冗長的操作。這個方法執行會花費很長的時間,對這樣的方法進行同步可能會使系統性能成數量級的下降。
Synchronized同步塊
在accessStudent方法中,我們真實需要保護的是student變量,所以我們可以進行一個更細粒度的加鎖。我們僅僅對student相關的代碼塊進行同步。
代碼
- synchronized(this) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
運行方法后,屏幕輸出:
a is running!
thread a set age to:18
thread a first read age is:18
b is running!
thread a second read age is:18
thread b set age to:62
thread b first read age is:62
thread b second read age is:62
需要特別注意這個輸出結果。
這個執行過程比上面的方法同步要快得多了。
只有對student進行訪問的代碼是同步的,而其它與部份代碼卻是異步的了。而student的值并沒有被錯誤的修改。如果是在一個真實的系統中,accessStudent方法的操作又比較耗時的情況下。使用同步的速度幾乎與沒有同步一樣快。
使用同步鎖
稍微把上面的例子改一下,在ThreadDemo中有一個私有變量count,。
private int count=0;
在accessStudent()中, 線程每訪問一次,count都自加一次, 用來記數線程訪問的次數。
代碼
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
為了模擬線程,所以讓它每次自加后都睡眠5秒。
accessStuden()方法的完整代碼如下:
代碼
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- System.out.println("thread "+currentThreadName+" read count:"+this.count);
-
-
- synchronized(this) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
運行程序后,屏幕輸出:
a is running!
b is running!
thread a read count:2
thread a set age to:49
thread a first read age is:49
thread b read count:2
thread a second read age is:49
thread b set age to:7
thread b first read age is:7
thread b second read age is:7
我們仍然對student對象以synchronized(this)操作進行同步。
我們需要在兩個線程中共享count失敗。
所以仍然需要對count的訪問進行同步操作。
代碼
- synchronized(this) {
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName+" read count:"+this.count);
-
-
- synchronized(this) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
- long endTime = System.currentTimeMillis();
- long spendTime = endTime - startTime;
- System.out.println("花費時間:"+spendTime +"毫秒");
程序運行后,屏幕輸出
a is running!
b is running!
thread a read count:1
thread a set age to:97
thread a first read age is:97
thread a second read age is:97
花費時間:10015毫秒
thread b read count:2
thread b set age to:47
thread b first read age is:47
thread b second read age is:47
花費時間:20124毫秒
我們在同一個方法中,多次使用synchronized(this)進行加鎖。有可能會導致太多額外的等待。
應該使用不同的對象鎖進行同步。
設置兩個鎖對象,分別用于student和count的訪問加鎖。
代碼
- private Object studentLock = new Object();
- private Object countLock = new Object();
-
- accessStudent()方法如下:
- long startTime = System.currentTimeMillis();
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
-
-
- synchronized(countLock) {
- try {
- this.count++;
- Thread.sleep(5000);
- }catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName+" read count:"+this.count);
-
-
- synchronized(studentLock) {
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
-
- this.student.setAge(age);
-
- System.out.println("thread "+currentThreadName+" first read age is:"+this.student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
- long endTime = System.currentTimeMillis();
- long spendTime = endTime - startTime;
- System.out.println("花費時間:"+spendTime +"毫秒");
這樣對count和student加上了兩把不同的鎖。
運行程序后,屏幕輸出:
a is running!
b is running!
thread a read count:1
thread a set age to:48
thread a first read age is:48
thread a second read age is:48
花費時間:10016毫秒
thread b read count:2
thread b set age to:68
thread b first read age is:68
thread b second read age is:68
花費時間:20046毫秒
與兩次使用synchronized(this)相比,使用不同的對象鎖,在性能上可以得到更大的提升。
由此可見synchronized是實現java的同步機制。同步機制是為了實現同步多線程對相同資源的并發訪問控制。保證多線程之間的通信。
可見,同步的主要目的是保證多線程間的數據共享。同步會帶來巨大的性能開銷,所以同步操作應該是細粒度的。如果同步使用得當,帶來的性能開銷是微不足道的。使用同步真正的風險是復雜性和可能破壞資源安全,而不是性能。
ThreadLocal
由上面可以知道,使用同步是非常復雜的。并且同步會帶來性能的降低。Java提供了另外的一種方式,通過ThreadLocal可以很容易的編寫多線程程序。從字面上理解,很容易會把ThreadLocal誤解為一個線程的本地變量。其它ThreadLocal并不是代表當前線程,ThreadLocal其實是采用哈希表的方式來為每個線程都提供一個變量的副本。從而保證各個線程間數據安全。每個線程的數據不會被另外線程訪問和破壞。
我們把第一個例子用ThreadLocal來實現,但是我們需要些許改變。
Student并不是一個私有變量了,而是需要封裝在一個ThreadLocal對象中去。調用ThreadLocal的set方法,ThreadLocal會為每一個線程都保持一份Student變量的副本。所以對student的讀取操作都是通過ThreadLocal來進行的。
代碼
- protected Student getStudent() {
- Student student = (Student)studentLocal.get();
- if(student == null) {
- student = new Student();
- studentLocal.set(student);
- }
- return student;
- }
-
- protected void setStudent(Student student) {
- studentLocal.set(student);
- }
accessStudent()方法需要做一些改變。通過調用getStudent()方法來獲得當前線程的Student變量,如果當前線程不存在一個Student變量,getStudent方法會創建一個新的Student變量,并設置在當前線程中。
Student student = getStudent();
student.setAge(age);
accessStudent()方法中無需要任何同步代碼。
完整的代碼清單如下:
TreadLocalDemo.java
代碼
- public class TreadLocalDemo implements Runnable {
- private final static ThreadLocal studentLocal = new ThreadLocal();
-
- public static void main(String[] agrs) {
- TreadLocalDemo td = new TreadLocalDemo();
- Thread t1 = new Thread(td,"a");
- Thread t2 = new Thread(td,"b");
-
- t1.start();
- t2.start();
-
-
-
- }
-
-
-
-
- public void run() {
- accessStudent();
- }
-
- public void accessStudent() {
-
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName+" is running!");
- Random random = new Random();
- int age = random.nextInt(100);
- System.out.println("thread "+currentThreadName +" set age to:"+age);
- Student student = getStudent();
- student.setAge(age);
- System.out.println("thread "+currentThreadName+" first read age is:"+student.getAge());
- try {
- Thread.sleep(5000);
- }
- catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- System.out.println("thread "+currentThreadName +" second read age is:"+student.getAge());
-
- }
-
- protected Student getStudent() {
- Student student = (Student)studentLocal.get();
- if(student == null) {
- student = new Student();
- studentLocal.set(student);
- }
- return student;
- }
-
- protected void setStudent(Student student) {
- studentLocal.set(student);
- }
- }
運行程序后,屏幕輸出:
b is running!
thread b set age to:0
thread b first read age is:0
a is running!
thread a set age to:17
thread a first read age is:17
thread b second read age is:0
thread a second read age is:17
可見,使用ThreadLocal后,我們不需要任何同步代碼,卻能夠保證我們線程間數據的安全。
而且,ThreadLocal的使用也非常的簡單。
我們僅僅需要使用它提供的兩個方法
void set(Object obj) 設置當前線程的變量的副本的值。
Object get() 返回當前線程的變量副本
另外ThreadLocal還有一個protected的initialValue()方法。返回變量副本在當前線程的初始值。默認為null
ThreadLocal是怎么做到為每個線程都維護一個變量的副本的呢?
我們可以猜測到ThreadLocal的一個簡單實現
代碼
- public class ThreadLocal
- {
- private Map values = Collections.synchronizedMap(new HashMap());
- public Object get()
- {
- Thread curThread = Thread.currentThread();
- Object o = values.get(curThread);
- if (o == null && !values.containsKey(curThread))
- {
- o = initialValue();
- values.put(curThread, o);
- }
- return o;
- }
-
- public void set(Object newValue)
- {
- values.put(Thread.currentThread(), newValue);
- }
-
- public Object initialValue()
- {
- return null;
- }
- }
由此可見,ThreadLocal通過一個Map來為每個線程都持有一個變量副本。這個map以當前線程為key。與synchronized相比,ThreadLocal是以空間換時間的策略來實現多線程程序。
Synchronized還是ThreadLocal?
ThreadLocal以空間換取時間,提供了一種非常簡便的多線程實現方式。因為多個線程并發訪問無需進行等待,所以使用ThreadLocal會獲得更大的性能。雖然使用ThreadLocal會帶來更多的內存開銷,但這點開銷是微不足道的。因為保存在ThreadLocal中的對象,通常都是比較小的對象。另外使用ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。
ThreadLocal和Synchonized都用于解決多線程并發訪問。但是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得數據共享。
Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離。
當然ThreadLocal并不能替代synchronized,它們處理不同的問題域。Synchronized用于實現同步機制,比ThreadLocal更加復雜。
=================Java 多線程程序設計要點(synchronized..) =====================
多線程程序設計要點:
1.多線程中有主內存和工作內存之分, 在JVM中,有一個主內存,專門負責所有線程共享數據;而每個線程都有他自己私有的工作內存, 主內存和工作內存分貝在JVM的stack區和heap區。
2.線程的狀態有'Ready', 'Running', 'Sleeping', 'Blocked', 和 'Waiting'幾個狀態,
'Ready' 表示線程正在等待CPU分配允許運行的時間。
3.線程運行次序并不是按照我們創建他們時的順序來運行的,CPU處理線程的順序是不確定的,如果需要確定,那么必須手工介入,使用setPriority()方法設置優先級。
4.我們無從知道一個線程什么時候運行,兩個或多個線程在訪問同一個資源時,需要synchronized
5. 每個線程會注冊自己,實際某處存在著對它的引用,因此,垃圾回收機制對它就“束手無策”了。
6. Daemon線程區別一般線程之處是:主程序一旦結束,Daemon線程就會結束。
7. 一個對象中的所有synchronized方法都共享一把鎖,這把鎖能夠防止多個方法對通用內存同時進行的寫操作。synchronized static方法可在一個類范圍內被相互間鎖定起來。
8. 對于訪問某個關鍵共享資源的所有方法,都必須把它們設為synchronized,否則就不能正常工作。
9. 假設已知一個方法不會造成沖突,最明智的方法是不要使用synchronized,能提高些性能。
10. 如果一個\"同步"方法修改了一個變量,而我們的方法要用到這個變量(可能是只讀),最好將自己的這個方法也設為 synchronized。
11. synchronized不能繼承, 父類的方法是synchronized,那么其子類重載方法中就不會繼承“同步”。
12. 線程堵塞Blocked有幾個原因造成:
(1)線程在等候一些IO操作
(2)線程試圖調用另外一個對象的“同步”方法,但那個對象處于鎖定狀態,暫時無法使用。
13.原子型操作(atomic), 對原始型變量(primitive)的操作是原子型的atomic. 意味著這些操作是線程安全的, 但是大部分情況下,我們并不能正確使用,來看看 i = i + 1 , i是int型,屬于原始型變量:
(1)從主內存中讀取i值到本地內存.
(2)將值從本地內存裝載到線程工作拷貝中.
(3)裝載變量1.
(4)將i 加 1.
(5)將結果給變量i.
(6)將i保存到線程本地工作拷貝中.
(7)寫回主內存.
注意原子型操作只限于第1步到第2步的讀取以及第6到第7步的寫, i的值還是可能被同時執行i=i+1的多線程中斷打擾(在第4步)。
double 和long 變量是非原子型的(non-atomic)。數組是object 非原子型。
14. 由于13條的原因,我們解決辦法是:
class xxx extends Thread{
//i會被經常修改
private int i;
public synchronized int read(){ return i;}
public synchronized void update(){ i = i + 1;}
..........
}
15. Volatile變量, volatile變量表示保證它必須是與主內存保持一致,它實際是"變量的同步", 也就是說對于volatile變量的操作是原子型的,如用在long 或 double變量前。
16. 使用yield()會自動放棄CPU,有時比sleep更能提升性能。
17. sleep()和wait()的區別是:wait()方法被調用時會解除鎖定,但是我們能使用它的地方只是在一個同步的方法或代碼塊內。
18. 通過制造縮小同步范圍,盡可能的實現代碼塊同步,wait(毫秒數)可在指定的毫秒數可退出wait;對于wait()需要被notisfy()或notifyAll()踢醒。
19. 構造兩個線程之間實時通信的方法分幾步:
(1). 創建一個PipedWriter和一個PipedReader和它們之間的管道;
PipedReader in = new PipedReader(new PipedWriter())
(2). 在需要發送信息的線程開始之前,將外部的PipedWriter導向給其內部的Writer實例out
(3). 在需要接受信息的線程開始之前,將外部的PipedReader導向給其內部的Reader實例in
(4). 這樣放入out的所有東西度可從in中提取出來。
20. synchronized帶來的問題除性能有所下降外,最大的缺點是會帶來死鎖DeadLock,只有通過謹慎設計來防止死鎖,其他毫無辦法,這也是線程難以馴服的一個原因。不要再使用stop() suspend() resume()和destory()方法
21. 在大量線程被堵塞時,最高優先級的線程先運行。但是不表示低級別線程不會運行,運行概率小而已。
22. 線程組的主要優點是:使用單個命令可完成對整個線程組的操作。很少需要用到線程組。
23. 從以下幾個方面提升多線程的性能:
檢查所有可能Block的地方,盡可能的多的使用sleep或yield()以及wait();
盡可能延長sleep(毫秒數)的時間;
運行的線程不用超過100個,不能太多;
不同平臺linux或windows以及不同JVM運行性能差別很大。
========================================================================
更多文章:
http://scholar.ilib.cn/A-xdjsj-xby200301007.html
posted on 2007-10-16 09:57
lk 閱讀(784)
評論(0) 編輯 收藏 所屬分類:
j2se