首先文法定義上的區別:靜態變數前要加static關鍵字,而執行個體變數前則不加。
在程式運行時的區別:執行個體變數屬於某個對象的屬性,必須建立了執行個體對象,其中的執行個體變數才會被分配空間,才能使用這個執行個體變數。靜態變數不屬於某個執行個體對象,而是屬於類,所以也稱為類變數,只要程式載入了類的位元組碼,不用建立任何執行個體對象,靜態變數就會被分配空間,靜態變數就可以被使用了。總之,執行個體變數必須建立對象後才可以通過這個對象來使用
代碼如下 |
複製代碼 |
package staticVar; public class svar { public int var = 1000; public static void main(String[] args){ svar svar = new svar(); System.out.println("var =" + svar.var); } }
|
靜態變數則可以直接使用類名來引用
代碼如下 |
複製代碼 |
package staticVar; public class svar { public static int var = 1000; public static void main(String[] args){ System.out.println("var =" + svar.var); } }
|
類的靜態變數在記憶體中只有一個,java虛擬機器在載入類的過程中為靜態變數分配記憶體,靜態變數位於方法區,被類的所有執行個體共用。靜態變數可以直接通過類名進行訪問,其生命週期取決於類的生命週期。
而執行個體變數取決於類的執行個體。每建立一個執行個體,java虛擬機器就會為執行個體變數分配一次記憶體,執行個體變數位於堆區中,其生命週期取決於執行個體的生命週期。
靜態變數生命週期較長,而且不易被系統回收,因此如果不能合理地使用靜態變數,就會適得其反,造成大量的記憶體浪費,所謂過猶不及。因此,建議在具備下列全部條件的情況下,盡量使用靜態變數:
(1)變數所包含的對象體積較大,佔用記憶體較多。
(2)變數所包含的對象生命週期較長。
(3)變數所包含的對象資料穩定。
(4)該類的對象執行個體有對該變數所包含的對象的共用需求。
你可以將方法和變數都聲明為static。static 成員的最常見的 例子是main( ) 。因為在程式開始執行時必須調用main() ,所以它被聲明為static。 聲明為static的變數實質上就是全域變數。聲明為static的方法有以下幾條限制: ·
A,它們僅能調用其他的static 方法
B,它們只能訪問static資料
C,它們不能以任何方式引用this 或super(this涉及到對象,super 與繼承有關)
樣本:如果你需要通過計算來初始化你的static變數,你可以聲明一個static塊。Static 塊僅在該類被載入時執行一次。下面的例子顯示的類有一個static方法,一些static變
量,以及一個static 初始化塊。
代碼如下 |
複製代碼 |
public class TestNew { static int a = 3; static int b; static void meth(int x){ System.out.println("x = "+x); System.out.println("a = "+a); System.out.println("b = "+b); } static { System.out.println("static block initialized"); b = a*4; } public static void main(String[] args) { // TODO Auto-generated method stub meth(42); } }
執行結果是: static block initialized x = 42 a = 3 b = 12 |
上述class TestNew的執行順序是:首先,a被設定為3,接著static 塊執行(列印一條訊息),最後b被初始化為a*4 成12。然後調用main(),main () 調用meth() ,把值42傳遞給x。3個println ( ) 語句引用兩個static變數a和b,以及局部變數x 。
外部使用靜態變數或者靜態方法
在定義它們的類的外面,static 方法和變數能獨立於任何對象而被使用,你只要在類的名字後面加點號運算子即可。可以看到,這種格式與通過對象引用變數調用非static方法或者變數的格 式類似。這就是Java 如何?全域功能和全域變數的一個控製版本。樣本:
代碼如下 |
複製代碼 |
class StaticDemo{ static int a = 42; static int b = 99; static void callme(){ System.out.println("a = "+a); } } public class TestNew { public static void main(String[] args) { // TODO Auto-generated method stub StaticDemo.callme(); System.out.println("b = "+StaticDemo.b); } } 執行結果: a = 42 b = 99 |
靜態變數和執行個體變數到底有什麼區別
/**
*
*/
package com.b510.test;
代碼如下 |
複製代碼 |
/** * 在程式運行時的區別:執行個體變數屬於某個對象的屬性,必須建立了執行個體對象,<br> * 其中的執行個體變數才會被分配空間,才能使用這個執行個體變數。靜態變數不屬於某<br> * 個執行個體對象,而是屬於類,所以也稱為類變數,只要程式載入了類的位元組碼,<br> * 不用建立任何執行個體對象,靜態變數就會被分配空間,靜態變數就可以被使用了。<br> * 總之,執行個體變數必須建立對象後才可以通過這個對象來使用,靜態變數則可以<br> * 直接使用類名來引用。例如,對於下面的程式,無論建立多少個執行個體對象,<br> * 永遠都只分配了一個<code>staticInt</code>變數,並且每建立一個執行個體對象,<br> * 這個<code>staticInt</code>就會加1;但是,每建立一個執行個體對象,就會分配一個<code>random</code>,<br> * 即可能分配多個<code>random</code>,並且每個<code>random</code>的值都只自加了1次。<br> * * @author <a href="mailto:hongtenzone@foxmail.com">hongten</a> * @date 2013-3-2 */ public class StaticTest { private static int staticInt = 2; private int random = 2; public StaticTest() { staticInt++; random++; System.out.println("staticInt = "+staticInt+" random = "+random); } public static void main(String[] args) { StaticTest test = new StaticTest(); StaticTest test2 = new StaticTest(); } } |
安全執行緒問題之靜態變數、執行個體變數
靜態變數:線程非安全。
靜態變數即類變數,位於方法區,為所有對象共用,共用一份記憶體,一旦靜態變數被修改,其他對象均對修改可見,故線程非安全。
執行個體變數:單例模式(只有一個對象執行個體存在)線程非安全,非單例安全執行緒。
執行個體變數為對象執行個體私人,在虛擬機器的堆中分配,若在系統中只存在一個此對象的執行個體,在多線程環境下,“猶如”靜態變數那樣,被某個線程修改後,其他線程對修改均可見,故線程非安全;如果每個線程執行都是在不同的對象中,那對象與對象之間的執行個體變數的修改將互不影響,故安全執行緒。
代碼如下 |
複製代碼 |
/** * 安全執行緒問題類比執行 * ------------------------------ * 線程1 | 線程2 * ------------------------------ * static_i = 4; | 等待 * static_i = 10; | 等待 * 等待 | static_i = 4; * static_i * 2; | 等待 * ----------------------------- * */ public class Test implements Runnable { private static int static_i;//靜態變數 public void run() { static_i = 4; System.out.println("[" + Thread.currentThread().getName() + "]擷取static_i 的值:" + static_i); static_i = 10; System.out.println("[" + Thread.currentThread().getName() + "]擷取static_i*3的值:" + static_i * 2); } public static void main(String[] args) { Test t = new Test(); //啟動盡量多的線程才能很容易的類比問題 for (int i = 0; i < 3000; i++) { //t可以換成new Test(),保證每個線程都在不同的對象中執行,結果一樣 new Thread(t, "線程" + i).start(); } } }
|
根據代碼注釋中類比的情況,當線程1執行了static_i = 4; static_i = 10; 後,線程2獲得執行權,static_i = 4; 然後當線程1獲得執行權執行static_i * 2; 必然輸出結果4*2=8,按照這個類比,我們可能會在控制台看到輸出為8的結果。
寫道
[線程27]擷取static_i 的值:4
[線程22]擷取static_i*2的值:20
[線程28]擷取static_i 的值:4
[線程23]擷取static_i*2的值:8
[線程29]擷取static_i 的值:4
[線程30]擷取static_i 的值:4
[線程31]擷取static_i 的值:4
[線程24]擷取static_i*2的值:20
看紅色標註的部分,確實出現了我們的預想,同樣也證明了我們的結論。
執行個體變數安全執行緒問題類比:
----------------------------------------------------------------------------------
代碼如下 |
複製代碼 |
Java代碼 public class Test implements Runnable { private int instance_i;//執行個體變數 public void run() { instance_i = 4; System.out.println("[" + Thread.currentThread().getName() + "]擷取instance_i 的值:" + instance_i); instance_i = 10; System.out.println("[" + Thread.currentThread().getName() + "]擷取instance_i*3的值:" + instance_i * 2); } public static void main(String[] args) { Test t = new Test(); //啟動盡量多的線程才能很容易的類比問題 for (int i = 0; i < 3000; i++) { //每個線程對在對象t中運行,類比單例情況 new Thread(t, "線程" + i).start(); } } } |
按照本文開頭的分析,猶如靜態變數那樣,每個線程都在修改同一個對象的執行個體變數,肯定會出現安全執行緒問題。
寫道
[線程66]擷取instance_i 的值:10
[線程33]擷取instance_i*2的值:20
[線程67]擷取instance_i 的值:4
[線程34]擷取instance_i*2的值:8
[線程35]擷取instance_i*2的值:20
[線程68]擷取instance_i 的值:4
看紅色字型,可知單例情況下,執行個體變數線程非安全。
將new Thread(t, "線程" + i).start();改成new Thread(new Test(), "線程" + i).start();類比非單例情況,會發現不存線上程安全問題