- 我的技术博客:https://nezha.github.io,https://nezhaxiaozi.coding.me
- 我的简书地址:https://www.jianshu.com/u/a5153fbb0434
本文的代码地址:GitHub ThreadLocal Demo
1. UserService
package com.nezha.learn.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
public class UserService {
private static final ThreadLocal<Long> numbers = new ThreadLocal<>();
private Long normal = new Long(1);
private ReentrantLock reentrantLock = new ReentrantLock();
/**
* 模拟高并发场景下的请求,观察ThreadLocal是否线程安全
* @param number
*/
public void getNumberSyncRest(int number) {
Long id = Thread.currentThread().getId();
log.info(">>>>>Thread ID:{},input number:{}", id, number);
numbers.set(id);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
log.info(">>>>>Thread ID:{},ThreadLocal content:{},input number:{}", id, numbers.get(), number);
}
/**
* 观察高并发场景下,普通共享变量是否线程安全
* @param number
*/
public void getNumberSyncRestNormal(int number) {
Long id = Thread.currentThread().getId();
log.info(">>>>>Thread ID:{},input number:{}", id, number);
normal = id;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
log.info(">>>>>Thread ID:{},Normal content:{},input number:{}", id, normal, number);
}
/**
* 异步状态下测试ThreadLocal和普通变量的线程安全情况
*/
@Async
public void getNumberAsync() {
Long id = Thread.currentThread().getId();
log.info(">>>>Thread ID:{}", id);
numbers.set(id);
normal = id;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
log.info(">>>>Thread ID:{},ThreadLocal content:{},normal:{}", id, numbers.get(), normal);
}
/**
* 测试可重入锁的并发安全性
*/
@Async
public void getNumberAsyncReeLock() {
reentrantLock.lock();
Long id = Thread.currentThread().getId();
log.info(">>>>>Thread ID:{}", id);
numbers.set(id);
normal = id;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
log.info(">>>>>Thread ID:{},ThreadLocal content:{},normal:{}", id, numbers.get(), normal);
reentrantLock.unlock();
}
/**
* 测试synchronized锁的线程安全性
*/
@Async
public void getNumberAsyncSyncLock() {
synchronized (normal){
Long id = Thread.currentThread().getId();
log.info(">>>>>Thread ID:{}", id);
numbers.set(id);
normal = id;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
log.info(">>>>>Thread ID:{},ThreadLocal content:{},normal:{}", id, numbers.get(), normal);
}
}
}
2. UserApi
package com.nezha.learn.demo.controller;
import com.nezha.learn.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserApi {
@Autowired
private UserService userService;
@GetMapping(value="/get/{id}")
public int printNumber(@PathVariable int id) {
// url的id可通过@PathVariable绑定到参数中
userService.getNumberSyncRest(id);
return 200;
}
@GetMapping(value="/getNorm/{id}")
public int printNormalNumber(@PathVariable int id) {
// url的id可通过@PathVariable绑定到参数中
userService.getNumberSyncRestNormal(id);
return 200;
}
}
3. 测试类 UserServiceTest
package com.nezha.learn.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.databene.contiperf.PerfTest;
import org.databene.contiperf.junit.ContiPerfRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.Random;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@EnableAsync
@AutoConfigureMockMvc
public class UserServiceTest {
@Autowired
private UserService userService;
@Autowired
private MockMvc mockMvc;
private Random random = new Random();
@Rule
public ContiPerfRule contiPerfRule = new ContiPerfRule();
@Test
public void getNumber() throws InterruptedException {
for (int i = 0; i < 10; i++) {
userService.getNumberAsync();
log.info("第{}个任务", i);
}
Thread.sleep(10000);
}
@Test
public void getNumberAsyncSyncLock() throws InterruptedException {
for (int i = 0; i < 10; i++) {
userService.getNumberAsyncSyncLock();
log.info("第{}个任务", i);
}
Thread.sleep(10000);
}
@Test
public void getNumberAsyncReeLock() throws InterruptedException {
for (int i = 0; i < 10; i++) {
userService.getNumberAsyncReeLock();
log.info("第{}个任务", i);
}
Thread.sleep(10000);
}
@Test
//10个线程 执行10次
@PerfTest(invocations = 10, threads = 10)
public void getNumberSyncRest() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/get/" + random.nextInt(20)));
}
@Test
//10个线程 执行10次
@PerfTest(invocations = 10, threads = 10)
public void getNumberSyncRestNormal() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/getNorm/" + random.nextInt(20)));
}
}
其中测试类中多线程,并发量的测试我用的工具是:
contiperf
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>