一、前言
并发编程中的三大特性:
- 原子性:一个或者多个操作,要么全部执行,要不全都不执行;而且执行中途不能被中断,如果被中断,则要进行回滚。
- 可见性:多个线程共同访问共享变量时,如果此时有某个线程修改了此变量,其他线程中的该变量的缓存会立刻失效,并且重新从内存中缓存修改后的值。
- 有序性:程序执行代码的顺序,共享内存模型(JMM)允许编译器和处理器提高执行效率,对代码执行的顺序进行调整和优化,在单线程的情况下,指令重排不会影响执行的结果,但是多线程的情况下,可能会出现不一样的结果。
synchronize 关键字同时保证了原子性和可见性两种特性,但是值得注意的是synchronize无法保证有序性,但是synchoronize修饰的方法或代码块同一时间只能由一个线程执行,所以相当于单线程的环境,指令重排对结果不影响。
volatitle 关键字同时保证了可见性和有序性两种特性。
二、synchronize
synchronize 关键字用于解决多线程中访问共享资源的同步性问题,synchronize关键字可以保证访问被该关键字修饰的方法或者代码块同一时间只能被一个对象访问。
通常,synchronize 关键字有三种用法:(讨论的情况是多个线程调用同一个对象)
- 修饰代码块:其作用的范围是 {} 内的代码,被锁的对象是执行该代码块的对象。
- 修饰方法:其作用的范围是整个方法,被锁的对象是调用该方法的对象。
- 修饰静态方法:其作用范围是整个静态方法,被锁的对象是调用该静态方法的对象。
三、synchronize 原理
实现原理是JVM通过对象监视器(minitor)实现对代码块或者方法的同步。
在同步块的入口和出口分别有monitor.enter和monitor.exit指令:
- monitor.enter:获取对象的 monitor 对象(monitor对象存在于每个Java对象的对象头中)。
- monitor.exit:释放对象的 monitor 对象。
当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1;同理,相应的在执行monitor.exit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
如果某个线程(对象)调用monitor.enter获取到了被 synchronize 修饰的方法(代码块)对应实例对象的monitor对象锁,则该线程(对象)可以继续执行同步方法(代码块),其他线程(对象)想要再获取时会被阻塞(synchronize是不公平锁);当拥有monitor对象锁的线程(对象)执行同步方法(代码块)结束后释放了monitor对象锁后,其他被阻塞的线程(对象)可以竞争该monitor对象锁。
从synchronized的特点中可以看到它是一种重量级锁,会涉及到操作系统状态的切换影响效率。