前言

最近有一个需求是需要将数据库的一些数据抽取出来放到文件文件命名方式为“FILENAME_yyyyMMddHHmmss”,例如FILENAME_20200625120011。计划使用多线程去实现,这样可能生成的文件名会有重复导致内容被覆盖,因此考虑加锁实现生成文件方式。这时候考虑到是使用synchronized还是Lock?

synchronized

synchronized是Java提供的一个并发控制的关键字。主要有两种用法,分别是同步方法同步代码块。也就是说,synchronized既可以修饰方法也可以修饰代码块。

代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待获取锁的线程释放锁,这里获取锁的线程释放锁只会有两种情况:

  • 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
  • 线程执行发生异常,此时JVM会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

使用synchronized实现创建文件的synchronizedDemoThread线程代码如下:

public class synchronizedDemoThread implements Runnable {public static ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<String, String>();@Overridepublic void run() {getThreadLog(getFileName());}public synchronized String getFileName() {try {String path = "FILENAME_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());if (!concurrentHashMap.containsKey(path)) {synchronized (synchronizedDemoThread.class) {if (!concurrentHashMap.containsKey(path)) {getThreadLog("不存在此路径,正在创建此路径");concurrentHashMap.put(path, path);return path;} else {getThreadLog("此路径已经存在了,需要等待创建");return getFileName();}}} else {getThreadLog("此文件路径已经存在了,请等待创建。。。");this.wait(2000);return getFileName();}} catch (Exception e) {e.printStackTrace();}return "";}
}

测试类代码:

public class LockTest {public static void main(String[] args) throws InstantiationException, IllegalAccessException {synchronizedDemoThreadTest();}//创建线程池对象模拟多线程调用public static void synchronizedDemoThreadTest(){ExecutorService executor = Executors.newCachedThreadPool();for (int i = 0; i < 5; i++) {executor.execute(new synchronizedDemoThread());}executor.shutdown();}//获取线程名和时间public static void getThreadLog(String logContent) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("[");stringBuffer.append(Thread.currentThread().getName());stringBuffer.append(" ");stringBuffer.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));stringBuffer.append("]");stringBuffer.append(logContent);System.out.println(stringBuffer.toString());}
}

测试日志:

[pool-1-thread-4 2020-06-25 12:08:47.004]不存在此路径,正在创建此路径
[pool-1-thread-4 2020-06-25 12:08:47.005]FILENAME_20200625120847
[pool-1-thread-1 2020-06-25 12:08:47.007]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-2 2020-06-25 12:08:47.009]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-3 2020-06-25 12:08:47.008]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-5 2020-06-25 12:08:47.010]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-1 2020-06-25 12:08:49.034]不存在此路径,正在创建此路径
[pool-1-thread-1 2020-06-25 12:08:49.037]FILENAME_20200625120849
[pool-1-thread-3 2020-06-25 12:08:49.038]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-2 2020-06-25 12:08:49.038]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-5 2020-06-25 12:08:49.039]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-3 2020-06-25 12:08:51.057]不存在此路径,正在创建此路径
[pool-1-thread-3 2020-06-25 12:08:51.060]FILENAME_20200625120851
[pool-1-thread-2 2020-06-25 12:08:51.066]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-5 2020-06-25 12:08:51.066]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-2 2020-06-25 12:08:53.066]不存在此路径,正在创建此路径
[pool-1-thread-2 2020-06-25 12:08:53.066]FILENAME_20200625120853
[pool-1-thread-5 2020-06-25 12:08:53.067]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-5 2020-06-25 12:08:55.069]不存在此路径,正在创建此路径
[pool-1-thread-5 2020-06-25 12:08:55.069]FILENAME_20200625120855

Lock

由于Lock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,Lock类提供了一些高级功能,主要有以下3项:

  • 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。
  • 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好
  • 锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

使用Lock实现创建文件的LockDemoThread线程代码如下:

public class LockDemoThread implements Runnable {//定义一个concurrentHashMap用来存放文件名public static ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<String, String>();//定义一个Lock对象public static Lock lock = new ReentrantLock();@Overridepublic void run() {getThreadLog(getFileName());}//获取文件名public synchronized String getFileName() {try {lock.lock();String path = "FILENAME_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());if (!concurrentHashMap.containsKey(path)) {getThreadLog("不存在此路径,重新创建");concurrentHashMap.put(path, path);return path;} else {getThreadLog("此文件路径已经存在了,请等待创建。。。");this.wait(2000);return getFileName();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}return "";}
}

测试类代码:

public class LockTest {public static void main(String[] args) throws InstantiationException, IllegalAccessException {LockDemoThreadTest();//synchronizedDemoThreadTest();}//创建线程池对象模拟多线程调用public static void LockDemoThreadTest(){ExecutorService executor = Executors.newCachedThreadPool();for (int i = 0; i < 5; i++) {executor.execute(new LockDemoThread());}executor.shutdown();}//获取线程名和时间public static void getThreadLog(String logContent) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("[");stringBuffer.append(Thread.currentThread().getName());stringBuffer.append(" ");stringBuffer.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));stringBuffer.append("]");stringBuffer.append(logContent);System.out.println(stringBuffer.toString());}
}

打印日志:

[pool-1-thread-1 2020-06-25 13:43:37.009]不存在此路径,重新创建
[pool-1-thread-2 2020-06-25 13:43:37.013]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-1 2020-06-25 13:43:37.011]FILENAME_20200625134337
[pool-1-thread-2 2020-06-25 13:43:39.054]不存在此路径,重新创建
[pool-1-thread-3 2020-06-25 13:43:39.055]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-2 2020-06-25 13:43:39.056]FILENAME_20200625134339
[pool-1-thread-3 2020-06-25 13:43:41.056]不存在此路径,重新创建
[pool-1-thread-3 2020-06-25 13:43:41.057]FILENAME_20200625134341
[pool-1-thread-4 2020-06-25 13:43:41.057]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-4 2020-06-25 13:43:43.058]不存在此路径,重新创建
[pool-1-thread-5 2020-06-25 13:43:43.060]此文件路径已经存在了,请等待创建。。。
[pool-1-thread-4 2020-06-25 13:43:43.060]FILENAME_20200625134343
[pool-1-thread-5 2020-06-25 13:43:45.062]不存在此路径,重新创建
[pool-1-thread-5 2020-06-25 13:43:45.062]FILENAME_20200625134345

