Java同步原语synchronized、volatile

synchronized

通常的多线程,执行顺序随机: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

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 关系能够分为两类:

  1. 根据程序次序规则,1 happens before 2; 3 happens before 4。
  2. 根据 volatile 规则,2 happens before 3。
  3. 根据 happens before 的传递性规则,1 happens before 4。 上述 happens before 关系的图形化表现形式以下: 在上图中,每个箭头连接的两个节点,表明了一个 happens before 关系。黑色 箭头表示程序顺序规则;橙色箭头表示 volatile 规则;蓝色箭头表示组合这些规则 后提供的 happens before 保证。 这里 A 线程写一个 volatile 变量后,B 线程读同一个 volatile 变量。A 线程在写 volatile 变量以前全部可见的共享变量,在 B 线程读同一个 volatile 变量后,将立 即变得对 B 线程可见。 volatile读写时就是几个线程经过主内存进行通讯

volatile 写-读的内存语义 volatile 写的内存语义以下: .当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷 新到主内存。 以上面示例程序 VolatileExample 为例,假设线程 A 首先执行 writer()方法,随后 线程 B 执行 reader()方法,初始时两个线程的本地内存中的 flag 和 a 都是初始状 态。

线程 A 在写 flag 变量后,本地内存 A 中被线程 A 更新过的两个共享变量的值被刷新到主内存中。此时,本地内存 A 和主内存中的共享变量的值是一致的。

volatile 读的内存语义以下:

当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

在读 flag 变量后,本地内存 B 包含的值已经被置为无效。此时,线程 B 必须从主内存中读取共享变量。线程 B 的读取操做将致使本地内存 B 与主内存中的共享变量的值也变成一致的了。若是咱们把 volatile 写和 volatile 读这两个步骤综合起来看的话,在读线程 B读一个volatile变量后,写线程A在写这个volatile变量以前全部可见的共享变量的值 都将当即变得对读线程 B 可见。下面对 volatile 写和 volatile 读的内存语义作个总结:

线程 A 写一个 volatile 变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所在修改的)消息。

线程 B 读一个 volatile 变量,实质上是线程B接收了以前某个线程发出的(在写这个volatile变量以前对共享变量所作修改的)消息。

线程 A 写一个 volatile 变量,随后线程 B 读这个 volatile 变量,这个过程实质上是线程 A 经过主内存向线程 B发送消息。

相关文章
相关标签/搜索