最近有一个需求是需要将数据库的一些数据抽取出来放到文件文件命名方式为“FILENAME_yyyyMMddHHmmss”,例如FILENAME_20200625120011。计划使用多线程去实现,这样可能生成的文件名会有重复导致内容被覆盖,因此考虑加锁实现生成文件方式。这时候考虑到是使用synchronized还是Lock?
synchronized
是Java提供的一个并发控制的关键字。主要有两种用法,分别是同步方法
和同步代码块
。也就是说,synchronized既可以修饰方法也可以修饰代码块。
代码块被synchronized
修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待获取锁的线程释放锁,这里获取锁的线程释放锁只会有两种情况:
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
使用synchronized实现创建文件的synchronizedDemoThread线程代码如下:
public class synchronizedDemoThread implements Runnable {
public static ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<String, String>();
@Override
public 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是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();
@Override
public 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都能达到锁的目的,那有哪些不同的地方呢?