文章目录
1.实现时间格式化
实现1000个日期的时间格式化
package threadpool_5_26;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo04 {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
10,60, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));
for (int i = 1; i < 1001; i++) {
final int k = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
Date date = new Date(k * 1000);
myFormatTime(date);
}
});
}
}
public static void myFormatTime(Date date){
String ret = simpleDateFormat.format(date);
System.out.println("时间:"+ret);
}
}
运行结果:
结果分析:
SimpleDateFormat是线程不安全的
源码如下:
分析:
- 线程 1 执⾏了 calendar.setTime(date) ⽅法,将⽤户输⼊的时间转换成了后⾯格式化时所需要的时 间;
- 线程 1 暂停执⾏,线程 2 得到 CPU 时间⽚开始执⾏;
- 线程 2 执⾏了 calendar.setTime(date) ⽅法,对时间进⾏了修改;
- 线程 2 暂停执⾏,线程 1 得出 CPU 时间⽚继续执⾏,因为线程 1 和线程 2 使⽤的是同⼀对象,⽽时 间已经被线程 2 修改了,所以此时当线程 1 继续执⾏的时候就会出现线程安全的问题了。
正常情况下:
非线程安全情况下:
2.解决线程不安全
2.1加锁机制(synchronized,lock)
缺点:
需要加锁排队执行,消耗时间较长
2.2使用局部变量
缺点:
每次执行任务都需要重新创建私有变量
2.3使用ThreadLocal
以1000个任务10个线程的示例来说,使用ThreadLocal就是创建10个SimpleDateFormat对象
3.ThreadLocal的基本使用
3.1五个方法
- get():返回此线程局部变量的当前线程副本中的值。
- set(T value): 将此线程局部变量的当前线程副本中的值设置为指定值。
- remove():移除此线程局部变量当前线程的值。
- initialValue():初始化方法,实例方法
- withInitial:初始化方法,静态方法
3.2get,set,remove
package threadpool_5_27;
import java.util.concurrent.ThreadFactory;
public class Demo01 {
private static ThreadLocal<String> threadLcal = new ThreadLocal<>();
public static void main(String[] args) {
//定义公共的任务
Runnable runnable = new Runnable() {
@Override
public void run() {
String tname = Thread.currentThread().getName();
System.out.println(tname +" 设置值:" +tname);
try {
threadLcal.set(tname);
//执行ThreadLocal打印
printTreadLocal();
}finally {
threadLcal.remove();
}
}
};
Thread t1 = new Thread(runnable,"线程1");
t1.start();
Thread t2 = new Thread(runnable,"线程2");
t2.start();
}
private static void printTreadLocal() {
String ret = threadLcal.get();
System.out.println(Thread.currentThread().getName()+" 中取值"+ret);
}
}
运行结果:
3.3initialValue方法
package threadpool_5_27;
import java.util.Random;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo03 {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal(){
@Override
protected Integer initialValue() {
int num = new Random().nextInt(10);
System.out.println("执行initialValue方法: "+num);
return num;
}
};
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
1,60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000));
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
int result = threadLocal.get();
System.out.println(Thread.currentThread().getName()+" 得到结果1:"+result);
}
});
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
int result = threadLocal.get();
System.out.println(Thread.currentThread().getName()+" 得到结果2:"+result);
}
});
}
}
运行结果:
3.4withInitial方法:
package threadpool_5_28;
import java.util.function.Supplier;
public class Demo01 {
//创建并初始化ThreadLocal
private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
System.out.println("执行了withInitial方法!");
return Thread.currentThread().getName() + ": Java";
}
});
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String ret = threadLocal.get();
System.out.println(ret);
}
},"线程1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String ret = threadLocal.get();
System.out.println(ret);
}
});
t2.start();
}
}
面试题:ThreadLocal的初始化方法在什么情况下不执行?
答:ThreadLocal在执行get方法的时候,才会去判断并调用初始化方法
3.5使用ThreadLocal来实现1000个日期的时间格式化
代码示例:
package threadpool_5_28;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo05 {
//创建并初始化ThreadLocal
private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal(){
@Override
protected SimpleDateFormat initialValue() {
System.out.println("执行了initial方法");
return new SimpleDateFormat("mm:ss");
}
};
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
10,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));
for (int i = 1; i < 1001; i++) {
int k = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
Date date = new Date(1000*k);
myDateTime(date);
}
});
}
}
public static void myDateTime(Date date){
String ret = threadLocal.get().format(date);
System.out.println("时间:"+ret);
}
}
运行结果:
ThreadLocal的使用场景:
1.解决线程安全问题
2.实现线程级别的数据传递(可以实现一定程度的解耦)
4.ThreadLocal缺点
4.1不可继承性
主线程设置了值,子线程访问不了(子线程不能继承主线程的私有变量)
代码示例:
package threadpool_5_29;
public class ThreadLocalDemo01 {
//创建threadlocal
private static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
threadLocal.set("Java");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());
}
});
t1.start();
}
}
运行结果:
Thread-0 null
解决方案:
使用InheritableThreadLocal这个类
代码示例:
package threadpool_5_29;
public class ThreadLocalDemo02 {
private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
public static void main(String[] args) {
inheritableThreadLocal.set("Java");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" "+inheritableThreadLocal.get());
}
});
t1.start();
}
}
运行结果:
Thread-0 Java
注意事项:
即使使用InheritableThreadLocal也不能实现并列线程之间的数据传输(数据设置和获取)
4.2脏读
脏读定义:
在一个线程中读取到了不属于自己的信息
原因:
线程池复用了线程,和这个线程相关的静态属性也复用,所以导致了脏读
代码示例:
package threadpool_5_29;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo05 {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
static class MyThreadLocal extends Thread{
private static boolean flag = false;
@Override
public void run() {
String name = this.getName();
if(!flag){
threadLocal.set(name);
System.out.println(name+" 设置了 "+name);
flag = true;
}
System.out.println(name+" 得到了 "+threadLocal.get());
}
}
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
1,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(100));
for (int i = 0; i < 2; i++) {
threadPoolExecutor.submit(new MyThreadLocal());
}
}
}
运行结果:
解决方案:
1.避免使用静态变量
2.使用完之后,执行remove操作
4.3内存溢出
什么是内存溢出:
当一个线程使用完资源之后,没有释放资源,或者说释放资源不及时就是内存溢出
代码示例:
package threadpool_5_29;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo06 {
private static ThreadLocal<MyThreadLocal> threadLocalThreadLocal = new ThreadLocal<>();
static class MyThreadLocal{
private byte [] bytes = new byte[1*1024*1024];
}
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,
0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
MyThreadLocal myThreadLocal = new MyThreadLocal();
threadLocalThreadLocal.set(myThreadLocal);
System.out.println(Thread.currentThread().getName()+" 设置了值");
}
});
}
}
}
运行结果:
内存溢出的原因:
1.线程池是长生命周期
2.Thread—>ThreadLocalMap—>Entry key ,value(1mb资源)(垃圾回收器就不会回收value资源)
垃圾回收可回收的资源相关的4种类型:
1.强引用:Object object = new Object(),即使发生OOM也不会进行垃圾回收
2.软引用:它的引用关系仅次于强引用。如果内存够用,那么垃圾回收器不会考虑回收此引用,将要发生此OOM的时候才会回收此引用
3.弱引用:不管内存够不够用,下一次回收都会将此引用的对象回收掉
4.虚引用:创建即回收,它可以触发一个垃圾回收的回调
为什么ThreadLocal会将key设置为弱引用?
答:为了最大程度的避免OOM
内存溢出解决方法:
调用remove方法
package threadpool_5_29;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo08 {
private static ThreadLocal<MyThreadLocal> threadLocalThreadLocal = new ThreadLocal<>();
static class MyThreadLocal{
private byte [] bytes = new byte[1*1024*1024];
}
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,
0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try{
MyThreadLocal myThreadLocal = new MyThreadLocal();
threadLocalThreadLocal.set(myThreadLocal);
System.out.println(Thread.currentThread().getName()+" 设置了值");
}finally {
//移除变量,防止OOM
threadLocalThreadLocal.remove();
}
}
});
}
}
}
运行结果:
5.链表与红黑树
HashMap与ThreadLocalMap解决冲突:
HashMap:链表法(链表+红黑树)
ThreadLocalMap:开放寻址法
链表升级为红黑树的条件:
(1)链表长度大于8
(2)数组长度大于64
红黑树降级为链表:
链表长度小于6
为什么从链表升级为红黑树
加速查询性能