理解 Java synchronized

来源:互联网 发布:js 相对路径 绝对路径 编辑:程序博客网 时间:2024/06/04 19:55

多线程开发离不开锁机制,现在的Java语言中,提供了2种锁,一种是语言特性提供的内置锁,还有一种是 java.util.concurrent.locks 包中的锁。对于synchronized的一些基本概念这里就不重复了,很多博客都写到了。这篇文章针对一个例子,讲一下我的理解。

这是一个调用同步方法的同时,调用该对象非同步方法(但是内部含有同步块)的例子

package tmp;/** * Created by Gracecoder on 2017/11/24. */public class Service {    private String anyString = new String();    public void a(){        try {            synchronized (anyString)            {                System.out.println("a begin");                Thread.sleep(3000);                System.out.println("a end");            }        }catch (Exception e)        {            e.printStackTrace();        }    }    synchronized public void b()    {        System.out.println("b begin");//        synchronized (anyString) {//           anyString = new String("hello");//            System.out.println(anyString);//        }        System.out.println("b end");    }}
package tmp;/** * Created by Gracecoder on 2017/11/24. */public class ThreadA extends Thread {    private Service service;    public ThreadA(Service service) {        super();        this.service = service;    }    @Override    public void run() {        service.a();    }}
package tmp;/** * Created by Gracecoder on 2017/11/24. */public class ThreadB extends Thread{    private Service service;    public ThreadB(Service service) {        super();        this.service = service;    }    @Override    public void run() {        service.b();    }}
package tmp;/** * Created by Gracecoder on 2017/11/24. */public class Run {    public static void main(String[] args){        Service service = new Service();        ThreadA a = new ThreadA(service);        ThreadB b = new ThreadB(service);        a.setName("A");        a.start();        b.setName("B");        b.start();    }}
运行结果:a beginb beginb enda end

运行这段代码首先线程A先获取成员变量anyString的内置锁,然后线程B访问同步方法,获取当前对象service的锁,这里anyString是对象service的成员变量,结果显示它们异步调用了。

按照我一开始的想法,如果反一下,由线程B先访问同步方法,获取当前对象service的锁之后,其他线程是否能获取其对象成员的锁? 我觉得 不能!但是通过运行代码验证,发现就算B先获得service的锁,A依旧能获得其成员anyString的锁,它们仍然是异步调用!

就很疑惑,我们获取一个对象的锁,但是不代表我们拥有其成员域对象的锁!

小结论:

每个对象都可以用作一个实现同步的锁,这些锁被称为内置锁!
对象的内置锁和对象的状态之间没有内在的关联,虽然大多数类都讲内置锁用作一种有效的加锁机制,但对象的域并不一定通过内置锁来保护。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,只能阻止其他线程获取同一个锁!
那显然每个对象都有自己的内置锁,尽管anyString是service的成员域,但它们两各自的内置锁也是不同的。所以就算反过来,B先获取this(synchronized方法相当于同步块synchronized(this))对象的锁,A还是可以获取anyString的锁。除非我们在B的同步方法中,再次请求获取anyString对象的锁,那就会同步,阻塞到A完成了。


这里还有一个类锁的概念,在synchronized同步方法前面加个static,那么访问该方法,就要获取当前类的锁。

//同步的类方法 synchronized public static void printA() {    //todo }

注意:同样的,类锁和对象锁也不存在关联,它们是不同的锁。类锁并不包括该类具体对象的锁,如同对象锁并不包括其对象域的锁。在具体代码中,如果线程A先调用同步类方法(获取了类锁),线程B这时候调用同步方法,调用同步方法的前提是获得当前对象锁,而此时当前对象锁空闲,所以可以异步执行。如果还有一个线程C要调用另一个同步类方法或者同步方法,则必须等A或B执行完毕后,才能获得锁,同步执行!

而类锁对类的所有对象实例起作用的本质是:
实例对象如果要调用synchronized类方法,那么就要获取类锁,此时不同对象获取的类锁都是同一个,那么就会产生同步!

总结:

synchronized 同步的本质就是:是否使用同一个对象监视器
任何对象都有一个自己的内置锁,同步或者异步,主要就是看多线程竞争的是否是同一把锁。区分这一点才是关键,当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,只能阻止其他线程获取同一个锁!

synchronized(非this对象x)格式的写法是将x对象本身作为“对象监视器”,这样就可以得出以下3个结论:

  1. 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果(竞争this当前对象的锁)
  2. 当其他线程执行x对象中synchronized同步方法时呈同步效果(竞争x对象)
  3. 当其他线程执行x对象方法里面的synchronized(this)代码块时也是同步

需要注意的是:如果其他线程调用不加synchronized关键字的方法时,还是异步调用,因为其他线程没有去竞争锁。

线程安全包括可见性和原子性,synchronized实现了原子性,同时间接现实可见性。而volatile只有可见性,没有原子性。

原创粉丝点击