这里写目录标题
线程安全问题
场景:实现1000任务的时间格式化
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) {
// 定义线程池
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
for (int i = 1; i < 1001; i++) {
final int finalI = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
Date date = new Date(finalI * 1000);
myFormatTime(date);
}
});
}
}
private static void myFormatTime(Date date) {
String result = simpleDateFormat.format(date);
System.out.println("时间:" + result);
}
结果:
原因:
异常情况:
解决方式修改部分代码:
1.加锁( synchronized )
private synchronized static void myFormatTime(Date date) {
String result = simpleDateFormat.format(date);
System.out.println("时间:" + result);
}
2.私有变量
private static void myFormatTime(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
String result = simpleDateFormat.format(date);
System.out.println("时间:" + result);
}
3.ThreadLocal 一种方案既可以避免加锁排队执行,又不会每次执行任务都需要重新创建私有变量的方法
ThreadLocal线程的本地变量,每个线程创建一个私有变量,以1000个任务10个线程池的示例来说,使用ThreadLocal就是创建10 SimpleDateFormat对象。
基本使用
// 创建 ThreadLocal
private static ThreadLocal<String> threadLocal =
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 {
// set ThreadLocal
threadLocal.set(tname);
// 执行 ThreadLocal 打印
printThreaLocal();
} finally {
// 移除 ThreadLocal
threadLocal.remove();
}
}
};
Thread t1 = new Thread(runnable, "线程1");
t1.start();
Thread t2 = new Thread(runnable, "线程2");
t2.start();
}
private static void printThreaLocal() {
// 从 ThreadLocal 中获取值
String result = threadLocal.get();
System.out.println(Thread.currentThread().getName() +
" 中取值:" + result);
}
初始化使用
// 创建和初始化 ThreadLocal
private static ThreadLocal<SimpleDateFormat> threadLocal =
new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {//返回值与该类泛型化一致
System.out.println("执行 InitialValue 方法");
return new SimpleDateFormat("mm:ss");
}
};
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Date date = new Date(1000);
// 从 ThreadLocal 获取 DateFormat 对象,并格式化时间
String result = threadLocal.get().format(date);
System.out.println("线程1 时间格式化:" + result);
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Date date = new Date(2000);
// 从 ThreadLocal 获取 DateFormat 对象,并格式化时间
String result = threadLocal.get().format(date);
System.out.println("线程2 时间格式化:" + result);
}
});
t2.start();
}
线程池使用
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 executor = new ThreadPoolExecutor(
1, 1,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
executor.submit(new Runnable() {
@Override
public void run() {
// get ThreadLocal
int result = threadLocal.get();
System.out.println(Thread.currentThread().getName() +
" 得到结果1:" + result);
}
});
executor.submit(new Runnable() {
@Override
public void run() {
// get ThreadLocal
int result = threadLocal.get();
System.out.println(Thread.currentThread().getName() +
" 得到结果2:" + result);
}
});
}
另一种用法ThreadLocal.withInitial
// 创建并初始化 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) {
Runnable runnable = new Runnable() {
@Override
public void run() {
// 获取 ThreadLoal
String result = threadLocal.get();
System.out.println(Thread.currentThread().getName() +
" 获取到的内容:" + result);
}
};
Thread t1 = new Thread(runnable, "线程1");
t1.start();
Thread t2 = new Thread(runnable, "线程2");
t2.start();
}
简洁版:
private static ThreadLocal<String> threadLocal =
ThreadLocal.withInitial(() -> "Java");
当ThreadLocal中出现set 方法之后,所有类型的初始化方法就不会执行了。
原因:ThreadLocal在执行get方法的时候,才去判断并调用初始化方法。
当调用ThreadLocal.get()方法时
ThreadLocal的使用场景
private static ThreadLocal<User> userThreadLocal
= new ThreadLocal();
/**
* 实体类
*/
static class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
Storage storage = new Storage();
Storage2 storage2 = new Storage2();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 用户登录
User user = new User();
user.setName("比特");
userThreadLocal.set(user);
// 打印用户的信息
storage.printUserName3();
storage2.printUserName3();
}
});
t1.start();
}
class UserStrorage {
public User getUser() {
// 登录
User user = new User();
user.setName("比特");
return user;
}
}
/**
* 仓储类
*/
static class Storage {
// public void printUserName(User user) {
// System.out.println(user.getName());
// }
//
// public void printUserName2() {
// System.out.println(new UserStrorage()
// .getUser().getName());
// }
public void printUserName3() {
User user = userThreadLocal.get();
System.out.println("用户名" + user.getName());
}
}
/**
* 类 2
*/
static class Storage2 {
public void printUserName3() {
User user = userThreadLocal.get();
System.out.println("用户名" + user.getName());
}
}
ThreadLocal的问题
不可继承性
在子进程里访问不到父进程的变量
解决方案
使用ThreadLocal的子类
// 创建 ThreadLocal
private static ThreadLocal threadLocal =
new InheritableThreadLocal();
public static void main(String[] args) {
// 在主线程里面设置值
threadLocal.set("Java");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("获取值:" +
threadLocal.get());
}
});
t1.start();
}
脏读问题
在一个线程中读取到了不属于自己的信息就叫做脏读
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1,
0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 2; i++) {
executor.submit(new MyThreadLocal());
}
// MyThreadLocal t1 = new MyThreadLocal();
// t1.start();
//
// MyThreadLocal t2 = new MyThreadLocal();
// t2.start();
}
static class MyThreadLocal extends Thread {
private static boolean flag = false;
@Override
public void run() {
String tname = this.getName();
// String tname = Thread.currentThread().getName();
if (!flag) {
// 第一次执行
threadLocal.set(tname);
System.out.println(tname + " 设置了:" + tname);
flag = true;
}
System.out.println(tname + "得到了:" + threadLocal.get());
}
}
结果:
脏读产生的原因:
线程池复用了线程,和这个线程相关的静态属性也复用,所以就导致了脏读。
解决方案:
a)避免使用静态变量。
b)使用完之后,执行remove操作。(threadLocal.remove();)
内存溢出
当一个线程使用完资源之后,没有释放资源,或者说释放资源不及时就是内存溢出。(使用线程池容易出现该情况)
内存溢出的原因:
1.线程池是长声明周期。
垃圾回收器就不会回收
2.Thread -> ThreadLocalMap ->Entry key, value(1mb资源)
value资源。
ThreadLocal 会将key设置为弱引用是因为Threadlocal 为了更大程度的避免OOM
解决方案
// 创建 ThreadLocal
private static ThreadLocal<MyThreadLocal> threadLocal =
new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 10, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000)
);
for (int i = 0; i < 10; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
MyThreadLocal myThreadLocal = new MyThreadLocal();
threadLocal.set(myThreadLocal);
System.out.println(Thread.currentThread().getName() +
" 设置了值。");
} finally {
// 溢出变量(防止OOM)
threadLocal.remove();
}
}
});
Thread.sleep(200);
}
}
// 创建大对象
static class MyThreadLocal {
private byte[] bytes = new byte[1 * 1024 * 1024];
}
ThreadLocal 和普通的Map解决哈希冲突
单例模式
线程通讯(自定义阻塞队列)
static class MyBlockingQueue {
private int[] values; // 存放数据的数组
private int first; // 队首的下标
private int last; // 队尾的下标
private int size; // 实际队列大小
/**
* 构造方法
* @param maxSize 最大容量
*/
public MyBlockingQueue(int maxSize) {
// 初始化队列
values = new int[maxSize];
first = 0;
last = 0;
size = 0;
}
/**
* 添加元素(将元素添加到队尾)
* @param val
*/
public void offer(int val) throws InterruptedException {
synchronized (this) {
// 判断容量是否达到最大值
if (size == values.length) {
// 阻塞等待,消费者先消费
this.wait();
}
values[last++] = val;
size++;
// 判断是否是最后一个元素
if (last == values.length) {
// 循环队列
last = 0;
}
// 唤醒消费者取队列中的信息
this.notify();
}
}
/**
* 取元素(队首元素)
* @return
*/
public int poll() throws InterruptedException {
int result = 0;
synchronized (this) {
// 判断队列是否有元素
if (size == 0) {
// 阻塞等待
this.wait();
}
result = values[first++];
size--;
// 判断 first 是否是最后一个元素
if (first == values.length) {
first = 0;
}
// 唤醒生产者生产数据
this.notify();
}
return result;
}
}
public static void main(String[] args) {
MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);
// 生产者
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 生产数据(添加数据)
while (true) {
int num = new Random().nextInt(10);
System.out.println("生产数据:" + num);
try {
// 生产数据
myBlockingQueue.offer(num);
// 休息 500 ms
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
// 消费者
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
int result = myBlockingQueue.poll();
System.out.println("消费数据:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start();
}