一.synchronized关键字
在多线程编程中,常常看到synchronized关键字。从字面来理解,就是一个锁,就是说被这个关键字修饰的部分相当于上了个锁,拿到了这个锁的线程才可以使用,其他线程无法使用,除非锁被释放。当然这只是浅显的理解了synchronized,接下来,就让我们来探索属于synchronized的奥秘吧~
-
synchronized作用于何处?
- synchronized作用于实例方法
同步方法,锁的是对象的实例
public synchronized void update(){
}
来看一段代码
public class SynchronizedTest implements Runnable {
static int i = 0;//共享数据
/*
* 自增操作
*/
public synchronized void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
increase();
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest instance = new SynchronizedTest();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
Thread.currentThread().sleep(1000);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
运行结果:
这个synchronized作用于increase这个实例方法上,实际上锁的是调用该方法的实例对象instance。我们很清楚一个对象只能有一把锁,因此当一个线程正在访问一个对象的synchronized作用的实例方法时,其他线程的就不能访问该对象的synchronized方法。这种同步方法也有可能会出现问题,比如当另一个线程重新new了一个实例对象,去访问synchronized作用的方法,此时,是不影响的,所以会导致共享资源的安全性就无法保证了。
2. synchronized作用于静态方法
//加在静态方法,锁的是类
public static synchronized void del(){
}
来看代码
public class SynchronizedTest implements Runnable {
static int i=0;
/**
* 作用于静态方法,锁是当前class对象,也就是
* SynchronizedTest类对应的class对象
*/
public static synchronized void increase(){
i++;
}
/**
* 非静态,访问时锁不一样不会发生互斥
*/
public synchronized void increase4Obj(){
i++;
}
@Override
public void run() {
for(int j=0;j<10000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new SynchronizedTest());
Thread t2=new Thread(new SynchronizedTest());
//启动线程
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
运行结果依旧是20000.
当synchronized作用于静态方法时,锁的是当前类的对象
3. synchronized作用于代码块
//同步代码块,锁的是代码块
public void add(Object obj) {
synchronized (obj) {
}
}
public class SynchronizedTest implements Runnable {
static SynchronizedTest instance=new SynchronizedTest();
static int i=0;
@Override
public void run() {
synchronized(instance){
for(int j=0;j<10000;j++){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
synchronized作用于一个给定的实例对象instance,这个实例对象就是锁对象,每次当线程进入synchronized包裹的代码块时就会要求当前线程持有instance实例对象锁,如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待。
二.volatile关键字
首先来说几个概念
原子性:意思就是操作不可被划分,它要么做,要么不做。比如读取和赋值操作
可见性:即一个共享变量被修改时,它的值会被立即更新到主存,这说明它是可见的。在Java中,普通的变量不具有可见性,只有被volatile关键字修饰的才具有可见性。或者可以通过synchronized和Lock加锁的方式,保证在同一时刻只有一个线程去操作这个共享变量。
有序性:Java内存模型具有先天的有序性,称其为happens-before原则,具体内容如下
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。
同时,Java中可以通过volatile关键字和加锁的方式来保证有序性。
了解了上述几个概念,接下来学习一下volatile关键字。
一旦一个变量被volatile修饰,就代表一个线程操作了这个变量,那么其他变量是立即可以看到改变后的值的。
有几个问题
1.volatile可以保证可见性吗?
当然是可以的。Java中通过volatile来保证共享变量的可见性。
2.volatile可以保证原子性吗?
不可以
来看代码
class Test{
/**
* 运行结果总小于10000
* 自增操作不是原子操作!分为读取当前值,对其+1,再写入内存。假设某时刻变量a值为10,
线程1堆a进行读操作后被阻塞,由于线程1对变量进行操作,导致线程2的缓存
行无效,去主存读取,线程1只进行读操作,并未修改a的值,因此a变为11,此时线程1再进行
自增,对自己缓存行内的10自增变为11,再将其写入内存,本来我们理解会是12,但实际上a
的值为11
*/
public volatile int a = 0;
public void increase(){
a++;
}
}
public class Test2 {
public static void main(String[] args) {
final Test test = new Test();
for(int i =0;i<10;i++){
new Thread(){
public void run(){
for(int i = 0;i<1000;i++){
test.increase();
}
}
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.a);
}
}
这段代码的打印结果总是小于10000,与预期打印出10000不符,原因见代码注释。
3.volatile可以保证有序性吗?
在前面提到volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;保证先写后读。
2)不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
三.synchronized与volatile比较
1.synchronized可以保证原子性和可见性,volatile只能保证可见性
2.volatile不会造成线程的阻塞,synchronized可能会造成线程的阻塞。由于volatile不需要加锁,比synchronized更轻量级,不会阻塞线程
3.volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。