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

本文深入探讨Java中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关键字以及单例模式与线程安全问题

下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一次成为扩展节点的机会,且会一次性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
源码来自:https://pan.quark.cn/s/a4b39357ea24 在VC++开发过程中,对话框(CDialog)作为典型的用户界面组件,承担着用户进行信息交互的重要角色。 在VS2008SP1的开发环境中,常常需要满足为对话框配置个性化背景图片的需求,以此来优化用户的操作体验。 本案例将系统性地阐述在CDialog框架下如何达成这一功能。 首先,需要在资源设计工具中构建一个新的对话框资源。 具体操作是在Visual Studio平台中,进入资源视图(Resource View)界面,定位到对话框(Dialog)分支,通过右键选择“插入对话框”(Insert Dialog)选项。 完成对话框内控件的布局设计后,对对话框资源进行保存。 随后,将着手进行背景图片的载入工作。 通常有两种主要的技术路径:1. **运用位图控件(CStatic)**:在对话框界面中嵌入一个CStatic控件,并将其属性设置为BST_OWNERDRAW,从而具备自主控制绘制过程的权限。 在对话框的类定义中,需要重写OnPaint()函数,负责调用图片资源并借助CDC对象将其渲染到对话框表面。 此外,必须合理处理WM_CTLCOLORSTATIC消息,确保背景图片的展示不会受到其他界面元素的干扰。 ```cppvoid CMyDialog::OnPaint(){ CPaintDC dc(this); // 生成设备上下文对象 CBitmap bitmap; bitmap.LoadBitmap(IDC_BITMAP_BACKGROUND); // 获取背景图片资源 CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap* pOldBitmap = m...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值