Java中ExecutorService线程池的使用(Runnable和Callable多线程实现):
Java中ExecutorService线程池的使用(Runnable和Callable多线程实现)_霸道流氓气质的博客-CSDN博客
Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例:
Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例_霸道流氓气质的博客-CSDN博客
Java中使用CountDownLatch实现并发流程控制:
Java中使用CountDownLatch实现并发流程控制_countdownlatch 并发_霸道流氓气质的博客-CSDN博客
以下会用到如上概念。
Java开发手册中对于SimpleDateFormat的使用的要求是:
【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,
必须加锁,或者使用 DateUtils 工具类。
正例:
注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:
simple beautiful strong immutable thread-safe。
注:
博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
为了验证以上结论,首先需要了解下时区的概念。
时区是地球上的区域使用同一个时间定义。
以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。
1863 年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。
世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。
这些偏差就是所谓的时差。现今全球共分为 24 个时区。
由于实用上常常 1 个国家,或 1 个省份同时跨着 2个或更多时区,为了照顾到行政上的方便,常将 1 个国家或 1 个省份划在一起。
所以时区并不严格按南北直线来划分,而是按自然条件来划分。
例如,中国幅员宽广,差不多跨 5 个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。
由于不同的时区的时间是不一样的,甚至同一个国家的不同城市时间都可能不一样,所以,
在 Java 中想要获取时间的时候,要重点关注一下时区问题。
默认情况下,如果不指明,在创建日期的时候,会使用当前计算机所在的时区作为默认时区,
这也是为什么我们通过只要使用new Date()就可以获取中国的当前时间的原因。
Java中可以通过SimpleDateFormat实现获取不同时区的时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(sdf.format(Calendar.getInstance().getTime()));
以上输出纽约的时间比中国北京时间早了12个小时
新建测试类TestStaticSDF
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;
public class TestStaticSDF {
//定义全局SimpleDateFormat
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//使用guava的ThreadFactoryBuilder定义一个线程池
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
private static ExecutorService pool =
new ThreadPoolExecutor(5,
200,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
//定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
private static CountDownLatch countDownLatch = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
//定义一个线程安全的HashSet
Set dates = Collections.synchronizedSet(new HashSet());
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(()->{
//时间增加
calendar.add(Calendar.DATE,finalI);
String dateString = simpleDateFormat.format(calendar.getTime());
dates.add(dateString);
countDownLatch.countDown();
});
}
//堵塞,直到countDown数量为0
countDownLatch.await();
System.out.println(dates.size());//60
}
}
就是循环100次,每次循环的时候都在当前时间基础上增加一个天数,然后把所有日期放在一个线程安全的、带有去重功能的Set中,
然后输出set的元素个数。
这里要注意:
在Java中,Collections类提供了许多线程安全的方法来处理集合类,其中一个重要的方法就是synchronizedSet()。
这是一个可以将任何Set集合转换为线程安全的Set集合的方法。
实际结果却是小于100的值,原因就是因为 SimpleDateFormat 作为一个非线程安全的类,
被当做了共享变量在多个线程中进行使用,这就出现了线程安全问题。
查看format的源码,方法在执行过程中,会使用一个成员变量calendar 来保存时间。
由于我们在声明 SimpleDateFormat 的时候,使用的是 static 定义的。
那么这 个 SimpleDateFormat 就 是 一 个 共 享 变 量, 随 之,SimpleDateFormat 中 的calendar 也就可以被多个线程访问到。
假设线程 1 刚刚执行完calendar.setTime把时间设置成 2018-11-11,还没等执行完,线程 2 又执行了calendar.setTime把时间改成了 2018-12-12。
这时候线程 1 继续往下执行,拿到的calendar.getTime得到的时间就是线程 2 改过之后的。
除了 format 方法以外,SimpleDateFormat 的 parse 方法也有同样的问题。所以,不要把 SimpleDateFormat 作为一个共享变量使用。
public class TestStaticSDFSolve {
//使用guava的ThreadFactoryBuilder定义一个线程池
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
private static ExecutorService pool =
new ThreadPoolExecutor(5,
200,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
//定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
private static CountDownLatch countDownLatch = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
//定义一个线程安全的HashSet
Set dates = Collections.synchronizedSet(new HashSet());
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(()->{
//SimpleDateFormat 声明成局部变量
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//时间增加
calendar.add(Calendar.DATE,finalI);
String dateString = simpleDateFormat.format(calendar.getTime());
dates.add(dateString);
countDownLatch.countDown();
});
}
//堵塞,直到countDown数量为0
countDownLatch.await();
System.out.println(dates.size());//100
}
}
public class TestStaticSDFSolve2 {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//使用guava的ThreadFactoryBuilder定义一个线程池
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
private static ExecutorService pool =
new ThreadPoolExecutor(5,
200,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
//定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
private static CountDownLatch countDownLatch = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
//定义一个线程安全的HashSet
Set dates = Collections.synchronizedSet(new HashSet());
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(()->{
synchronized (simpleDateFormat){
//时间增加
calendar.add(Calendar.DATE,finalI);
String dateString = simpleDateFormat.format(calendar.getTime());
dates.add(dateString);
countDownLatch.countDown();
}
});
}
//堵塞,直到countDown数量为0
countDownLatch.await();
System.out.println(dates.size());//100
}
}
public class TestStaticSDFSolve3 {
private static ThreadLocal simpleDateFormatThreadLocal = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//使用guava的ThreadFactoryBuilder定义一个线程池
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
private static ExecutorService pool =
new ThreadPoolExecutor(5,
200,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
//定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
private static CountDownLatch countDownLatch = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
//定义一个线程安全的HashSet
Set dates = Collections.synchronizedSet(new HashSet());
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(()->{
//时间增加
calendar.add(Calendar.DATE,finalI);
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());
dates.add(dateString);
countDownLatch.countDown();
});
}
//堵塞,直到countDown数量为0
countDownLatch.await();
System.out.println(dates.size());//100
}
}
TestStaticSDFSolve4 {
private static DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//使用guava的ThreadFactoryBuilder定义一个线程池
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
private static ExecutorService pool =
new ThreadPoolExecutor(5,
200,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
//定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
private static CountDownLatch countDownLatch = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
//定义一个线程安全的HashSet
Set dates = Collections.synchronizedSet(new HashSet());
for (int i = 0; i < 100; i++) {
//获取当前时间
LocalDateTime now = LocalDateTime.now();
int finalI = i;
pool.execute(()->{
//时间增加
LocalDateTime localDateTime = now.plusDays(finalI);
String dateString = localDateTime.format(format);
dates.add(dateString);
countDownLatch.countDown();
});
}
//堵塞,直到countDown数量为0
countDownLatch.await();
System.out.println(dates.size());//100
}
}