在java编程中,当可能存在多个线程同时访问一部分资源时,可能会出现一些访问冲突。
为了解决冲突,java提供了synchronized关键值,通过加锁的方式,保证访问的有序性。

synchronized关键字的两种作用范围

1.作用于对象.

1
2
3
4
5
6
7
8
9
10
public synchronized void method(){
//
}

等价写法
public void method(){
synchronized(this){
...
}
}

作用于对象可以防止多个线程同时访问该对象的sychronized方法。所以不同实例时不干扰的,也就是说,以该类创建的其它对象依然可以同时访问该sychronized方法。

2.作用于类

1
2
3
4
5
6
7
8
9
10
11
public static synchronized void method(){

}

等价写法:
public void method(){
synchronized(Myclass.class){
...
}
}

作用于类可以防止多个线程同时访问该类的实例的synchronized方法。所以该类的实例之间时存在干扰的。也就是说,该类创建的对象不能同时访问该synchronized方法了。

synchronized方法

每个调用sychronized方法的线程都必须获得调用给方法的类实例的锁才能执行,否者所属的线程阻塞。方法一旦执行就独占该锁,直到从该方法返回时财将锁释放。同步方法实质是将synchronized作用于object reference。将一个大的方法什么伟synchronized会严重影响效率。

synchronized代码块

对一块代码进行加锁,表示只对这个区块的资源进行互斥访问,他的作用域是当前对象,当没有明确的对象作为锁时,可以创建一个特殊的instance变量,来充当锁。

1
2
3
4
5
6
7
8
9
class Foo implements Runnable{
private byte[] lock=new byte[0];
public void methodA(){
synchronized(lock){
//
}
}
}

零长度的byte数组,创建的时候只需要三行字节码即可。

重进入

内部锁是可重进入的,因此线程在试图获得它自己占有的锁时,请求会成功。重进入意味着请求是基于“每线程”,而不是基于”每调用的“。原理是每个锁都关联一个请求计数器。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Widget{
public synchronized void doSomething(){
...
}
}

public class LoggingWidget extends Widget{
//代码没有死锁,说明内部锁是可重入的
public synchronized void doSomething{
System.out.println(toString()+":calling doSomething");
super.doSomething();
}
}

synchronized 与原子性

原子性是指一个操作时不可中断的,要么全部执行完成,要么都不执行。
线程时CPU调度的基本单位,CPU有时间篇的概念,会根据不同的调度算法来进行线程的调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU的使用权。所以在多线程的场景下,就会出现原子性的问题。
java提供了monitorentermonitorexit两个高级操作字节码,来确保被synchronized修饰的代码在同一时间只能被同一个线程访问到,在锁为释放之前,其它线程无法获得该段代码的执行权。
原理是:
线程1在执行 monitorenter时,会对Monitor进行加锁,加锁后其它线程无法获得锁,除非线程1主动释放锁。及时在执行过程中,线程1放弃了CPU使用权,但是因为它没有释放锁,由于synchronized可重进入的,下一次还是只有线程1才能获得执行权,还会继续执行代码,保证所有的代码在没有其它线程打扰下执行完,所以保证了原子性。

synchronzied 与可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量,其它线程是能够立刻看到修改的值。
synchronized锁住的对象,其值具有可见性。
原理是:
java内存模型规定了所有的变量都存放在主存中,每个线程还有自己的工作内存,线程会将要主存中的变量拷贝到工作内存中,线程对所有的操作都必须在工作内存中进行,不能直接读写主内存,不同的线程之间也无法直接访问对方的工作内存,线程之间变量需要借助主存作为中转。
为了保证可见性,对一个变量解锁之前,必须要将变量同步会主存。这样解锁后,后续线程就可以访问到被修改后的值。

synchronized与有序性

有序性即程序执行的顺序按照代码的先后顺序执行。
synchronized提供了有序性保证
由于存在处理器优化和指令重排,CPU可能对代码乱序执行。当synchronized是不能禁止指令重拍和处理优化的。

如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的。

重排序是有规则限制的,其必须保证单线程程序执行的结果不能因为重排序而改变。由于synchronized确保了同一时间只能被唯一线程访问(单线程执行),所以可以保证有序性。