synchronized关键字与volatile关键字

一.synchronized关键字

在多线程编程中,常常看到synchronized关键字。从字面来理解,就是一个锁,就是说被这个关键字修饰的部分相当于上了个锁,拿到了这个锁的线程才可以使用,其他线程无法使用,除非锁被释放。当然这只是浅显的理解了synchronized,接下来,就让我们来探索属于synchronized的奥秘吧~

  • synchronized作用于何处?

  1. 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是一个方法或块的修饰符。

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值