㈠ java類的實例化順序是什麼樣的Java線程同步的方式有哪些
引言:java是在1990年初 ,被詹姆斯•高斯林等人開發的一門面向對象的編程語言。起初,java被稱為0ak,來經過發展0ak改名為java,與1995年的五月份正式向大家發布。
java的實例化順序在繼承沒有的情況
單獨一個類的場景下,初始化順序為依次為靜態數據,繼承的基類的構造函數,成員變數,被調用的構造函數。
其中靜態數據只會初始化一次。(靜態數據包括靜態代碼塊和靜態變數,每個類的靜態數據只會初始化一次)
在繼承的情況下
添加兩個基類,讓繼承父親,父親繼承祖父。
繼承的情況就比較復雜了。由繼承了基類,還將往上回溯,遞歸地調用基類的無參構造方法。
在我們的例子中,在初始化靜態數據後,會先往上追溯,調用父的默認構造方法,此時再往上追溯到爺爺的默認構造方法。
無論是java還是什麼別的東西他都體現了現代社會與信息技術的不斷發展,人們在進行進行技術開發時也有了越來越多的方法。程序類的工作也有了更為快捷的方法,這為信息技術的發展也提供了更好的發展方法
㈡ JAVA中線程同步方法有哪些
JAVA中線程同步方法一般有以下三種:
1 wait方法:
該方法屬於Object的方法,wait方法的作用是使得當前調用wait方法所在部分(代碼塊)的線程停止執行,並釋放當前獲得的調用wait所在的代碼塊的鎖,並在其他線程調用notify或者notifyAll方法時恢復到競爭鎖狀態(一旦獲得鎖就恢復執行)。
調用wait方法需要注意幾點:
第一點:wait被調用的時候必須在擁有鎖(即synchronized修飾的)的代碼塊中。
第二點:恢復執行後,從wait的下一條語句開始執行,因而wait方法總是應當在while循環中調用,以免出現恢復執行後繼續執行的條件不滿足卻繼續執行的情況。
第三點:若wait方法參數中帶時間,則除了notify和notifyAll被調用能激活處於wait狀態(等待狀態)的線程進入鎖競爭外,在其他線程中interrupt它或者參數時間到了之後,該線程也將被激活到競爭狀態。
第四點:wait方法被調用的線程必須獲得之前執行到wait時釋放掉的鎖重新獲得才能夠恢復執行。
2 notify方法和notifyAll方法:
notify方法通知調用了wait方法,但是尚未激活的一個線程進入線程調度隊列(即進入鎖競爭),注意不是立即執行。並且具體是哪一個線程不能保證。另外一點就是被喚醒的這個線程一定是在等待wait所釋放的鎖。
notifyAll方法則喚醒所有調用了wait方法,尚未激活的進程進入競爭隊列。
3 synchronized關鍵字:
第一點:synchronized用來標識一個普通方法時,表示一個線程要執行該方法,必須取得該方法所在的對象的鎖。
第二點:synchronized用來標識一個靜態方法時,表示一個線程要執行該方法,必須獲得該方法所在的類的類鎖。
第三點:synchronized修飾一個代碼塊。類似這樣:synchronized(obj) { //code.... }。表示一個線程要執行該代碼塊,必須獲得obj的鎖。這樣做的目的是減小鎖的粒度,保證當不同塊所需的鎖不沖突時不用對整個對象加鎖。利用零長度的byte數組對象做obj非常經濟。
㈢ java中同步有幾種方式啊
1。同步代碼塊:
synchronized(同一個數據){} 同一個數據:就是N條線程同時訪問一個數據。
2。
同步方法:
public synchronized 數據返回類型 方法名(){}
就
是使用 synchronized 來修飾某個方法,則該方法稱為同步方法。對於同步方法而言,無需顯示指定同步監視器,同步方法的同步監視器是
this
也就是該對象的本身(這里指的對象本身有點含糊,其實就是調用該同步方法的對象)通過使用同步方法,可非常方便的將某類變成線程安全的類,具有如下特徵:
1,該類的對象可以被多個線程安全的訪問。
2,每個線程調用該對象的任意方法之後,都將得到正確的結果。
3,每個線程調用該對象的任意方法之後,該對象狀態依然保持合理狀態。
註:synchronized關鍵字可以修飾方法,也可以修飾代碼塊,但不能修飾構造器,屬性等。
實現同步機制注意以下幾點: 安全性高,性能低,在多線程用。性能高,安全性低,在單線程用。
1,不要對線程安全類的所有方法都進行同步,只對那些會改變共享資源方法的進行同步。
2,如果可變類有兩種運行環境,當線程環境和多線程環境則應該為該可變類提供兩種版本:線程安全版本和線程不安全版本(沒有同步方法和同步塊)。在單線程中環境中,使用線程不安全版本以保證性能,在多線程中使用線程安全版本.
線程通訊:
為什麼要使用線程通訊?
當
使用synchronized
來修飾某個共享資源時(分同步代碼塊和同步方法兩種情況),當某個線程獲得共享資源的鎖後就可以執行相應的代碼段,直到該線程運行完該代碼段後才釋放對該
共享資源的鎖,讓其他線程有機會執行對該共享資源的修改。當某個線程佔有某個共享資源的鎖時,如果另外一個線程也想獲得這把鎖運行就需要使用wait()
和notify()/notifyAll()方法來進行線程通訊了。
Java.lang.object 里的三個方法wait() notify() notifyAll()
wait方法導致當前線程等待,直到其他線程調用同步監視器的notify方法或notifyAll方法來喚醒該線程。
wait(mills)方法
都是等待指定時間後自動蘇醒,調用wait方法的當前線程會釋放該同步監視器的鎖定,可以不用notify或notifyAll方法把它喚醒。
notify()
喚醒在同步監視器上等待的單個線程,如果所有線程都在同步監視器上等待,則會選擇喚醒其中一個線程,選擇是任意性的,只有當前線程放棄對該同步監視器的鎖定後,也就是使用wait方法後,才可以執行被喚醒的線程。
notifyAll()方法
喚醒在同步監視器上等待的所有的線程。只用當前線程放棄對該同步監視器的鎖定後,才可以執行被喚醒的線程
㈣ 初學Java多線程:使用Synchronized塊同步方法
synchronized關鍵字有兩種用法 第一種就是在《使用Synchronized關鍵字同步類方法》一文中所介紹的直接用在方法的定義中 另外一種就是synchronized塊 我們不僅可以通過synchronized塊來同步一個對象變數巧頃 也可以使用synchronized塊來同步類中的靜態孝寬陸方法和非靜態方法
synchronized塊的語法如下
public void method()
{
… …
synchronized(表達式)
{
… …
}
}
一 非靜態類方法的同步
從《使用Synchronized關鍵字同步類方法》一文中我們知道使用synchronized關鍵字來定義方法就會鎖定類中所有使用synchronzied關鍵字定義的靜態方法或非靜態方法 但這並不好理解 而如果使用synchronized塊來達到同樣的效果 就不難理解為什麼會產生這種效果了 如果想使用synchronized塊來鎖定類中所有的同步非靜態方法 需要使用this做為synchronized塊的參數傳入synchronized塊國 代碼如下
通過synchronized塊同步非靜態方法
public class SyncBlock
{
public void method ()
{
synchronized(this) // 相當於對method 方法使用synchronized關鍵字
{
… …
}
}
public void method ()
{
synchronized(this) // 相當於對method 方法使用synchronized關鍵字
{
… …
}
}
public synchronized void method ()
{
… …
}
}
在上面的代碼中的method 和method 方法中使用了synchronized塊 而第 行的method 方法仍然使用synchronized關鍵字來定義方法 在使用同一個SyncBlock類實例時 這三個方法只要有一個正在執行 其他兩個方法就會因未獲得同步鎖而被阻塞 在使用synchronized塊時要想達到和synchronized關鍵字同樣的效果 必須將所有的代碼都寫在synchronized塊中 否則 將無法使當前方法中的所有代碼和其他的方法同步
除了使用this做為synchronized塊的參數外 還可以使用SyncBlock this作為synchronized塊的參數來達到同樣的效果
在內類(InnerClass)的方法中使用synchronized塊來時 this只表示內類 和外類(OuterClass)沒有關系 但內類的非靜態方法可以和外類的非靜態方法同步 如在內類InnerClass中加一個method 方法 並使method 方法和SyncBlock的三個方法同步 代碼如下
使內類的非靜態方法和外類的非靜態方法同步
public class SyncBlock
{
… …
class InnerClass
{
public void method ()
{
synchronized(SyncBlock this)
{
… …
}
}
}
… …
}
在上面SyncBlock類的新版本中 InnerClass類的method 方法和SyncBlock類的其他三個方法同步 因此 method method method 和method 四個方法在同一時間巧手只能有一個方法執行
Synchronized塊不管是正常執行完 還是因為程序出錯而異常退出synchronized塊 當前的synchronized塊所持有的同步鎖都會自動釋放 因此 在使用synchronized塊時不必擔心同步鎖的釋放問題
二 靜態類方法的同步
由於在調用靜態方法時 對象實例不一定被創建 因此 就不能使用this來同步靜態方法 而必須使用Class對象來同步靜態方法 代碼如下
通過synchronized塊同步靜態方法
public class StaticSyncBlock
{
public static void method ()
{
synchronized(StaticSyncBlock class)
{
… …
}
}
public static synchronized void method ()
{
… …
}
}
在同步靜態方法時可以使用類的靜態欄位class來得到Class對象 在上例中method 和method 方法同時只能有一個方法執行 除了使用class欄位得到Class對象外 還可以使用實例的getClass方法來得到Class對象 上例中的代碼可以修改如下
使用getClass方法得到Class對象
public class StaticSyncBlock
{
public static StaticSyncBlock instance;
public StaticSyncBlock()
{
instance = this;
}
public static void method ()
{
synchronized(instance getClass())
{
}
}
}
在上面代碼中通過一個public的靜態instance得到一個StaticSyncBlock類的實例 並通過這個實例的getClass方法得到了Class對象(一個類的所有實例通過getClass方法得到的都是同一個Class對象 因此 調用任何一個實例的getClass方法都可以) 我們還可以通過Class對象使不同類的靜態方法同步 如Test類的靜態方法method和StaticSyncBlock類的兩個靜態方法同步 代碼如下
Test類的method方法和StaticSyncBlock類的method method 方法同步
public class Test
{
public static void method()
{
synchronized(StaticSyncBlock class)
{
}
}
}
lishixin/Article/program/Java/gj/201311/27374
㈤ java 方法同步
設置三個同步變數不就完了,然後synchronized的時候就同步這三個變數區分就行了
㈥ java中實現同步的兩種方式syschronized和lock的區別和聯系
Lock是java.util.concurrent.locks包下的介面,Lock實現提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作,它能以更優雅的方式處理線程同步問題,我們拿Java線程(二)中的一個例子簡單的實現一下和sychronized一樣的效果,代碼如下:
[java]view plain
Thread-4准備讀取數據
Thread-3准備讀取數據
Thread-5准備讀取數據
Thread-5讀取18
Thread-4讀取18
Thread-3讀取18
Thread-2准備寫入數據
Thread-2寫入6
Thread-2准備寫入數據
Thread-2寫入10
Thread-1准備寫入數據
Thread-1寫入22
Thread-5准備讀取數據
從結果可以看出實現了我們的需求,這只是鎖的基本用法,鎖的機制還需要繼續深入學習。
本文來自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7461369,轉載請註明。
在java中有兩種方式實現原子性操作(即同步操作):
1)使用同步關鍵字synchronized
2)使用lock鎖機制其中也包括相應的讀寫鎖
package com.xiaohao.test;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test {
public static void main(String[] args) {
final LockTest lock=new LockTest();
//輸出張三
new Thread(){
public void run(){
lock.test("張三張三張三張三張三張三張三張三張三張三");
}
}.start();
//輸出李四
new Thread(){
public void run(){
lock.test("李四李四李四李四李四李四李四李四李四李四");System.out.println
("
---------------------------------------------------------------");
}
}.start();
//---------------------------------------------------------------
//模擬寫入數據的
for (int i = 0; i < 3; i++) {
new Thread(){
public void run() {
for (int j = 0; j < 5; j++) {
// lock.set(new Random().nextInt(30));
lock.set2(new Random().nextInt(30));
}
}
}.start();
}
//模擬讀取數據的
for (int i = 0; i < 3; i++) {
new Thread(){
public void run() {
for (int j = 0; j < 5; j++) {
// lock.get();
lock.get2();
}
}
}.start();
}
}
}
class LockTest{
private Lock lock=new ReentrantLock(); //創建普通的鎖
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();//創建讀寫鎖
private int data;// 共享數據
//實現同步的方法一 使用同步關鍵字 synchronized
public synchronized void test(String name){
//下面的相關操作是一個原子性的操作
// lock.lock();// 得到鎖
try {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
} finally {
// lock.unlock();// 釋放鎖
}
}
//實現同步的方法二 使用lock鎖機制
public void test2(String name){
//下面的相關操作是一個原子性的操作
lock.lock();// 得到鎖
try {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
} finally {
lock.unlock();// 釋放鎖
}
}
//使用set方法模擬寫入數據
//使用 synchronized 實現了讀讀,寫寫,讀寫之間的互斥 ,但讀讀之間的互斥是沒有什麼必要的
public synchronized void set(int data){
System.out.println(Thread.currentThread().getName() + "准備寫入數據");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "寫入" + this.data);
}
//使用get方法模擬讀取數據
//使用 synchronized 實現了讀讀,寫寫,讀寫之間的互斥 ,但讀讀之間的互斥是沒有什麼必要的
public synchronized void get() {
System.out.println(Thread.currentThread().getName() + "准備讀取數據");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "讀取" + this.data);
}
//使用set方法模擬寫入數據
//使用 讀寫鎖實現了寫寫,讀寫之間的互斥 ,但讀讀之間的互斥是沒有什麼必要的
public void set2(int data){
readWriteLock.writeLock().lock();//獲取寫入鎖
try{
System.out.println(Thread.currentThread().getName() + "准備寫入數據");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "寫入" + this.data);
}
finally{
readWriteLock.writeLock().unlock();
}
}
//使用get方法模擬讀取數據
//使用 讀寫鎖實現了寫寫,讀寫之間的互斥 ,但讀讀之間的互斥是沒有什麼必要的
public void get2() {
//獲取相應的讀鎖
readWriteLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName() + "准備讀取數據");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "讀取" + this.data);
}
finally{
// 釋放相應的寫鎖
readWriteLock.readLock().unlock();
}
}
}
線程同步經典版:
package com.xiaohao.test;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test2{
public static void main(String[] args){
final LockTest2 lockTest=new LockTest2();
for(int i=0;i<3;i++) {
new Thread(){
public void run(){
try {
for (int j = 0; j < 3; j++) {
lockTest.setValue();
} } catch (InterruptedException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
}.start();
}
for(int i=0;i<3;i++) {
new Thread(){
public void run(){
try {
for (int j = 0; j < 3; j++) {
lockTest.getValue();
}
} catch (InterruptedException e)
{ // TODO Auto-generated catch block e.printStackTrace(); }
}
}.start();
}
}
}
class LockTest2 {
int data=0;
ReentrantReadWriteLock lock= new ReentrantReadWriteLock();// 鎖對象
public void setValue() throws InterruptedException{
lock.writeLock().lock();
System.out.println("正在使用寫鎖......");
data=(int) (Math.random()*10);
System.out.println("正在寫入:"+data);
Thread.sleep(500);
System.out.println("寫鎖調用完畢---------------------------");
lock.writeLock().unlock(); }
public void getValue() throws InterruptedException{
lock.readLock().lock();
System.out.println("正在使用讀鎖...........................................");
System.out.println("正在讀入:"+data); Thread.sleep(500);
System.out.println("讀鎖調用完畢......");
lock.readLock().unlock();
}
}
**** 當一個線程進入了一個對象是的synchronized方法,那麼其它線程還能掉否調用此對象的其它方法?
這個問題需要分幾種情況進行討論。
1)查看其它方法是否使用了同步關鍵字(synchronized)修飾,如果沒有的話就可以調用相關的方法。
2)在當前synchronized方法中是否調用了wait方法,如果調用了,則對應的鎖已經釋放,可以訪問了。
3)如果其它方法也使用synchronized修飾,並且當前同步方法中沒有調用wait方法的話,這樣是不允許訪問的。
4)如果其它方法是靜態方法的話,由於靜態方法和對象是扯不上什麼關系,對於靜態同步方法而言,其對應的同步監視器為當前類的位元組碼
所以肯定可以訪問的了。
㈦ Java多線程初學者指南(10):使用Synchronized關鍵字同步類方法
要想解決 臟數據 的問題 最簡單的方法就是使用synchronized關鍵字來使run方法同步 代碼如下
publicsynchronizedvoidrun(){}
從上面的代碼可以看出 只要在void和public之間加上synchronized關鍵字 就可以使run方法同步 也就是說 對於同一個Java類的對象實例 run方法同時只能被一個線程調用 並當前的run執行完後 才能被其他的線程調用 即使當前線程執行到了run方法中的yield方法 也只是暫停了一下 由於其他線程無法執行run方法 因此 最終還是會由當前的線程來繼續執行 先看看下面的代碼
sychronized關鍵字只和一個對象實例綁定
classTest{publicsynchronizedvoidmethod(){}納察}{privateTesttest;publicvoidrun() { thod(); } publicSync(Testtest) { this test=test; } publicstaticvoidmain(String[]args)throwsException { Testtest =newTest(); Testtest =newTest(); Syncsync =newSync(test ); Syncsync =newSync(test ); newThread(sync ) start(); newThread(sync ) start(); } }
在Test類中的method方法是同步的 但上面的代碼建立了兩個Test類的實例 因此 test 和test 的method方法是分別執行的 要想讓method同步 必須在建立仿茄升Sync類的實例時向它的構造方法中傳入同一個Test類的實例 如下面的代碼所示
Syncsync =newSync(test );
不僅可以使用synchronized來同步非靜態方法 也可以使用synchronized來同步靜態方法 如可以按如下方式來定義method方法
classTest{ (){}}
建立Test類的對象實例如下
Testtest=newTest();
對於靜態方法來說 只要加上了synchronized關鍵字 這個方法就是同步的 無論是使用thod() 還是使用thod()來調用method方法 method都是同步的 並不存在非靜態方法的多個實例的問題
在 種設計模式中的單件(Singleton)模式如果按傳統的方法設計 也是線程不安全的 下面的代碼是一個線程不安全的單件模式
packagetest;//線程安全的Singleton模式classSingleton{privatestaticSingletonsample;privateSingleton(){備老}(){if(sample==null){Thread yield();//為了放大Singleton模式的線程不安全性sample=newSingleton();}returnsample;}}{publicvoidrun(){Singletonsingleton=Singleton getInstance();System out println(singleton hashCode());}publicstaticvoidmain(String[]args){Threadthreads[]=newThread[ ];for(inti= ;i<threads length;i++)threads[i]=newMyThread();for(inti= ;i<threads length;i++)threads[i] start();}}
在上面的代碼調用yield方法是為了使單件模式的線程不安全性表現出來 如果將這行去掉 上面的實現仍然是線程不安全的 只是出現的可能性小得多
程序的運行結果如下
上面的運行結果可能在不同的運行環境上有所有同 但一般這五行輸出不會完全相同 從這個輸出結果可以看出 通過getInstance方法得到的對象實例是五個 而不是我們期望的一個 這是因為當一個線程執行了Thread yield()後 就將CPU資源交給了另外一個線程 由於在線程之間切換時並未執行到創建Singleton對象實例的語句 因此 這幾個線程都通過了if判斷 所以 就會產生了建立五個對象實例的情況(可能創建的是四個或三個對象實例 這取決於有多少個線程在創建Singleton對象之前通過了if判斷 每次運行時可能結果會不一樣)
要想使上面的單件模式變成線程安全的 只要為getInstance加上synchronized關鍵字即可 代碼如下
(){}
當然 還有更簡單的方法 就是在定義Singleton變數時就建立Singleton對象 代碼如下
=newSingleton();
然後在getInstance方法中直接將sample返回即可 這種方式雖然簡單 但不知在getInstance方法中創建Singleton對象靈活 讀者可以根據具體的需求選擇使用不同的方法來實現單件模式
在使用synchronized關鍵字時有以下四點需要注意
synchronized關鍵字不能繼承
雖然可以使用synchronized來定義方法 但synchronized並不屬於方法定義的一部分 因此 synchronized關鍵字不能被繼承 如果在父類中的某個方法使用了synchronized關鍵字 而在子類中覆蓋了這個方法 在子類中的這個方法默認情況下並不是同步的 而必須顯式地在子類的這個方法中加上synchronized關鍵字才可以 當然 還可以在子類方法中調用父類中相應的方法 這樣雖然子類中的方法不是同步的 但子類調用了父類的同步方法 因此 子類的方法也就相當於同步了 這兩種方式的例子代碼如下
在子類方法中加上synchronized關鍵字
classParent{ publicsynchronizedvoidmethod(){}}classChildextendsParent{ publicsynchronizedvoidmethod(){}}
在子類方法中調用父類的同步方法
classParent{ publicsynchronizedvoidmethod(){}}classChildextendsParent{publicvoidmethod(){thod();}}
在定義介面方法時不能使用synchronized關鍵字
構造方法不能使用synchronized關鍵字 但可以使用下節要討論的synchronized塊來進行同步
synchronized可以自由放置
在前面的例子中使用都是將synchronized關鍵字放在方法的返回類型前面 但這並不是synchronized可放置唯一位置 在非靜態方法中 synchronized還可以放在方法定義的最前面 在靜態方法中 synchronized可以放在static的前面 代碼如下
publicsynchronizedvoidmethod();synchronizedpublicvoidmethod();();();();
但要注意 synchronized不能放在方法返回類型的後面 如下面的代碼是錯誤的
publicvoidsynchronizedmethod();();
synchronized關鍵字只能用來同步方法 不能用來同步類變數 如下面的代碼也是錯誤的
publicsynchronizedintn= ;publicstaticsynchronizedintn= ;
雖然使用synchronized關鍵字同步方法是最安全的同步方式 但大量使用synchronized關鍵字會造成不必要的資源消耗以及性能損失 雖然從表面上看synchronized鎖定的是一個方法 但實際上synchronized鎖定的是一個類 也就是說 如果在非靜態方法method 和method 定義時都使用了synchronized 在method 未執行完之前 method 是不能執行的 靜態方法和非靜態方法的情況類似 但靜態和非靜態方法不會互相影響 看看如下的代碼
packagetest;publicclassMyThread extendsThread{publicStringmethodName;publicstaticvoidmethod(Strings){System out println(s);while(true);}publicsynchronizedvoidmethod (){method( 非靜態的method 方法 );}publicsynchronizedvoidmethod (){method( 非靜態的method 方法 );} (){method( 靜態的method 方法 );} (){method( 靜態的method 方法 );}publicvoidrun(){try{getClass() getMethod(methodName) invoke(this);}catch(Exceptione){}}publicstaticvoidmain(String[]args)throwsException{MyThread myThread =newMyThread ();for(inti= ;i<= ;i++){thodName= method +String valueOf(i);newThread(myThread ) start();sleep( );}}}
運行結果如下
非靜態的method 方法靜態的method 方法
lishixin/Article/program/Java/gj/201311/27526
㈧ java類內多個函數如何同步
線程間的通訊首要的方式就是對欄位及其欄位所引用的對象的共享訪問。這種通信方式是及其高效的,但是也是導致了可能的錯誤:線程間相互干涉和內存一致性的問題。避免出現這兩種錯誤的方法就是同步。
線程間相互干擾描述了當多個線程訪問共享數據時可能出現的錯誤。
內存一致性錯誤描述的了共享內存可能導致的錯誤。
同步方法(Synchronized method)描述了一種簡單的可以有效防止線程間相互干擾及其內存一致性錯誤的方法。
明鎖及同步描述了一種更加通用的同步方法,以及同步是如何基於明鎖而實現的。
原子性描述了不能被其它線程干擾的操作。
線程間的相互干擾
考慮下面的一個簡單的類Counter:
[java] view plain
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
不難看出,其中的increment()方法用來對c加1;decrement()方法用來對c減1。然而,有多個線程中都存在對某個Counter對象的引用,那麼線程間的干擾就可能導致出現我們不想要的結果。
線程間的干擾出現在多個線程對同一個數據進行多個操作的時候,也就是出現了「交錯」。山純局這就意味著操作是由多個步驟構成的,而此時,褲仿在這多個步驟的執行上出現了疊加。
Counter類對象的操作貌似不可能出現這種「交錯」,因為其中的兩個關於c的操作都很簡單,只有一條語句。然而,即使是一條語句也是會被VM翻譯成多個步驟的。在這里,我們不深究VM具體上上面的操作翻譯成了什麼樣的步驟。只需要知道即使簡單的c++這樣的表達式也是會被翻譯成三個步驟的:
1. 獲取c的當前值。
2. 對其當前值加1。
3. 將增加後的值存儲到c中。
表達式c--也是會被按照同樣的方式進行翻譯,只不過第二步變成了減1,而不是加1。
假定線程A中調用increment方法,線逗讓程B中調用decrement方法,而調用時間基本上相同。如果c的初始值為0,那麼這兩個操作的「交錯」順序可能如下:
1. 線程A:獲取c的值。
2. 線程B:獲取c的值。
3. 線程A:對獲取到的值加1;其結果是1。
4. 線程B:對獲取到的值減1;其結果是-1。
5. 線程A:將結果存儲到c中;此時c的值是1。
6. 線程B:將結果存儲到c中;此時c的值是-1。
這樣線程A計算的值就丟失了,也就是被線程B的值覆蓋了。上面的這種「交錯」只是一種可能性。在不同的系統環境中,有可能是B線程的結果丟失了,或者是根本就不會出現錯誤。由於這種「交錯」是不可預測的,線程間相互干擾造成的缺陷是很難定位和修改的。
內存一致性錯誤
內存一致性錯誤發生在不同線程對同一數據產生不同的「見解」。導致內存一致性錯誤的原因很負責,超出了本文的描述范圍。慶幸的是,程序員並不需要知道出現這些原因的細節。我們需要的只是一種可以避免這種錯誤的方法。
避免出現內存一致性錯誤的關鍵在於理解「先後順序」關系。這種關系是一種簡單的方法,能夠確保一條語句對內存的寫操作對於其它特定的語句都是可見的。為了理解這點,我們可以考慮如下的示例。假定定義了一個簡單的int類型的欄位並對其進行了初始化:
int counter = 0;
該欄位由兩個線程共享:A和B。假定線程A對counter進行了自增操作:
counter++;
然後,線程B輸出counter的值:
System.out.println(counter);
如果以上兩條語句是在同一個線程中執行的,那麼輸出的結果自然是1。但是如果這兩條語句是在兩個不同的線程中,那麼輸出的結構有可能是0。這是因為沒有保證線程A對counter的修改對線程B來說是可見的。除非程序員在這兩條語句間建立了一定的「先後順序」。
我們可以採取多種方式建立這種「先後順序」。使用同步就是其中之一,這點我們將會在下面的小節中看到。
到目前為止,我們已經看到了兩種建立這種「先後順序」的方式:
當一條語句中調用了Thread.start()方法,那麼每一條和該語句已經建立了「先後順序」的語句都和新線程中的每一條語句有著這種「先後順序」。引入並創建這個新線程的代碼產生的結果對該新線程來說都是可見的。
當一個線程終止了並導致另外的線程中調用join的語句回,那麼此時這個終止了的線程中執行了的所有語句都和隨後的join語句隨後的所有語句建立了這種「先後關系」。也就是說終止了的線程中的代碼效果對調用join方法的線程來說是可見。
關於哪些操作可以建立這種「先後關系」,更多的信息請參閱「java.util.concurrent包的概要說明」。
同步方法
Java編程語言中提供了兩種基本的同步用語:同步方法和同步語句。同步語句相對而言更為復雜一些,我們將在下一小節中進行描述。本節重點討論同步方法。
我們只需要在聲明方法的時候增加關鍵字synchronized即可:
[java] view plain
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
如果count 是SynchronizedCounter類的實例,設置其方法為同步方法將有一下兩個效果:
首先,不可能出現對同一對象的同步方法的兩個調用的「交錯」。當一個線程在執行一個對象的同步方式的時候,其他所有的調用該對象的同步方法的線程對會被掛起,直到第一個線程對該對象操作完畢。
其次,當一個同步方法退出時,會自動與該對象的後續同步方法間建立「先後順序」的關系。這就確保了對該對象的修改對其他線程是可見的。
注意:構造函數不能為同步的——在構造函數前使用synchronized關鍵字將導致語義錯誤。同步構造函數是沒有意義的。這是因為只有創建該對象的線程才能調用其構造函數。
警告:在創建多個線程共享的對象時,要特別小心對該對象的引用不能過早地「泄露」。例如,假定我們想要維護一個保存類的所有實例的列表instances。我們可能會在構造函數中這樣寫到:
instances.add(this);
但是,其他線程可會在該對象的構造完成之前就訪問該對象。
同步方法是一種簡單的可以避免線程相互干擾和內存一致性錯誤的策略:如果一個對象對多個線程都是可見的,那麼所有對該對象的變數的讀寫都應該是通過同步方法完成的(一個例外就是final欄位,他在對象創建完成後是不能被修改的,因此,在對象創建完畢後,可以通過非同步的方法對其進行安全的讀取)。這種策略是有效的,但是可能導致「liveness」問題。這點我們會在本課程的後面進行描述。
內部鎖及同步
同步是構建在被稱為「內部鎖或者是監視鎖」的內部實體上的。(在API中通常被稱為是「監視器」。)內部鎖在兩個方面都扮演著重要的角色:保證對對象訪問的排他性和建立也對象可見性相關的重要的「先後順序」。
每一個對象都有一個與之相關聯動的內部鎖。按照傳統的做法,當一個線程需要對一個對象的欄位進行排他性訪問並保持訪問的一致性時,他必須在訪問前先獲取該對象的內部鎖,然後才能訪問之,最後釋放該內部鎖。在線程獲取對象的內部鎖到釋放對象的內部鎖的這段時間,我們說該線程擁有該對象的內部鎖。只要有一個線程已經擁有了一個內部鎖,其他線程就不能在擁有該鎖了。其他線程將會在試圖獲取該鎖的時候被阻塞了。
當一個線程釋放了一個內部鎖,那麼就會建立起該動作和後續獲取該鎖之間的「先後順序」。
同步方法中的鎖
當一個線程調用一個同步方法的時候,他就自動地獲得了該方法所屬對象的內部鎖,並在方法返回的時候釋放該鎖。即使是由於出現了沒有被捕獲的異常而導致方法返回,該鎖也會被釋放。
我們可能會感到疑惑:當調用一個靜態的同步方法的時候會怎樣了,靜態方法是和類相關的,而不是和對象相關的? 在這種情況下,線程獲取的是該類的類對象的內部鎖。這樣對於靜態欄位的方法是通過一個和類的實例的鎖相區分的另外的鎖來進行的。
同步語句
另外一種創建同步代碼的方式就是使用同步語句。和同步方法不同,使用同步語句是必須指明是要使用哪個對象的內部鎖:
[java] view plain
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
在上面的示例中,方法addName需要對lastName和nameCount的修改進行同步,還要避免同步調用其他對象的方法(在同步代碼段中調用其他對象的方法可能導致「Liveness」中描述的問題)。如果沒有使用同步語句,那麼將不得不使用一個單獨的,未同步的方法來完成對nameList.add的調用。
在改善並發性時,巧妙地使用同步語句能起到很大的幫助作用。例如,我們假定類MsLunch有兩個實例欄位,c1和c2,這兩個變數絕不會一起使用。所有對這兩個變數的更新都需要進行同步。但是沒有理由阻止對c1的更新和對c2的更新出現交錯——這樣做會創建不必要的阻塞,進而降低並發性。此時,我們沒有使用同步方法或者使用和this相關的鎖,而是創建了兩個單獨的對象來提供鎖。
[java] view plain
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
採用這種方式時需要特別的小心。我們必須絕對確保相關欄位的訪問交錯是完全安全的。
同步的重入
回憶前面提到的:線程不能獲取已經被別的線程獲取的鎖。單絲線程可以獲取自身已經擁有的鎖。允許一個線程能重復獲得同一個鎖就稱為同步重入。它是這樣的一種情況:在同步代碼中直接或者間接地調用了還有同步代碼的方法,兩個同步代碼段中使用的是同一個鎖。如果沒有同步重入,在編寫同步代碼時需要額外的小心,以避免線程將自己阻塞。
原子性
在編程中,原子性動作就是指一次性有效完成的動作。原子性動作是不能在中間停止的:要麼一次性完全執行完畢,要麼就不執行。在動作沒有執行完畢之前,是不會產生可見結果的。
通過前面的示例,我們已經發現了諸如c++這樣的自增表達式並不屬於原子操作。即使是非常見到的表達式也定義了負責的動作,這些動作可以被解釋成許多別的動作。然而,的確存在一些原子操作的:
對幾乎所有的原生數據類型變數的讀寫(除了long和都變了外)以及引用變數的讀寫都是原子的。
對所有聲明為volatile的變數的讀寫都是原子的,包括long和double類型。
原子性動作是不會出現交錯的,因此,使用這些原子性動作時不用考慮線程間的干擾。然而,這並不意味著可以移除對原子操作的同步。因為內存一致性錯誤還是有可能出現的。使用volatile變數可以減少內存一致性錯誤的風險,因為任何對volatile變數的寫操作都和後續對該變數的讀操作建立了「先後順序」。這就意味著對volatile類型變數的修改對於別的線程來說是可見的。更重要的是,這意味著當一個線程讀取一個volatile類型的變數是,他看到的不僅僅是對該變數的最後一次修改,還看到了導致這種修改的代碼帶來的其他影響。
使用簡單的原子變數訪問比通過同步代碼來訪問變數更高效,但是需要程序員的更多細心考慮,以避免內存一致性錯誤。這種額外的付出是否值得完全取決於應用程序的大小和復雜度。
在包java.util.concurrent中的一些類提供了原子方法,這些方法不依賴於同步。我們會在章節:High Level Concurrency Objects中進行討論。
㈨ java 總結幾種線程非同步轉同步的方法
以Java語言為例:
用synchronized關鍵字修飾同步方法。
同步有幾種實現方法分別是synchronized,wait與notify
wait():使一個線程處於等待狀態,並且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處於等待狀態的線程,注意的是在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先順序。
Allnotity():喚醒所有處入等待狀態的線程,注意並不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
同步是多線程中的重要概念。同步的使用可以保證在多線程運行的環境中,程序不會產生設計之外的錯誤結果。同步的實現方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關鍵字。
給一個方法增加synchronized修飾符之後就可以使它成為同步方法,這個方法可以是靜態方法和非靜態方法,但是不能是抽象類的抽象方法,也不能是介面中的介面方法。下面代碼是一個同步方法的示例:
public synchronized void aMethod() {
// do something
}
public static synchronized void anotherMethod() {
// do something
}
線程在執行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執行完它所調用的同步方法並從中退出,從而導致它釋放了該對象的同步鎖之後。在一個對象被某個線程鎖定之後,其他線程是可以訪問這個對象的所有非同步方法的。
同步塊是通過鎖定一個指定的對象,來對同步塊中包含的代碼進行同步;而同步方法是對這個方法塊里的代碼進行同步,而這種情況下鎖定的對象就是同步方法所屬的主體對象自身。如果這個方法是靜態同步方法呢?那麼線程鎖定的就不是這個類的對象了,也不是這個類自身,而是這個類對應的java.lang.Class類型的對象。同步方法和同步塊之間的相互制約只限於同一個對象之間,所以靜態同步方法只受它所屬類的其它靜態同步方法的制約,而跟這個類的實例(對象)沒有關系。
㈩ java中線程同步的幾種方法
線程同步主要有以下種方法(示例中是實現計數的功能):
1、同步方法,即使用synchronized關鍵字修飾方法,例如:
publicsynchronizedvoidadd(intc){...}
2、同步代碼塊,即有synchronized關鍵字修飾的語句塊,例如:
publicvoidaddAndGet(intc){
synchronized(this){
count+=c;
}
}
3、使用特殊域變數(volatile)實現線程同步,該方法不能保證絕對的同步。
例如:privatevolatileintcount=0;
4、使用鎖實現線程同步,例如:
privateLocklock=newReentrantLock();
publicvoidadd(intc){
lock.lock();//上鎖
try{
count+=c;
}finally{
lock.unlock();//解鎖
}
}
5、使用原子變數實現線程同步,在java的util.concurrent.atomic包中提供了創建了原子類型變數的工具類,例如:
privateAtomicIntegercount=newAtomicInteger(1);
publicvoidadd(intc){
count.addAndGet(c);
}
6、使用局部變數實現線程同步,如果使用ThreadLocal管理變數,則每一個使用該變數的線程都獲得該變數的副本, 副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變數副本,而不會對其他線程產生影響。
ThreadLocal 類的常用方法
new ThreadLocal<T>() : 創建一個線程本地變數
get() : 返回此線程局部變數的當前線程副本中的值
initialValue() : 返回此線程局部變數的當前線程的"初始值"
set(T value) : 將此線程局部變數的當前線程副本中的值設置為value
示例代碼:
privatestaticThreadLocal<Integer>count=newThreadLocal<Integer>(){
@Override
protectedIntegerinitialValue(){
return1;
}
};
publicvoidadd(intc){
count.set(count.get()+c);
}
7、使用阻塞隊列實現,例如LinkedBlockingQueue,具體使用可網路LinkedBlockingQueue的用法或查看java文檔。