深入理解高并发编程(一)
文章目录
- 深入理解高并发编程(一)
- SimpleDateFormat线程安全问题
- 重现问题
- 线程不安全的原因
- 解决办法
- 局部变量
- synchronized锁
- Lock锁
- ThreadLocal
- DateTimeFormatter
- Thread源码解读
- Thread类定义
- 线程的状态定义
- run()方法
- start()方法
- sleep()方法
- join()方法
- interrupt()方法
- stop()方法
- stop()和interupt()区别
- ThreadLocal
- 使用示例
- ThreadLocal原理
- set()
- get()
- remove()
- ThreadLocal的传递性
- InheritableThreadLocal
- InheritableThreadLocal原理
SimpleDateFormat线程安全问题
重现问题
- 定义实例变量
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- 多线程的环境使用
simpleDateFormat
:下面的方法会重现线程安全问题
@Test
public void test() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
try {
String dateString = simpleDateFormat.format(new Date());
Date parse = simpleDateFormat.parse(dateString);
logger.info(simpleDateFormat.format(parse));
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}).start();
}
}
线程不安全的原因
Calendar establish(Calendar cal) {
// ...
cal.clear();
// Set the fields from the min stamp to the max stamp so that
// the field resolution works in the Calendar.
for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
for (int index = 0; index <= maxFieldIndex; index++) {
if (field[index] == stamp) {
cal.set(index, field[MAX_FIELD + index]);
break;
}
}
}
// ...
}
-
在
CalendarBuilder.establish()
方法中先后调用了cal.clear()
与cal.set()
,也就是先清除cal
对象中设置的值,再重新设置新的值。 -
由于
Calendar
内部并没有线程安全机制,并且这两个操作也都不是原子性的,所以当多个线程同时操作一个SimpleDateFormat
时就会引起cal
的值混乱。类似地,format()
方法也存在同样的问题 -
DateFormat
类中的Calendar
对象被多线程共享,而Calendar
对象本身不支持线程安全。
解决办法
- 注意下面的测试代码中为了方便直接使用
new Thread
创建线程,不符合创建线程的规范,请使用规范使用线程池
局部变量
- 将
SimpleDateFormat
类对象定义成局部变量:会创建大量的SimpleDateFormat
对象,影响程序性能
synchronized锁
SimpleDateFormat
类对象定义成全局静态变量,此时所有线程共享SimpleDateFormat
类对象,此时在调用格式化时间的方法时,对SimpleDateFormat
对象进行同步 ,加锁之后相当与串行执行失去了多线程的意义
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* <p> simpleDateFormat1 定义为类变量 对 simpleDateFormat1 在使用的时候同步处理 </p>
* <p> 同步处理之后相当与串行执行 </p>
*/
@Test
public void test1() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
try {
synchronized (simpleDateFormat) {
String dateString = simpleDateFormat.format(new Date());
Date parse = simpleDateFormat.parse(dateString);
logger.info(simpleDateFormat.format(parse));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}).start();
}
}
Lock锁
Lock
锁方式 :影响高并发场景下的性能
/**
* 类变量
*/
private static final SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* Lock对象
*/
private static Lock LOCK = new ReentrantLock();
/**
* <p> simpleDateFormat1 定义为类变量 对 simpleDateFormat1 在使用的时候同步处理 </p>
* <p> 使用 Lock 锁 </p>
*/
@Test
public void test2() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
LOCK.lock();
try {
String dateString = simpleDateFormat.format(new Date());
Date parse = simpleDateFormat.parse(dateString);
logger.info(simpleDateFormat.format(parse));
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
LOCK.unlock();
}
}).start();
}
}
ThreadLocal
- 使用
ThreadLocal
存储每个线程拥有的SimpleDateFormat
对象的副本,能够有效的避免多线程造成的线程安全问题
/**
* 保证线程安全
*/
private static final ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
/**
* 使用 ThreadLocal
*/
@Test
public void test3() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
try {
DateFormat dateFormat = DATE_FORMAT_THREAD_LOCAL.get();
String dateString = dateFormat.format(new Date());
Date parse = dateFormat.parse(dateString);
logger.info(dateFormat.format(parse));
} catch (Exception e) {
logger.error("Error Occur:{}", e.getMessage(), e);
}
}).start();
}
}
DateTimeFormatter
DateTimeFormatter
是Java8
提供的新的日期时间API
中的类,DateTimeFormatter
类是线程安全的,可以在高并发场景下直接使用
DateTimeFormatter
类来处理日期的格式化操作
/**
* DateTimeFormatter
*/
private static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 使用 DateTimeFormatter
*/
@Test
public void test4() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
try {
String dateString = "2023-10-25 12:00:00";
LocalDate localDate = LocalDate.parse(dateString, DATE_TIME_FORMATTER);
logger.info(localDate.toString());
} catch (Exception e) {
logger.error("Error Occur:{}", e.getMessage(), e);
}
}).start();
}
}
Thread源码解读
Thread
类的继承关系如图
Thread类定义
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
// 在静态代码块中调用注册本地系统资源的方法
registerNatives();
}
}
线程的状态定义
public enum State {
//初始化状态
NEW,
//可运行状态,此时的可运行包括运行中的状态和就绪状态
RUNNABLE,
//线程阻塞状态
BLOCKED,
//等待状态
WAITING,
//超时等待状态
TIMED_WAITING,
//线程终止状态
TERMINATED;
}
- 线程的生命周期,状态之间的转换图
- 初始:线程被构建没有调用
start
方法 - 可运行:运行中状态和就绪状态
- 阻塞状态:该状态的线程需要等到其他线程释放锁或者等待进入
synchronized
- 等待:处于该状态的线程需要等待其它线程对其进行通知或者中端操作
- 超时等待:在一定的时间自行返回
- 终止:线程执行完毕
run()方法
Thread
类实现Runnable
接口重写run()
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 要使用
run()
方法,需要继承Thread
类并重写run()
方法,将自定义的线程逻辑放在其中 - 直接调用
run()
方法并不会启动一个新线程,而是在当前线程中执行run()
方法的代码。要启动一个新线程,必须调用start()
方法。
start()方法
start()
方法使用synchronized
关键字修饰,说明start()
方法是同步的,它会在启动线程前检查线程的状态,如果不是初始化状态,则直接抛出异常
public synchronized void start() {
// 校验线程初始状态
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 添加当前启动的线程到线程组
group.add(this);
// 标记线程是否已经启动
boolean started = false;
try {
// 调用本地方法启动线程
start0();
// 将线程是否启动标记为true
started = true;
} finally {
try {
// 线程未启动成功
if (!started) {
// 将线程在线程组里标记为启动失败
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
- 调用
start()
方法后,新创建的线程就会处于就绪状态(如果没有分配到CPU
执行),当有空闲的CPU
时,这个线程就会被分配CPU
来执行,此时线程的状态为运行状态,JVM
会调用线程的run()
方法执行任务。
sleep()方法
-
调用
sleep()
方法使线程休眠后,线程不会释放相应的锁。 -
假设您想要一个线程暂停2秒(2000毫秒)。你可以使用
sleep()
方法实现如下
/**
* <p> sleep </p>
*
* @throws InterruptedException InterruptedException
*/
@Test
public void test2() throws InterruptedException {
Thread thread = new Thread(() -> {
try {
logger.info("Thread is sleeping...");
Thread.sleep(2000);
logger.info("Thread woke up!");
} catch (InterruptedException e) {
logger.error(e.getMessage());
}
});
thread.start();
Thread.sleep(8000);
logger.info("主线程结束");
}
join()方法
- 当在一个线程上调用
join()
方法时,调用线程将进入等待状态,直到被加入的线程完成 - 如果被加入的线程已经执行完成,调用线程将立即继续执行
- 如果被加入的线程仍在运行,调用线程将等待,直到它完成或达到指定的超时时间
- 一旦被加入的线程完成,调用线程将恢复执行
- 如果被加入的线程被中断或超过了超时时间,
join()
方法可能会抛出异常。因此,在使用join()
方法时,最好处理可能出现的异常情况。
public class ThreadJoinTest {
private static final Logger logger = LoggerFactory.getLogger(ThreadJoinTest.class);
/**
* 如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到 join() 方法了。
*
* @param args args
* @throws InterruptedException 中断异常
*/
public static void main(String[] args) throws InterruptedException {
Thread currentThread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
JoinThreadTest joinTestTread = new JoinThreadTest(currentThread);
Thread thread = new Thread(joinTestTread, "线程 " + i);
thread.start();
currentThread = thread;
}
Thread.sleep(5000);
logger.info("主线程执行结束");
}
private static class JoinThreadTest implements Runnable {
private final Thread thread;
private JoinThreadTest(Thread currentThread) {
thread = currentThread;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
logger.info("当前线程:{}", Thread.currentThread().getName());
}
}
}
interrupt()方法
interrupt()
方法是Java
中用来中断正在执行的线程的方法。- 它是在
Thread
类中定义的方法。当该方法在一个线程上被调用时,它会将该线程的中断状态设置为true
,从而导致任何阻塞的方法或sleep
方法抛出InterruptedException
异常。这样,线程可以优雅地退出执行或执行任何必要的清理任务
public void interrupt() {
// 检查调用interrupt()的线程是否不是当前线程
if (this != Thread.currentThread())
// 如果它不是当前线程,则检查调用者是否具有修改线程所需的访问权限。
checkAccess();
// 使用blockerLock进入一个同步块。该锁用于在中断被阻塞在特定对象上的线程时防止出现竞争条件。
synchronized (blockerLock) {
// 检查线程当前是否被阻塞在可中断对象(阻塞器)上
Interruptible b = blocker;
if (b != null) {
// 线程没有被可中断对象阻塞,它只需调用interrupt0()来设置线程的中断状态。
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
- 调用
interrupt()
不会强制终止线程。它只是设置线程的中断状态,允许它通过抛出InterruptedException
或使用thread .interrupted()
或isInterrupted()
方法检查中断状态来优雅地处理中断。
stop()方法
Java
中的stop()
方法是一个已弃用的方法,用于强制停止线程的执行。- 强烈建议不要使用这种方法,因为它可能导致不可预测的行为和潜在的资源泄漏。
- 建议使用像
interrupt()
这样的协作方法来优雅地停止线程。下面是一个如何使用interrupt()
方法来停止线程的示例:
/**
* <p> interrupt 方法使用 </p>
*
* @throws InterruptedException InterruptedException
*/
@Test
public void test1() throws InterruptedException {
Thread thread = new Thread(() -> {
Thread currentThread = Thread.currentThread();
while (true) {
if (currentThread.isInterrupted()) {
break;
}
logger.info("Current date:{}", System.currentTimeMillis());
}
});
// 启动线程
thread.start();
// 主线程等待 3 秒
Thread.sleep(1000);
// 线程中断
thread.interrupt();
// 判断线程是否中断
if (thread.isInterrupted()) {
logger.info("Thread was interrupted..");
}
Thread.sleep(3000);
logger.info("线程中断后3秒");
}
stop()和interupt()区别
stop()
方法是用于立即终止线程的执行,它会导致线程立即停止并且不会执行任何清理工作。这可能会导致线程在不安全的状态下停止,并且可能会破坏线程所持有的锁。因此,stop()
方法已被标记为过时的方法,不推荐使用。interrupt()
方法是用于中断线程的执行。当调用interrupt()
方法时,它会设置线程的中断状态为"中断",但实际上并不会立即停止线程。线程可以通过检查自身的中断状态来决定是否停止执行。通常,线程在执行过程中会周期性地检查中断状态,并根据需要进行适当的清理和终止操作。stop()
方法是直接终止线程的执行,可能会导致线程在不安全状态下停止。interrupt()
方法是设置线程的中断状态,并由线程自行决定是否停止执行。interrupt()
方法更加安全和可控,推荐使用。
ThreadLocal
使用示例
public class ThreadLocalTest {
private static final Logger logger = LoggerFactory.getLogger(ThreadLocalTest.class);
/**
* 当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
* 为了避免重复创建TSO(thread specific object,即与线程相关的变量) 使用 static final 修饰
*/
private static final ThreadLocal<String> THREAD_LOCAL_MAP = new ThreadLocal<>();
@Test
public void test1() throws InterruptedException {
ThreadPoolUtils.executor(new ThreadLocaleTask());
ThreadPoolUtils.executor(new ThreadLocaleTask());
Thread.sleep(5000);
}
private class ThreadLocaleTask implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
THREAD_LOCAL_MAP.set(name);
String str = THREAD_LOCAL_MAP.get();
logger.info("线程变量当中的值:{}", str);
}
}
}
ThreadLocal原理
Thread
类中threadLocals
和inheritableThreadLocals
属性
public class Thread implements Runnable {
// 只允许当前线程访问
ThreadLocal.ThreadLocalMap threadLocals = null;
// 允许子线程访问
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
set()
set
方法把数据放到当前线程的threadLocals
变量中
public void set(T value) {
// 获取调用 set 的线程
Thread t = Thread.currentThread();
// 获取 t 线程的 threadLocals 属性
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
get()
- 通过当前线程来获取
threadLocals
成员变量,如果threadLocals
成员变量不为空,则直接返回当前线程绑定的本地变量,否则调用setInitialValue()
方法初始化threadLocals
成员变量的值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
remove()
public void remove() {
// 根据当前线程获取threadLocals成员变量
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// threadLocals成员变量不为空,则移除value值
m.remove(this);
}
使用完
threadLocal
之后必须调用remove()
否则有可能会造成内存泄漏问题
ThreadLocal的传递性
- 无传递性:父线程的线程变量,子线程无法获得父线程
threadLocal
的值
InheritableThreadLocal
InheritableThreadLocal
类继承自ThreadLocal
类,它能够让子线程访问到在父线程中设置的本地变量的值
/**
* InheritableThreadLocal 子线程可以访问父线程的属性
*/
private static ThreadLocal<String> INHER_ITABLE_MAP = new InheritableThreadLocal<>();
/**
* InheritableThreadLocal 使用示例
*/
@Test
public void test2() {
// 在主线程中设置值
INHER_ITABLE_MAP.set("ThreadLocalTest");
// 在子线程中获取值
Thread thread = new Thread(() -> logger.info("子线程获取值:{}", INHER_ITABLE_MAP.get()));
// 启动子线程
thread.start();
// 在主线程中获取值
logger.info("主线程获取值:{}", INHER_ITABLE_MAP.get());
INHER_ITABLE_MAP.remove();
}
- 使用
InheritableThreadLocal
类存储本地变量时,子线程能够获取到父线程中设置的本地变量
InheritableThreadLocal原理
InheritableThreadLocal
类的childValue()
方法是何时被调用
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
// 判断传递的inheritThreadLocals变量是否为true
// 父线程中的inheritableThreadLocals不为null,则调用ThreadLocal类的createInheritedMap()方法。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
- 调用
createInheritedMap
:使用父线程的inheritableThreadLocals
变量作为参数创建新的ThreadLocalMap
对象。然后在Thread
类的init()
方法中会将这个ThreadLocalMap
对象赋值给子线程的inheritableThreadLocals
成员变量。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
ThreadLocalMap
构造方法如下:
- 在
ThreadLocalMap
的构造函数中,调用了InheritableThreadLocal
类重写的childValue()
方法。
- 而
InheritableThreadLocal
类通过重写getMap()
方法和createMap()
方法,让本地变量保存到了Thread
线程的inheritableThreadLocals
变量中,线程通过InheritableThreadLocal
类的set()
方法和get()
方法设置变量时,就会创建当前线程的inheritableThreadLocals
变量。 - 如果父线程创建子线程,在
Thread
类的构造函数中会把父线程中的inheritableThreadLocals
变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals
变量中。