锁和同步,学习多线程避不开的两个问题,Java提供了synchronized关键字来同步方法和代码块,还提供了很多方便易用的并发工具类,例如:LockSupport、CyclicBarrier、CountDownLatch、Semaphore…

有没有想过自己实现一个锁呢?

笔者通过一个“抢票”的程序,分别用几种不同的方式来实现方法的同步和加锁,并分析它们的优劣。

自旋

就是让加锁失败的线程死循环,不要去执行逻辑代码。

/**

* @author 潘

* @Description 抢票-自旋锁

*/

public class Ticket {

//加锁标记

private AtomicBoolean isLock = new AtomicBoolean(false);

//票库存

private int ticketCount = 10;

//抢票

public void bye(){

while (!lock()) {

//加锁失败,自旋

}

String name = Thread.currentThread().getName();

//加锁成功,执行业务逻辑

System.out.println(name + ":加锁成功...");

System.out.println(name + ":开始抢票...");

//SleepUtil.sleep(1000);

ticketCount--;

System.out.println(name + ":抢到了,库存:" + ticketCount);

System.out.println(name + ":释放锁.");

unlock();

}

//加锁的过程必须是原子操作,否则会导致多个线程同时加锁成功。

public boolean lock(){

return isLock.compareAndSet(false, true);

}

//释放锁

public void unlock() {

isLock.set(false);

}

public static void main(String[] args) {

Ticket lock = new Ticket();

//开启10个线程去抢票

for (int i = 0; i < 10; i++) {

new Thread(() -> lock.bye()).start();

}

}

}

输出如下:

Thread-0:加锁成功...

Thread-0:开始抢票...

Thread-0:抢到了,库存:9

Thread-0:释放锁.

Thread-3:加锁成功...

Thread-3:开始抢票...

Thread-3:抢到了,库存:8

Thread-3:释放锁.

Thread-4:加锁成功...

Thread-4:开始抢票...

Thread-4:抢到了,库存:7

Thread-4:释放锁.

......

加锁的过程必须是原子操作,否则会导致多个线程同时加锁成功。

自旋是实现加锁最简单的方式,但是缺点也很明显:

自旋时CPU空转,浪费CPU资源。

如果使用不当,线程一直获取不到锁,会造成CPU使用率极高,甚至系统崩溃。

yield+自旋

要解决自旋锁的性能问题,首先就是尽可能的防止CPU空转,让获取不到锁的线程主动让出CPU资源。

获取不到锁的线程主动让出CPU资源,可以通过Thread.yield()实现。

bye()可以做如下优化:

public void bye(){

while (!lock()) {

//获取不到锁,主动让出CPU资源

Thread.yield();

}

String name = Thread.currentThread().getName();

//加锁成功,执行业务逻辑

System.out.println(name + ":加锁成功...");

System.out.println(name + ":开始抢票...");

//SleepUtil.sleep(1000);

ticketCount--;

System.out.println(name + ":抢到了,库存:" + ticketCount);

System.out.println(name + ":释放锁.");

unlock();

}

Thread.yield()虽然让出了CPU资源,但还是会继续争夺,很可能CPU下次还会继续分配时间片给该线程。

yield+自旋适用于两个线程竞争的情况,如果线程太多,频繁的yield也会增加CPU的调度开销。

Sleep+自旋

除了使用yield让出CPU资源外,还可以使用Sleep将获取不到锁的线程暂时休眠,不占用CPU的资源。

bye()可以做如下优化:

public void bye(){

while (!lock()) {

//获取不到锁的线程,暂时休眠1ms,释放CPU资源

SleepUtil.sleep(1);

}

String name = Thread.currentThread().getName();

//加锁成功,执行业务逻辑

System.out.println(name + ":加锁成功...");

System.out.println(name + ":开始抢票...");

//SleepUtil.sleep(1000);

ticketCount--;

System.out.println(name + ":抢到了,库存:" + ticketCount);

System.out.println(name + ":释放锁.");

unlock();

}

使用Sleep可以减轻CPU的压力,但是缺点也很明显:

sleep时间不可控

使用多线程的目的就是为了提升性能,减少响应时间,我们无法预估线程运行结束的时间,sleep的时间是不可控的,在高并发的场景下,哪怕1毫秒、1纳秒都应该分秒必争。

性能测试

笔者进行了简单的测试,抢夺一亿张票,结果如下:

自旋:耗时21806ms。

yield+自旋:耗时2543ms。

sleep+自旋:耗时1593ms。

测试结果仅供参考。

park+自旋

相较于前几种,是比较好的一种实现方式,需要借助于LockSupport来完成。

/**

* @author 潘

* @Description 抢票-park+自旋

*/

public class TicketPark {

//加锁标记

private AtomicBoolean isLock = new AtomicBoolean(false);

//票库存

private int ticketCount = 10;

//等待线程队列

private final Queue WAIT_THREAD_QUEUE = new LinkedBlockingQueue<>();

//抢票

public void bye(){

while (!lock()) {

//获取不到锁的线程,添加到队列,并休眠

lockWait();

}

String name = Thread.currentThread().getName();

//加锁成功,执行业务逻辑

System.out.println(name + ":加锁成功...");

System.out.println(name + ":开始抢票...");

ticketCount--;

System.out.println(name + ":抢到了,库存:" + ticketCount);

System.out.println(name + ":释放锁.");

unlock();

}

//加锁的过程必须是原子操作,否则会导致多个线程同时加锁成功。

public boolean lock(){

return isLock.compareAndSet(false, true);

}

//释放锁

public void unlock() {

isLock.set(false);

//唤醒队列中的第一个线程

LockSupport.unpark(WAIT_THREAD_QUEUE.poll());

}

public void lockWait(){

//将获取不到锁的线程添加到队列

WAIT_THREAD_QUEUE.add(Thread.currentThread());

//并休眠

LockSupport.park();

}

}

