并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题
再说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可以禁止指令重排序,会对该变量的写操作设置一道屏障,在该变量没有赋值完成之前,不允许其他线程对该变量进行读操作。
volatile的使用及原理:
volatile在多线程的环境下保证了变量的修改对每个线程可见,可以轻量级的保证线程安全问题,比如在A线程中修改了a变量的值,那么该修改在线程b中是可见的,a变量一旦发生改变完成,那么b线程就能立刻读取到a改变后的值,这样就实现了线程之间通信的一个概念,保证线程的安全,相比较synchronized而已,volatile只能够保证一些原子性操作的变量的值一致性。在运用可以使用volatile来告诉另一个线程是否可以执行等操作。
volatile实现的原理是,在多个处理器处理任务时,cpu会将任务缓存到处理器中,这样可以加快任务的执行效率,但是到资源被写回到内存中时,所有处理器上缓存的该资源都将会失效,需要重新从内存中读取资源,为了保证数据一致性。而volatile关键字的作用就是将每个变量经处理器执行之后立刻写回到内存中,此时其他处理器失效,重新重内存中读取该资源,实现了变量在线程中的可见性,但是根据原理也可以看出,使用volatile也会降低处理器的执行效率,进而影响到程序的执行速度。所以在开发中volatile的使用也是需要注意和思考取舍的。
原文 并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题
并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题的更多相关文章
- Java并发编程学习笔记
Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...
- 并发编程学习笔记(4)----jdk5中提供的原子类及Lock使用及原理
(1)jdk中原子类的使用: jdk5中提供了很多原子类,它会使变量的操作变成原子性的. 原子性:原子性指的是一个操作是不可中断的,即使是在多个线程一起操作的情况下,一个操作一旦开始,就不会被其他线程 ...
- JUC并发编程学习笔记
JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...
- 并发编程学习笔记(15)----Executor框架的使用
Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...
- 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理
1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...
- 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理
· 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...
- 并发编程学习笔记(11)----FutureTask的使用及实现
1. Future的使用 Future模式解决的问题是.在实际的运用场景中,可能某一个任务执行起来非常耗时,如果我们线程一直等着该任务执行完成再去执行其他的代码,就会损耗很大的性能,而Future接口 ...
- 并发编程学习笔记(12)----Fork/Join框架
1. Fork/Join 的概念 Fork指的是将系统进程分成多个执行分支(线程),Join即是等待,当fork()方法创建了多个线程之后,需要等待这些分支执行完毕之后,才能得到最终的结果,因此joi ...
- 并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理
在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并 ...
随机推荐
- linux句柄泄露问题查看
背景: 我们在开发linux在线server的时候常常会遇会句柄泄露的问题.由于在linux系统设计里面遵循一切都是文件的原则.即磁盘文件.文件夹.网络套接字.磁盘.管道等,全部这些都是文件.在我们进 ...
- iOS开发----iOS 8的虚化效果
在iOS 7中,一个重大的改变就是随处可见的虚化,这在通知中心和控制中心表现得尤为抢眼: 然而,当开发人员们着手去将类似的模糊效果增加自己的App的时候,他们会发现有相当严重的障碍. 那时苹果所界定的 ...
- 李洪强漫谈iOS开发[C语言-044]-翻译数字
李洪强漫谈iOS开发[C语言-044]-翻译数字
- 在Linux上安装zsh
简单介绍: 相对于绝大多数linux发行版默认的shell--bash,zsh绝对是一个优秀的替代品.zsh是交互型shell,同一时候它也是一个强大的编程语言,很多bash,ksh,tcsh优秀的地 ...
- XML解析方式汇总
XML解析方式汇总 分类: XML2011-08-23 19:19 167人阅读 评论(0) 收藏 举报 xmlstringexceptionattributesclassiterator DOM解析 ...
- [Codeforces 339D] Xenia and Bit Operations
[题目链接] https://codeforces.com/problemset/problem/339/D [算法] 线段树模拟即可 时间复杂度 :O(MN) [代码] #include<bi ...
- werkzeug 详解
首先,先向大家介绍一下什么是 werkzeug,Werkzeug是一个WSGI工具包,他可以作为一个Web框架的底层库.这里稍微说一下, werkzeug 不是一个web服务器,也不是一个web框架, ...
- SVN主干与分支的合并 ***
下面我将step by step地演示如何一次完整的branching和merging,包括创建分支.分支开发.分支和主线同步,分支合并到主线的全过程,甚至包括如何在本地创建一个测试用的reposit ...
- 26.Extjs 部门列表信息展示页面
/** * @author sux * @time 2011-1-14 * @desc 部门信息显示 */ deptInfoGridPanel = Ext.extend(Ext.grid.Editor ...
- easyui-datebox 年月视图显示
//年月视图做法 $('#startYearDate').datebox({ onShowPanel: function () { //显示日趋选择对象后再触发弹出月份层的事件,初始化时没有生成月份层 ...