synchronized和volatile关键字的使用

synchronized和volatile关键字的使用总结

  • 前言
    • 1.synzhronized 关键字
      • Demo:synchronized 关键字锁的对象 instance object v.s. class object
    • 2.volatile 关键字
      • Demo:volatile的使用及局限性
    • 3. 关于synchronized关键字

前言

本文着重介绍synchronized和volatile关键字的各种使用,并且尽可能多的将使用中可能碰到的情况,通过代码demo的形式展现出来。

1.synzhronized 关键字

synchronized 关键字,给一个对象上锁,任何需要访问该对象的线程,都必须先获得该锁。
synchronize关键字锁定的是对象不是代码块

Demo:synchronized 关键字锁的对象 instance object v.s. class object

synchronized 关键字锁定的对象有2中:1是类的实例(实例对象锁),2是类对象(类对象锁)
synchronized 关键字用得不恰当时,并不一定能保证实现线程安全,具体还要看锁定的对象是否唯一

/**
 *	这里syncrhonized 关键字锁定的是 object对象
 **/
public class Demo1 {

    private int count = 10;
    private Object object = new Object();

    public void test(){
        synchronized (object){
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }

}
/**
 *	这里syncrhonized 关键字锁定的是当前类的实例对象,即Demo2的Instance
 **/
 
 public class Demo2 {

    private int count = 10;

    public void test(){
        //synchronized(this)锁定的是当前类的实例,这里锁定的是Demo2类的实例
        synchronized (this){
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }

}
 
/**
 *	这里syncrhonized关键字直接加在方法声明上,相当于是syncrhonized(this)
 **/
 
public class Demo3 {

    private int count = 10;

    //直接加在方法声明上,相当于是synchronized(this)
    public synchronized void test(){
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
}

/**
 *	这里syncrhonized关键字修饰静态方法,锁定的是类对象
 **/
 
public class Demo4 {

    private static int count = 10;

    //synchronize关键字修饰静态方法锁定的是类对象
    public synchronized static void test(){
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    public static void test2(){
        synchronized (Demo4.class){//这里不能替换成this
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);

        }
    }

}

2.volatile 关键字

volatile 关键字,使一个变量在多个线程间可见

Demo:volatile的使用及局限性

使用:
volatile关键字可以用来保证变量在线程之间的可见性

/**
*	main线程和t1线程都共用到一个变量running,java默认保留的是t1线程中的一份副本,这样如果main线程修改了该变量,t1线程未必知道
*	使用volatile关键字,会让所有线程都会读到变量的修改值
*	在下面的代码中,running是存在与Java堆内存的t对象中,
*	当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中,直接使用这个副本,
*	并不会每次都去读取对内存,这样,当主线程修改running的值之后,t1线程无法知道,因此也不会停止运行。
*	而当我们在running值加上volatile关键字之后,当running值发生变化时,会第一时间通知到t1线程,t1线程也会更新running值,
*	此时t1也将停止运行。
**/
public class Demo {

    /*volatile*/ boolean running = true;

    public void test(){
        System.out.println("test start...");
        while (running){

        }
        System.out.println("test end...");
    }

    public static void main(String[] args) {
        Demo demo = new Demo();

        new Thread(() -> demo.test(),"t1").start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        demo.running = false;
    }

}

局限性:
volatile并不能保证多个线程共同修改同一个变量时所带来的不一致问题,
也就是说volatile并不能替代synchronized或者说volatile保证不了原子性。

/**
*	理想情况,当10个线程每个线程对count进行1万次添加操作后,我们得到的结果应该是10w,然而运行以下代码,
*	我们得到的count值要远远小于10w。Why? 
*	比如,当第一个线程加到100时,还没往上加,这个时候,第二个线程也来了,把100拿过来执行++操作。
*	然后第一个线程继续加到101,第二个线程也加到101,他们往回写的值都是101。所以虽然说他们都进行了++操作,实际上只加了1。
**/
public class Demo {

    volatile int count = 0;

    public  void test(){
        for (int i = 0; i < 10000; i++) {
            count ++;
        }
    }


    public static void main(String[] args) {
        Demo demo = new Demo();

        List<Thread> threads = new ArrayList();

        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(demo::test, "thread-" + i));
        }

        threads.forEach((o)->o.start());

        threads.forEach((o)->{
            try {
                o.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        System.out.println(demo.count);
    }


}

3. 关于synchronized关键字

在jdk1.5及之前的版本,synchronized关键字只存在一种锁:重量级锁。这种锁虽然可以保证被锁定对象的线程安全,但是性能过于低下。于是Sun公司在jdk1.6之后对synchronized 关键字进行了大幅度的优化,引入了偏向锁和轻量级锁。当不存在线程竞争时(即只有1个线程获取被syncrhonized关键字所修饰的对象时),synchronized关键字会使用偏向锁;而当有2个或以上线程获取该对象且未发生竞争时(即2个或以上线程交替获取该对象),锁会膨胀为轻量级锁;只有当2个或以上线程获取该对象且发生竞争时,锁会最终膨胀为重量级锁。这三种锁的性能相差极大,具体细节我将在以后的Blog中证明与阐述。

你可能感兴趣的:(并发)