java.util.concurrent包下很多类都是采用park+自旋来实现同步的,ReentrantLock也不例外!

尾巴

Java实现锁大致分为这么几种方式,感兴趣的同学也可以自己动手写一个Lock。

java同步锁有哪几种_Java实现锁的几种方式相关推荐

  1. 操作系统锁的实现方法有哪几种_java 偏向锁、轻量级锁及重量级锁synchronized原理...

    Java对象头与Monitor java对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的. 对象头包含两部分:Mark Word 和 ...

  2. 用java语言求前50个素数_Java求质数的几种常用算法总结

    Java求质数的几种常用算法分析 本文实例讲述了Java求质数的几种常用算法.分享给大家供大家参考,具体如下: 1.根据质数的定义求 质数定义:只能被1或者自身整除的自然数(不包括1),称为质数. 利 ...

  3. java包装类有几种_Java包装类型有几种

    Java包装类型有8种,分别是:1.Byte:2.Integer:3.Short:4.Long:5.Float:6.Double:7.Boolean:8.Character. [相关学习推荐:java ...

  4. java截取倒数后六位的字符串_java 字符串截取的三种方法(推荐)

    众所周知,java提供了很多字符串截取的方式.下面就来看看大致有几种. 1.split()+正则表达式来进行截取. 将正则传入split().返回的是一个字符串数组类型.不过通过这种方式截取会有很大的 ...

  5. java 同步中的线程出现异常会放弃锁吗

    http://zhidao.baidu.com/question/374619544.html 实验证明:会 下面代码r1会抛出异常,但是r2仍能拿到o对象的锁 public class Test { ...

  6. java同步锁售票_Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)...

    学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线程:线程是 ...

  7. java细粒度锁_Java细粒度锁实现的3种方式

    最近在工作上碰见了一些高并发的场景需要加锁来保证业务逻辑的正确性,并且要求加锁后性能不能受到太大的影响.初步的想法是通过数据的时间戳,id等关键字来加锁,从而保证不同类型数据处理的并发性.而java自 ...

  8. java key锁_Java细粒度锁实现的3种方式

    转自:http://www.cnblogs.com/wxd0108/p/5488297.html 最近在工作上碰见了一些高并发的场景需要加锁来保证业务逻辑的正确性,并且要求加锁后性能不能受到太大的影响 ...

  9. java线程锁有哪几种_java有哪些锁种类

    java有哪些锁种类(转) 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲 ...

  10. java同步锁实例_Java lock同步锁使用实例解析

    这篇文章主要介绍了Java lock同步锁使用实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1)Lock是一个接口,而synchroniz ...

最新文章

  1. sap系统前台数据与后台表之间_数据治理之SAP软件非生产数据清除方法
  2. 为了OFFER,菜鸟的我必须搞懂动态规划系列三个背包问题之多重背包(二进制优化方法)
  3. mysql数据库、表、索引、触发器
  4. MyBatis 缓存详解-一级缓存(本地缓存)介绍
  5. 实战 | GitLab + Docker 实现多环境部署
  6. bat脚本 git pull_Git遇到错误时如何解决的一些坑
  7. Ext this.getView(...).saveDocumentAs is not a function
  8. LoadRunner 11 安装及破解
  9. 未来计算机的新技术有哪些,科技改变生活!人类未来的十大高科技生活
  10. 【漏洞复现】Hadoop YARN 资源管理系统 REST API未授权访问
  11. WiMAX与Wi-Fi、DSL和3G的竞合关系
  12. 计算机itunes无法安装,Win7电脑无法安装itunes怎么办 win7安装itunes失败的解决方法...
  13. html网页如何在手机上实现,html和css结合实现手机端网页自适应的代码
  14. html实现百度换肤,案例实战(二):百度脑图网站换肤
  15. “.NET研究”专访微软MVP衣明志:走进ASP.NET MVC 2框架开发
  16. python distribute包管理工具安装AttributeError错误
  17. 绿芯GreenChip电容式触控触摸IC型号性能介绍
  18. 易语言路由器服务器,无线路由器一键设置工具,制作过程,成品。
  19. 二叉树的遍历-先序遍历、中序遍历、后序遍历
  20. 英雄联盟MSI季中赛第四局,UZI卡莎再次天秀,RNG3:1夺冠

热门文章

  1. SoftICE使用(3)—在VMware中配置远程SoftICE的另一种办法 zz xfocus
  2. sourceTree 添加 ssh key 方法
  3. NVIDIA Forceware 260.89 Final 提升了多款游戏的性能
  4. 武汉理工大学计算机转专业名单,武汉理工大学转专业的限制
  5. 小米手机打开报告mimu查看程序调试错误locat
  6. 级数ex展开_泰勒级数的若干展开方法
  7. 74hc595级联c语言程序,10个74HC595级联 单片机程序请教
  8. Day2:Surveying China’s livestreaming economy
  9. 赵小楼《天道》《遥远的救世主》深度解析(38)丁元英的“自嘲”和作者豆豆的深意
  10. python星号直角三角形边长公式_直角三角形求边长公式图解