总结一下

我们知道了synchronized和Lock都能达到锁的目的,那有哪些不同的地方呢?

  1. 如果获取线程需要等待IO或者其他(比如调用sleep方法)被阻塞了,但是没有释放锁,如果使用synchronized则其他线程一直无限期等待下去。这种场景适合使用Lock作为锁。
  2. 当有多个线程读写文件时,读操作和写操作会发生冲突现象,写写操作会发生冲突现象,但是读读操作不会发生冲突现象。但是采用synchronized关键字来实现同步的话,如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。这种场景适合使用Lock作为锁。
  3. 通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
  4. Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问。
  5. Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

Java中synchronized和Lock的区别相关推荐

  1. 【Java】synchronized与lock的区别

    从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchronized来实现同步访问了 ...

  2. java中二进制怎么说_面试常用:说清楚Java中synchronized和volatile的区别

    回顾一下两个关键字:synchronized和volatile 1.Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized.v ...

  3. Java多线程之Synchronized和Lock的区别

    Java多线程之Synchronized和Lock的区别 目录: 原始构成 使用方法 等待是否可以中断 加锁是否公平 锁绑定多个条件Condition 小结:Lock相比较Synchronized的优 ...

  4. Java之多线程里面的锁理解以及synchronized与Lock的区别

    一.宏观的说下锁的分类 1)锁分为乐观锁.悲观锁 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改.因此对于同一个数据的并发操作,悲观锁采取加锁的形式.悲观的认为,不 ...

  5. 深入Synchronized和java.util.concurrent.locks.Lock的区别详解

    转载自  深入Synchronized和java.util.concurrent.locks.Lock的区别详解 本篇文章是对Synchronized和java.util.concurrent.loc ...

  6. Java中synchronized和volatile有什么区别?

    Java中synchronized和volatile有什么区别? 相关内容 synchronized的问题 什么叫做不完整对象,这个怎么理解呢? 总结 ) 相关内容 1.Java语言为了解决并发编程中 ...

  7. synchronized与Lock的区别与使用

    原文链接 https://blog.csdn.net/u012403290/article/details/64910926 ###引言: 昨天在学习别人分享的面试经验时,看到Lock的使用.想起自己 ...

  8. 题目:三个售票员 卖出 30张票 || 多线程编程的企业级套路+模板||synchronized与Lock的区别

    package com.dym.juc;//资源类 class Ticket {private int number =30;public synchronized void saleTicket() ...

  9. java中wait和sleep的区别

    文章目录 Wait和sleep的区别 唤醒wait和sleep java中wait和sleep的区别 在本篇文章中,我们将会讨论一下java中wait()和sleep()方法的区别.并讨论一下怎么使用 ...

最新文章

  1. unix odbc php 连接sqlserver,Ubuntu下通过unixODBC连接MS SqlServer2005
  2. php 根据ip 扫描端口,python实现指定ip端口扫描方式
  3. java中volatile
  4. ci/cd heroku_在Heroku上部署Dash或Flask Web应用程序。 简易CI / CD。
  5. origin如何绘制双y轴曲线_Origin用矩阵绘制多层曲面映射图
  6. 新闻发布项目——接口类(commentDao)
  7. php 获取文件最后几行,php获取文件的最后N行数据
  8. tc溜溜865手机投屏卡_溜溜tcgames老版本(电脑玩手机游戏)-溜溜TC Games32位/64位旧版本PC下载V2.0.0官网安卓真机投屏-西西软件下载...
  9. java微信demo_微信登陆 , 简单的demo , java
  10. 水滴石穿C语言之C语言的底层操作
  11. DDD:四色原型、DDD、DCI之间的关系
  12. 21-hadoop-weibo推送广告
  13. mysql 恢复root权限_Linux MySQL root权限恢复[图文]
  14. PGPool使用限制 1
  15. Linux基础命令操作
  16. STM32F10x 学习笔记4(CRC计算单元 续)
  17. IOS 文件目录的获取与操作
  18. 信捷plc用c语言编程视频,信捷PLC/触摸屏全套编程软件/学习教程视频资料 大全编程操作手册...
  19. java tic tac toe_确定Tic Tac Toe游戏的算法
  20. 78㎡网络机房动环监控及告警方案

热门文章

  1. 艾弗森、穆大叔遭“姚式幽默”调侃 全场爆笑
  2. SAP 财务模块 FI-TV 差旅管理
  3. 完全删除垃圾Q+!!!
  4. 教育硬件告别“不温不火”:有道首战告捷,教育、科技巨头虎视眈眈
  5. 在线音频“三国争霸”,谁能率先登陆资本市场?
  6. 剪映导出帧率选多少_剪映帧率怎么设置?剪映帧率设置方法
  7. python中unique函数_正在计算unique()的返回值
  8. vue点击改变data_vue实现响应式原理即vue如何监听data的每个属性的变化
  9. python字典类型实现的键值对的映射_python数据类型-映射和集合-字典
  10. xenserver 虚拟机扩容lvm磁盘分区的方法_从零开始学Linux运维|35.LVM(逻辑卷管理)的创建...