volatile关键字
volatile是一个类型修饰符(type specifier),就像大家更熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量。volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
特点:
- 其他线程对变量的修改可以立即反应到当前线程中
- 确保当前线程对volatile变量的修改,能即时的写到共享内存中,并被其他线程所见
- 使用volatile修饰的变量,编译器会保证其有序性
正确使用volatile变量的条件:
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量的表达式中
第一个条件显示volatile变量不适合用作线程安全计数器,因为在多线程中,增量操作(count++)虽然看上去是一个单独的操作,但实际上是由读取-->修改-->写入操作组成的组合操作,volatile并不能保证整个过程都是原子操作。
适用场景:
状态标志
这种类型的状态标记的一个公共特性是:通常只有一种标记状态的来回转换,而且状态变量通常是原子变量
- 一次性安全发布
缺乏同步会导致无法实现可见性,这使得确定何时写入对象引用而不是原语值变得更加困难,此时我们可以利用volatile实现对象的安全发布。这种模式的一个必要条件是:被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布后不会被修改)。volatile类型的引用可以确保对象的发布形式的可见性,但是如果对象的状态在发布后发生改变,那么需要额外的同步
- 独立观察
安全使用volatile的另一种简单模式是:定期“发布”观察结果供程序内部使用。使用该模式的另一种应用程序是收集程序的统计信息,例如记录一个程序中最后登录的用户,可以将反复使用lastUser引用来发布值,以便程序的其他部分使用。这个模式要求被发布的值是有效不可变的--即值的状态在发布后不会改变。
- “volatile bean”模式
volatile bean 模式适用于将 JavaBeans 作为“荣誉结构”使用的框架。在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession
)提供了容器,但是放入这些容器中的对象必须是线程安全的。
在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 volatile
时,只有引用而不是数组本身具有 volatile 语义)。对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性。
- 开销较低的读-写锁策略
当读操作远远超过写操作时,我们可以结合使用内部锁和volatile变量来减少公共代码路径的开销。如果更新不频繁的话,该方法可以实现更好的性能,因为读路径的开销仅仅涉及到volatile的读操作,这通常要由于一个无竞争的锁的开销。
public class ChessyCounter{
private volatile int count;
public int getValue(){return this.count;}
public synchronized int increment(){
return count++;
}
}
之所以将这种技术称之为“开销较低的读-写锁”,是因为使用的不同的同步机制进行读写操作。由于在读操作中使用volatile可以确保当前值的可见性,所以可以使用锁进行所有变化的操作,使用volatile进行只读操作。
总结:
与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件 —— 即变量真正独立于其他变量和自己以前的值 —— 在某些情况下可以使用 volatile
代替 synchronized
来简化代码。然而,使用 volatile
的代码往往比使用锁的代码更加容易出错。本文介绍的模式涵盖了可以使用 volatile
代替 synchronized
的最常见的一些用例。遵循这些模式(注意使用时不要超过各自的限制)可以帮助您安全地实现大多数用例,使用 volatile 变量获得更佳性能。
synchronized关键字
synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。
Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。
特点:
- 被synchronized修饰的方法或代码块同一时间只能被一个线程使用
- 当一个线程访问Object的一个synchronized代码块时,另一个线程仍可访问该对象的非synchronized代码块
- 当一个线程访问Object的一个synchronized代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
- 当一个线程访问Object的一个synchronized(this)同步代码块时,它就获得了这个Object的对象锁。结果,其他线程对该Object对象所有的同步代码部分的访问都暂时被阻塞
使用场景:
- synchronized方法:
通过在方法声明中加入synchronized关键字,synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)
- synchronized代码块:
通过 synchronized关键字来声明synchronized 块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
- synchronized方法和synchronized同步代码块的区别:
- synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的。
- synchronized方法是粗粒度的并发控制,某一个时刻只能有一个线程执行该synchronized方法。
- synchronized同步代码块是细粒度的并发控制,只会将块中的代码同步,代码块之外的代码可以被其他线程同时访问。
final关键字
特点:
- final类不能被继承,没有子类,final类中的方法默认是final的。
- final方法不能被子类的方法覆盖,但可以被继承。
- final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
- final不能用于修饰构造方法。
使用场景:
- 修饰变量
当final修饰一个变量的时候一般把他作为常量,通常和static关键字配合使用。一般static修饰的常量都用大写字母来表示。
- 修饰方法
当一个方法被final修饰后,表示该方法不能被子类重写,final方法有一个好处是比非final方法要快,因为在编译时已经静态绑定了,不需要在运行时在动态绑定。
- 修饰类
当一个类被final修饰后,表示该类是完整的,不能被继承,例如Java中String、Integer类都是final类
优点:
- final关键字提高了性能。JVM和Java应用都会缓存final变量
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销
- 使用final关键字,JVM会对方法、变量及类进行优化
- 不可变类创建不可变类要使用final关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等
参考资料: