通常的多線程,執行順序隨機:bash
public class ThreadTest {
public static void main(String[] args)
{
for(int i=0;i<3;i++)
{
MyThread temp=new MyThread();
temp.start();
}
}
}
class MyThread extends Thread
{
@Override
public void run()
{
for(int i=0;i<5;i++)
{
System.out.println(Thread.currentThread().getName()+": ["+i+"]");
}
}
}
複製代碼
執行效果:多線程
使用synchronized關鍵字修飾的多線程會順序執行:app
package 同步;
public class SynchronizedTest {
static int number=0;
//synchronized塊(對象級)
public String setNumber1()
{
synchronized(this)
{
number++;
return "setNumber1.:"+number;
}
}
//synchronized塊(類級別)
public String setNumber2()
{
synchronized(SynchronizedTest.class)
{
number++;
return "setNumber2.:"+number;
}
}
//synchronized 方法
public synchronized String setNumber3()
{
number++;
return "setnumber3.:"+number;
}
public String setNumber4() {
return"setnumber4.:"+number;
}
}
package 同步;
public class Test{
public static void main(String[] args)
{
SynchronizedTest t = new SynchronizedTest();
for(int i=0;i<3;i++)
{
MyThread2 temp=new MyThread2(t);
temp.start();
}
}
}
class MyThread2 extends Thread{
SynchronizedTest Object;
public MyThread2(SynchronizedTest Object) {
this.Object = Object;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(0);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--"+Object.setNumber1());
System.out.println(Thread.currentThread().getName()+"--"+Object.setNumber3());
}
}
複製代碼
執行效果:ide
因此使用鎖進行多線程的同步就能控制執行的順序。ui
volatile的讀寫操做與鎖的釋放和獲取有着一樣的效果this
public class VolatileTest {
volatile int number = 0;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getandIncrement() {
number++;
return number;
}
public static void main(String args[]) {
VolatileTest test = new VolatileTest();
test.setNumber(5);
System.out.println( test.getNumber());
test.getandIncrement();
System.out.println(test.getandIncrement());
}
}
複製代碼
運行結果:spa
package 同步;
public class VolatileExample {
int number = 0;
public synchronized void setNumber(int number) {
this.number = number;
}
public int getandIncrement() {
int t =getNumber();
t+= 1;
setNumber(t);
return t;
}
public synchronized int getNumber() {
return number;
}
public static void main(String args[]) {
VolatileExample test = new VolatileExample();
test.setNumber(5);
System.out.println( test.getNumber());
test.getandIncrement();
System.out.println(test.getandIncrement());
}
}
複製代碼
運行結果:線程
從上面的例子中能夠看出對一個 volatile變量的單個讀/寫操做,與對一個普通變量的讀/寫操做使用同一個鎖來同步,它們之間的執行效果相同。鎖的happens-before規則保證釋放鎖和獲取鎖的兩個線程之間的內存可見性,這意味着對一個volatile變量的讀,老是能看到(任意線程)對這個 volatile變量最後的寫入。鎖的語義決定了臨界區代碼的執行具備原子性。這意味着即便是 64 位的 long 型和double 型變量,只要它是volatile變量,對該變量的讀寫就將具備原子性。若是是多個volatile操做或相似於 volatile++這種複合操做,這些操做總體上不具備原子性。簡而言之,volatile 變量自身具備下列特性:code
可見性。對一個 volatile 變量的讀,老是能看到(任意線程)對這個 volatile 變量最後的寫入。cdn
原子性:對任意單個 volatile 變量的讀/寫具備原子性,但相似於 volatile++這 種複合操做不具備原子性
volatile 寫-讀創建的 happens before 關係
從內存語義的角度來講,volatile 的寫-讀與鎖的釋放-獲取有相同的內存效果: volatile 寫和鎖的釋放有相同的內存語義;volatile 讀與鎖的獲取有相同的內存語 義。 請看下面使用 volatile 變量的示例代碼:
class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; //1
flag = true; //2
}
public void reader() {
if (flag) { //3
int i = a; //4
}
}
}
複製代碼
假設線程 A 執行 writer()方法以後,線程 B 執行 reader()方法。根據 happens before 規則,這個過程創建的 happens before 關係能夠分爲兩類:
volatile 寫-讀的內存語義 volatile 寫的內存語義以下: .當寫一個 volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量值刷 新到主內存。 以上面示例程序 VolatileExample 爲例,假設線程 A 首先執行 writer()方法,隨後 線程 B 執行 reader()方法,初始時兩個線程的本地內存中的 flag 和 a 都是初始狀 態。
volatile 讀的內存語義以下:
當讀一個 volatile 變量時,JMM 會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。
線程 A 寫一個 volatile 變量,實質上是線程A向接下來將要讀這個volatile變量的某個線程發出了(其對共享變量所在修改的)消息。
線程 B 讀一個 volatile 變量,實質上是線程B接收了以前某個線程發出的(在寫這個volatile變量以前對共享變量所作修改的)消息。
線程 A 寫一個 volatile 變量,隨後線程 B 讀這個 volatile 變量,這個過程實質上是線程 A 經過主內存向線程 B發送消息。