首页 > 编程语言 > 详细

并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题

时间:2018-09-20 22:33:53      阅读:161      评论:0      收藏:0      [点我收藏+]

再说synchronized关键字之前,我们首先先小小的了解一个概念-内置锁。

什么是内置锁?

在java中,每个java对象都可以用作synchronized关键字的锁,这些锁就被称为内置锁,每个对象的锁的信息都存在对象头中

所以synchronized关键字在使用过程中之所以能够保证线程的安全,也是因为使用了锁。下面就说说synchronized具体的几种用法,及使用何种类型的内置锁。

(一)用synchronized关键字修饰实例方法,示例代码:

/**
     * synchronized 作用在实力方法上时,此时的内置锁为当前对象,即this
     */
    public synchronized void fun1 () {

    }

当synchronized关键字作用在实例方法上时,此时的内置锁为当前的对象-thsi。

(二)用synchronized关键字修饰静态方法,示例代码:

 /**
     * synchronized 作用在静态方法上时,此时的内置锁为当前类的字节码(Xxx.class)所对应Class对象
     */
    public static synchronized void fun2 () {

    }

当synchronized关键字作用在静态方法(类方法)上时,此时的内置锁为当前类的字节码对应的Class对象,及Xxx.class所对应的对象。

(三)用synchronized关键字修饰代码块,示例代码:

 /**
     * synchronized 作用在代码块时,此时的内置锁为括号中的对象
     */
    public static synchronized void fun3 () {

        synchronized (Main.class){
        }
    }

当作用在代码块上时,内置对象为括号中的对象,此时的括号是必须要有的,因为在代码块中,synchronized必须要接收一把锁来保证线程的安全,这里对象可以是任意的java对象,如你可以自己创建一个obj传入也是没有问题的。

由于每次的锁的获取和释放总是会耗费时间,所以在synchronized中优化中也存在了偏向锁,轻量级锁等。

偏向锁:很多情况下,竞争锁时不是多个线程,而是单个线程在访问,如果此时依然按照多个线程来获取和释放锁,就会在消耗资源,所以在java的对象头中的Mark Word中就会存储偏向锁的信息,偏向锁会偏向于第一次进来的线程,如果在运行过程中不存在其他线程使用同步锁的情况下,那么就会给当前线程添加偏向锁标记。它不会每次执行都获取释放锁,当遇到其他线程竞争该锁时,该偏向锁会被挂起,jvm将会消除该锁的偏向锁标记,使其变成标准的轻量级锁。偏向锁适用于始终只有一个线程执行任务的情况。可以通过命令 开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

关闭偏向锁:-XX:-UseBiasedLocking

轻量级锁:轻量级锁是偏向锁失败升级得来的,它会简单的将对象头部作为指针,自旋的指向持有锁的线程堆栈内部,以此来判断一个线程是否持有对象锁,如果线程获取到轻量级锁成功,该线程可以顺利的进入到临界区执行,如果失败,则表示其他线程已经抢夺到了所,那么当前的轻量级锁就会膨胀成为重量级锁。

单例模式与线程安全问题。

在单利模式的懒汉式下,由于所执行的都是原子性操作,所以不会产生线程安全问题,但是当在饿汉式的模式下,由于非原子性操作,所以在多线程的环境下就会产生安全问题,产生非单例的对象。

所以一般在写多线程下的单例模式时,都采用的是双重判断加锁的机制方式来实现单例模式。实例代码:

package com.wangx.thread.t3;

/**
 * 单利模式与线程安全
 */
public class Singleton {

    /**
     * 使用volatile可以防止指令重排序的情况下产生的线程安全问题
     */
    private volatile static Singleton singleton;
    private Singleton() {

    }

    /**
     * 使用双重判断加锁的可以避免线程安全问题
     * @return
     */
    public static Singleton getInstance() {
        if (singleton == null ) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

使用双重判定是因为在第一个if为空的情况下,可能两个线程同时进入了if代码块,但是当前一个线程获取锁并创建了对象之后,后一个语句块由于是在第一个if语句块中,所以会继续往下执行,创建新的对象,产生了安全问题,所以在synchronized代码块中还需要再进行一次判断。使用volatile关键字修饰属性是因为在虚拟机的执行过程中由于性能优化,会进行指令重排序,如第一步申请了内存空间,第二部需要在空间上创建实例,第三步将对象引用指向实例空间,但是由于指令重排序可能会在申请空间之后就将对象引用指向空间,此时的对象还会创建,但是singleton 已经指向了空间,此时如果有其他线程进来刚好执行到第一个if语句时,singleton已经不为空了,就会产出线程安全问题,所以使用volatile可以禁止指令重排序,会对该变量的写操作设置一道屏障,在该变量没有赋值完成之前,不允许其他线程对该变量进行读操作。

并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题

原文:https://www.cnblogs.com/Eternally-dream/p/9683717.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!