文章目录
多线程的使用
使用线程池ExecutorService
,exe.execute
来开始线程,thread,runabble,callabble
都可以,isTerminated
来判断线程池的线程是否都执行完毕
@Test
public void testThread() throws InterruptedException {
//创建线程池
ExecutorService exe = Executors.newFixedThreadPool(50);
for (int i = 1; i <= 1000; i++) {
//执行线程
exe.execute(new Runnable() {
@Override
public void run() {
System.out.println("1-->"+System.currentTimeMillis());
}
});
//执行线程
exe.execute(new Runnable() {
@Override
public void run() {
System.out.println("2-->"+System.currentTimeMillis());
}
});
//执行线程
exe.execute(new Runnable() {
@Override
public void run() {
System.out.println("3-->"+System.currentTimeMillis());
}
});
//执行线程
exe.execute(new Runnable() {
@Override
public void run() {
System.out.println("4-->"+System.currentTimeMillis());
}
});
}
//关闭线程池
exe.shutdown();
while (true) {
//判断线程是否都执行完毕
if (exe.isTerminated()) {
System.out.println("结束了!");
break;
}
//睡眠一会再检查是否执行完毕
Thread.sleep(200);
}
}
使用场景:生成报告,要填充数据、表格、图表,但是大数据量生成报告时间太久。
优化方案:使用多线程同时进行执行获取数据的sql和使用代码生成表格、生成图表、插入数据,然后判断线程都执行完毕后,再执行整合插入的方法,来缩短报告生成时间
ThreadLocal和InheritableThreadLocal
ThreadLocal
为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,但是存在一个问题
,就是子线程获取该值的时候为Null
,可以用InheritableThreadLocal代替ThreadLocal
,依旧会为每个线程提供一个独立的副本,而不会共享
- ThreadLocal
@Test
public void test2() throws InterruptedException {
ThreadLocal num=new ThreadLocal();
num.set(100);
new Thread(()->{
System.out.println(num.get());
}).start();
Thread.sleep(1000);
System.out.println("完成");
}
可以看到子线程获取的值为空
2. InheritableThreadLocal
@Test
public void test2() throws InterruptedException {
//ThreadLocal变量无法传到子线程中使用,InheritableThreadLocal就是为了解决这个问题
InheritableThreadLocal num=new InheritableThreadLocal();
num.set(100);
new Thread(()->{
System.out.println("thread-before:"+num.get());
num.set(200);
System.out.println("thread-after:"+num.get());
}).start();
Thread.sleep(1000);
System.out.println("main:"+num.get());
System.out.println("完成");
}
正常取到值
ThreadLocal
ThreadLocal引起的内存泄漏
ThreadLocal涉及到静态内部类ThreadLocalMap,ThreadLocalMap的键是弱引用,而值是强引用,垃圾回收的时候,会将键回收,导致值没法被回收,所以一直存在,如果要解决ThreadLocal引起的内存泄漏,就要使用完以后,手动进行remove()。
ThreadLocalMap为什么键是弱引用,而值是强引用
通过 ThreadLocal 实例自身作为键,结合每个线程内部的 ThreadLocalMap 来实现线程局部变量的存储和访问。
线程局部变量的值通常是需要被长时间使用的对象,如用户数据、数据库连接等。如果这些值是弱引用,它们可能会在GC时被回收,从而影响程序的正常运作。
多个线程都使用ThreadLocal,岂不是每个线程都用的是同一个ThreadLocal对象,那怎么实现变量副本
ThreadLocal中通过Thread.currentThread()获取当前正在执行的线程的引用,当多个线程访问同一个ThreadLocal对象时,它们实际上是在访问各自线程不同的内存空间
如果手动实现一个类似ThreadLocal,怎么实现
要自己实现类似ThreadLocal的功能,可以通过以下步骤来实现:
-
创建线程局部变量:首先,需要创建一个类来表示线程局部变量。这个类可以包含一个私有的Map对象,用于存储每个线程对应的值。可以使用ThreadLocal作为键,将线程ID与线程局部变量的值关联起来。
-
初始化线程局部变量:在类的构造函数中,可以初始化线程局部变量。可以将当前线程的ID作为键,将初始值作为值存储到Map中。
-
获取和设置线程局部变量:为了方便地获取和设置线程局部变量的值,可以在类中提供相应的方法。例如,可以提供一个get()方法来获取当前线程的局部变量值,以及一个set()方法来设置当前线程的局部变量值。
-
清理线程局部变量:为了避免内存泄漏,需要在不再需要线程局部变量时进行清理。可以在类中提供一个remove()方法,用于清除当前线程的局部变量值。
下面是一个简单的示例代码,演示了如何实现类似于ThreadLocal的功能:
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal<T> {
private Map<Long, T> threadLocalMap = new HashMap<>();
public MyThreadLocal(T initialValue) {
long threadId = Thread.currentThread().getId();
threadLocalMap.put(threadId, initialValue);
}
public T get() {
long threadId = Thread.currentThread().getId();
return threadLocalMap.get(threadId);
}
public void set(T value) {
long threadId = Thread.currentThread().getId();
threadLocalMap.put(threadId, value);
}
public void remove() {
long threadId = Thread.currentThread().getId();
threadLocalMap.remove(threadId);
}
}
上述代码中,MyThreadLocal类使用一个Map来存储每个线程对应的值。通过调用get()、set()和remove()方法,可以方便地获取、设置和清理线程局部变量的值。
需要注意的是,这只是一个简单的示例,实际使用时可能需要根据具体需求进行扩展和优化。此外,还需要考虑线程安全性和性能等因素,以确保实现的正确性和高效性。
简单说就是一个Map,key为对应的线程id,然后存对应的值,每个线程操作的话,都是通过自己线程id来操作的,这就实现了类似ThreadLocal,每个线程都是自己的变量