1. 当存在多个线程操作共享数据时,必须要保证在同一时刻有且只有一个线程在操作共享数据,其它线程必须等到该线程处理完后再进行操作。在java中,用关键字synchronized可以做到这一点。
2. Synchronized有三种应用方式
a) 修饰代码块
Synchronized(this){
doSomething();
}
b) 修饰实例方法
Public synchronized void doSomething(){
}
c) 修饰静态方法
Public static synchronized void doSomething(){
}
其中修饰实例方法和修饰静态方法,他们两者之间不互斥,因为一个是获取实例的锁,一个是获取类对象的锁。
3. Synchronized属于重量级锁,效率很低下,因为synchronized指针指向的是monitor(监视器锁)对象,而monitor是依赖于底层的操作系统Mutex Lock来实现的,而操作系统实现线程之间的切换需要从用户态转换到核心态,这个状态的转换需要比较长的时间,时间成本比较高。而在java6以后从jvm层面对synchronized做了较大的优化。
4. Java6以后的锁的状态总共分为五种:无锁状态,偏向锁,轻量级锁,自旋锁和重量级锁。其中重量级的锁就是synchronized
a) 偏向锁
适用于同一个线程获得锁。如果一个线程获得了锁,那锁就进入偏向模式,当这个线程再次请求锁时,不需要再做锁申请的动作,可以直接获取锁,从而提升性能。
但是如果锁竞争比较激烈,偏向锁就失效了,因为每次申请锁的线程都是不相同的。
当偏向锁失败后,先升级为轻量级锁。
b) 轻量级锁
适用于在整个同步时间内不存在竞争,线程交替执行同步块的场合。
c) 自旋锁
轻量级锁失败后,会进行自旋锁的优化手段。当一个线程获得锁,虚拟机让其它想要获取锁的线程做几个空循环,一般不会太久,可能是50个或100个循环,如果循环后能够得到锁,则继续执行,如果得不到,就只能升级为重量锁了。
d) 锁消除
这是另个一种锁的优化,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。
举例如下:
public void add(String str){
StringBuffer sb=new StringBuffer("");
sb.append("aaa");
}
StringBuffer是线程安全的,它的append方法是同步的。
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
按正常来说,这儿会有线程竞争的,但是StringBuffer这个变量是在方法体中的,方法体中的变量都是线程安全的(关于这个,可以参考我的下一篇关于线程的文章—线程安全与不安全的情况),因此jvm会自动将StringBuffer的锁消除,不进行线程的同步竞争。
5. synchronized的重入特性
当一个线程通过synchronized获取锁的时候,在持有锁期间,再次获取这个锁,是可以直接获取的,这儿不会产生锁的竞争。
public class Test {
public void add(){
synchronized(this){
System.out.println("获取锁");
synchronized(this){
System.out.println("再次获取锁");
}
}
}
public static void main(String[] args){
final Test t=new Test();
new Thread(){
public void run(){
t.add();
}
}.start();
}
}