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