java关键字volatile介绍、和synchronized的简单比较

Java架构师交流群:793825326

java版本:jdk1.8

IDE:idea 18

详细信息可参考博客:https://www.cnblogs.com/dolphin0520/p/3920373.html

这里面只简单介绍下,看下面的代码(java版本:1.8):

public class Test {
    private boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    public void start() {
        System.out.println("start");
        while (!flag) ;
        System.out.println("end");
    }
}

在main函数里面添加如下代码:

public class Main {

        public static void main(String[] args) {
            Test t=new Test();
            try {
                new Thread(() ->  t.start()).start();
                Thread.sleep(100);
                new Thread(() ->  t.setFlag()).start();
            }
            catch (Exception ex)
            {

            }
        }
}

这段代码会进入死循环,但如果用volatile修饰,就不会进入死循环了,由此可以看出volatile的作用,当变量被修改后,通知所有的线程修改变量缓存(java8里面有个主内存,同时每个线程还有个自己的缓存。当线程用到主内存里面的变量时,会主动将变量拷贝到缓存里面。)。

另外这里面还有一点需要注意下,看下面的代码:

public class Test {
    private boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    public void start() {
        System.out.println("start");
        while (!flag)
        {
            System.out.println("-ing");
        }
        System.out.println("end");
    }
}

也就是在while循环里面加上

System.out.println("-ing");

这样线程就不会再进入死循环了。这是为什么呢,看下println的源码:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

注意到代码里面加了一把锁,也就是说可能是因为这把锁导致了数据的更新,我们不妨尝试一下,修改代码如下:

public class Test {
    private boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    public void start() {
        System.out.println("start");
        while (!flag)
        {
            int i=0;
            i++;
        }
        System.out.println("end");
    }
}

这段代码仍然会进入死循环,那么我们在这里加把锁,代码如下:

public class Test {
    private boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    private Object lock=new Object();

    public void start() {
        System.out.println("start");
        while (!flag)
        {
            synchronized(lock)
            {
                int i=0;
                i++;
            }

        }
        System.out.println("end");
    }
}

这个时候线程再次不会进入死循环了。

那么如果我们把锁的级别再提高一点,放到循环外面,会怎样:

public class Test {
    private boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    private Object lock = new Object();

    public void start() {
        System.out.println("start");
        synchronized (lock) {
            while (!flag) {

                int i = 0;
                i++;
            }

        }
        System.out.println("end");
    }
}

答案是这段代码仍然会进入死循环。

这说明了synchronized的作用,也证实了我们的猜测,每次线程尝试获取锁的时候,会清空工作内存,同步主内存的数据进来,这个时候flag自然就会被同步过来了。

获取锁的过程大概如下:

1.获取锁

2.清空工作内存,copy主内存。

3.执行代码

4.更新主内存

5.释放锁

synchronized的原子性也就因此保证了。同时也说明volatile只保证内存可见性,并不保证原子性,比如它能够保证及时拿到最新的数据,但是不保证该数据被更新成功。而synchronized虽然保证内存可见性,但是这种内存可见性不像volatile那样是实时的,也就是说只有在尝试获取的锁的时候,清空一次缓存,后面如果涉及到的变量变更了,加锁的线程仍然是不知道的,而volatile是会收到通知的,这是它们两个之间的内存可见性的区别,它们两个的另一个区别是锁还保证原子性。

另外还有一种情况,也会导致循环退出,就是在while循环体内调用native修饰的方法,比如下面的三个方法随意调用一个,都会导致循环退出

new Object().hashCode();
Thread.sleep(10);
Thread.yield();

另外,虽然

TimeUnit.MICROSECONDS.sleep(100);

没有被native修饰,但是由于其内部实现最终调用的还是sleep方法,所以不管调用的方法有没有native修饰,只要内部有代码是native的,都会让循环正常退出。

这是什么原因呢。我们知道被native修饰的方法,其实就是jvm帮我们调用的非java实现的操作系统的方法。有就是虚拟机调用操作系统的方法。回到我们的问题中来,首先循环能够正常退出,就说明该线程的缓存更新了,那么为何调用native方法就要更新缓存呢,这个我猜测是为了保证native方法执行结果的准确性,所以在调用native方法的时候,重新更新了线程的缓存,具体是因为什么,我现在还没搞清楚。等我搞清楚了,再补充原因。

另外如果你很细心,你会发现,java的atomic包里面的方法,最终调用的都是native方法,由于native保证了可见性,所以atomic也保证了可见性,然而事实上atomic的方法并单单是因为这个,它还维护一个volatile的value变量。

所以native方法的机制,大概就是在调用前更新下缓存,至于是不是在中途也会随时更新缓存,我暂时还没找到方法验证,后面找到会补充。

你可能感兴趣的:(java关键字volatile介绍、和synchronized的简单比较)