Java线程同步实现方式

作为一个Java程序员,平常的工作中除了实现代码的业务逻辑,还需要提高代码运行的效率。Java作为一个支持多线程的语言,并发编程对于提高程序的效率至关重要,在Java并发编程是整个Java开发体系中最难以理解但也是最重要的知识点,也是各类开源分布式框架中各个并发组件实现的基础。本周学习了一下Java的并发编程,总结如下。

一、Java并发编程需要注意什么?

1、共享数据的安全性问题

堆内存和方法区内存可以共享。 因此成员变量和静态变量存在数据安全性问题。

2、锁竟争带来的程序效率问题

多个线程访问共享资源时,只有获取到锁的线程才允许访问共享资源,未获得到锁的线程只能在临界区进行排队等待,试想如果有1000个线程同时访问共享资源,那么最后一个线程必须要等前面999个线程执行完后才能够进入监界区操作共享资源。如果锁没有控制好,非常容易出现程序整体性能低下的情况。

二、程序安全性主要体现在哪些方面

原子性:Java 的原子性就和数据库事务的原子性差不多,一个操作中要么全部执行成功或者失败。

可见性:一个线程修改数据,修改的数据对其它线程来说立即可见

顺序性:程序执行顺序与代码一致,禁止Java的指令重排。

三、并发控制的方法

1、采用同步关键字synchronized

 

2、采用原子变量aomitc

 

3、加锁ReentrantLock

 

4、采用信号量Semaphore

 

四、synchronized原理

synchonized原理是借用对象markword中的标记以及monitor监控器生成monitorEnter以及monitorExit指令以及对应的计数器。如图1所示,Thread1、Thread2、Thread3竞争资源锁,如果Thread1获取到资源锁,Thread2和Thread3将在EntryList中等待执行、此时Thread2和Thread3处于阻塞状态、不会消耗CPU,响应时间缓慢。

 

 

 

 

图1

 

在jdk1.6之前,synchronized是非常重量级的,因为它会无时无刻进行锁住对象,而不考虑到程序实际的竞争情况,大多数程序在都是进行交替执行,也就是说不存在资源的竞争,如果没有竞争,但是加锁,加锁和解锁是非常耗费性能的,(重量级)因为线程之间的切换以及线程从内核态到用户态的时间是耗费性能的。在jdk1.6之后,sun公司对synchronized进行了大幅度的优化,现在采用偏向锁+(轻量级锁+cas)+重量级,之间通过锁碰撞进行切换。

五、原子变量atomic原理

原子变量atomic的原理是乐观锁:自旋+CAS。首先使用value保存int值,使用volatile修饰保证线程可见性(即线程每次读取均从主内存读取,而不是读取线程自己的工作内存的副本值,这样可以确保某个线程将副本值覆盖主内存的写操作对其他线程的可见性)。自增这一类操作都是乐观方式的失败重试机制。

六、ReentrantLock原理

ReentrantLock是基于AbstractQueuedSynchronizer实现的,下面简称为AOS,AQS是整个并发框架里面锁的核心,给出了一个抽象阻塞队列同步器,用一个state表示竞争资源,队列中头结点的后续节点排队等待。AQS中已经实现好了同步队列节点的维护(出队入队、删除等等),条件队列节点的维护(用于模拟线程通信),实现了独占和共享两种方式的获取逻辑(同步队列入队以及线程挂起)、释放逻辑(同步队列出队以及线程唤醒),而其子类扩展四个方法则做两件事情:1、通过CAS对资源加减或者0/1赋值操作,2、获取逻辑返回正/负数用于AQS判断是否需要加入同步队列进行挂起,释放逻辑返回true/false用于AQS判断是否需要唤醒同步队列的节点。比如locks包下面的ReentrantLock、Semaphore、CountDownLatch等都有一个Sync(AQS子类)引用用于实现不同功能。大概原理图如下:

 

 

 

 

图2

上图展示的独占模式的锁实现,使用state=0/1来表明资源是否被占用。共享模式使用一个整数state=n来表示资源个数,某个节点(线程)调用acquire(m)会一次性获取m个许可对应着state=n-m,某个线程Thread(5)需要5个许可但是state小于5那么这个线程尝试获取锁失败,加入同步队列利用自旋和CAS阻塞等待。比如Semaphore(10)构造时,AQS的state=10,然后每个线程调用semaphore.acquire()获取锁就会使得state减一,同样释放锁release()会使的state加1,这样控制某个任务在acquire()和release()中间的代码只能由10个线程同时执行。

与synchronized不同,AbstractQueuedSynchronizer中除了获取到资源的线程外,在队列中等待的线程只有一个处于自旋转状态,这样既可以保证等待的线程快速获取到锁,又可以让其它线程处于阻塞状态,避免不必要的CPU消耗。

七、Semaphore的原理

Semaphore提供了一种基于许可的同步方式,构造时初始化一个许可数量n,acquire()阻塞方式获取1个许可,release()返回1个许可,这样可以控制两个方法之间的任务代码只能最多有n个持有许可的线程同时执行,其余线程绑定节点后在同步队列上挂起等待。

与ReentrantLock类似,Lock()上锁,Unlock()释放锁,使用的是AQS的独占模式,而Semaphore使用的是AQS的共享模式。

八、总结

Java并发编程中,虽然实现的技术在不断演进,但是主要的优化方向不外乎两个方面:共享数据的安全性和锁的性能问题。所以今后在用Java进行多线程编程时,主要针对数据安全性和锁的性能进行现实方案的选型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值