记录:271
场景:多线程并发访问同一个变量、方法、一段代码。对访问的变量、方法、一段代码,在它们的开始位置进行加锁,在它们结束位置进行解锁。并发的多线程,只有获得锁,才能访问。达到效果就是,对于加锁和解锁之间的代码,并发的多线程是串行逐个执行。
案例场景:
1.加锁场景
1.1>并发启动10个线程,同时访问restful服务端的f1方法。
1.2>f1会同时接到10个请求,并打印日志,接收请求时间戳秒级是一样的值。
1.3>单个线程处理任务时间为2秒,并把字符串时间写入List<String>中。
1.4>加锁后,10个任务串行获取锁并处理任务,总计会花费20秒。
1.5>f1返回客户端,并打印日志,返回的时间戳是每隔2秒打印一次。
2.不加锁场景
2.1>并发启动10个线程,同时访问restful服务端的f1方法。
2.2>f1会同时接到10个请求,并打印日志,接收请求时间戳秒级是一样的值。
2.3>单个线程处理任务时间为2秒,并把字符串时间写入List<String>中。
2.4>不加锁,10个任务并行处理任务,会花费2秒。
2.5>f1返回客户端,并打印日志,返回的时间戳秒级是一样的值。
3.服务端与客户端
服务端是基于springboot的web项目。
客户端普通的main函数启动。
一、基础
1.ReentrantLock
ReentrantLock,即java.util.concurrent.locks.ReentrantLock。实现Lock接口。
2.ReentrantReadWriteLock
ReentrantReadWriteLock,即java.util.concurrent.locks.ReentrantReadWriteLock。实现ReadWriteLock接口。
二、ReentrantLock
1.服务端MultiLockController
Java类MultiLockController,接收restful请求并处理。
@RestController
@RequestMapping("/multi")
@Slf4j
public class MultiLockController {
private final ReentrantLock putLock = new ReentrantLock();
@GetMapping("/f1/{input}")
public String f1(@PathVariable("input") String input) {
log.info("MultiController->f1,接收参数, input= " + input.toString());
final ReentrantLock lock = this.putLock;
lock.lock();
try {
String strTime = DateUtil.format(
new Date(), "yyyy-MM-dd HH:mm:ss");
CashUtils.addStrTime(strTime);
sleepTime(2000);
} finally {
lock.unlock();
}
log.info("MultiController->f1,返回.");
return "杭州";
}
@GetMapping("/f2")
public String f2() {
log.info("MultiController->f2,开始.");
final ReentrantLock lock = this.putLock;
lock.lock();
try {
CashUtils.printStrTime();
} finally {
lock.unlock();
}
log.info("MultiController->f2,结束.");
return "杭州";
}
public void sleepTime(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.服务端CashUtils
Java类CashUtils,读写变量。
@Slf4j
public class CashUtils {
private static final List<String> strTime = new ArrayList<>();
public static void addStrTime(String varTime) {
strTime.add(varTime);
}
public static void printStrTime() {
for (String time : strTime) {
log.info("取String型时间: " + time);
}
}
}
3.客户端线程池类MultiThreadPractice
Java类MultiThreadPractice,创建线程池和启动线程执行。
@Slf4j
public class MultiThreadPractice {
public MultiThreadPractice(){}
// 线程池
private ExecutorService threadPool;
// 线程池大小
private int poolSize = 0;
public boolean create(int poolSize) {
try {
this.poolSize = poolSize;
this.threadPool = Executors.newScheduledThreadPool(this.poolSize, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable);
}
});
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void run(){
for (int i = 0; i < this.poolSize; i++) {
TaskExecutor executor;
try {
executor = new TaskExecutor();
this.threadPool.execute(executor);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.客户端线程接口实现类TaskExecutor
Java类TaskExecutor,线程具体执行,即使用restful客户端调用服务端代码。
@Slf4j
public class TaskExecutor implements Runnable {
@Override
public void run() {
try {
String threadName = Thread.currentThread().getName();
log.info("线程:" + Thread.currentThread().getName() + ",开始.");
RestClientUtils.f1(threadName);
log.info("线程:" + Thread.currentThread().getName() + ",完成.");
} catch (Exception e) {
log.info("线程执行异常.");
e.printStackTrace();
}
}
}
5.客户端Restful客户端工具RestClientUtils
Java类RestClientUtils,调用服务端代码。
调用的url: http://127.0.0.1:18080/server/multi/f1/hangzhou
@Slf4j
public class RestClientUtils {
private static RestTemplate restTemplate = new RestTemplate();
public static void f1(String threadName){
String url = "http://127.0.0.1:18080/server/multi/f1/hangzhou";
try {
Object obj = restTemplate.getForObject(url,String.class);
if (obj != null ) {
log.info(threadName + ",返回结果: " + obj.toString());
} else {
log.info(threadName + ",返回结果: 为空");
}
} catch (Exception e) {
log.info(threadName + ",调用rest异常.");
e.printStackTrace();
}
}
}
6.客户端启动类main函数
Java类ThreadMain,调用服务端代码。
public class ThreadMain {
public static void main(String[] args) {
MultiThreadPractice multi = new MultiThreadPractice();
//创建10个线程
multi.create(10);
//线程执行
multi.run();
}
}
7.加锁验证
加锁,多线程并发10个请求同时到达,每隔2秒处理一个请求,每隔2秒逐个返回,20秒后全部返回完毕。
7.1 并发请求
请求URL:http://127.0.0.1:18080/server/multi/f1/hangzhou
日志输出:
7.2 打印写入数据
服务端存入数据,按照时间每隔2秒存入一条,可以调用打印方法确认。
请求URL: http://127.0.0.1:18080/server/multi/f2
日志输出:
8.不加锁验证
不加锁,多线程并发10个请求同时到达,同时处理,都在2秒后同时返回。
8.1 并发请求
请求URL:http://127.0.0.1:18080/server/multi/f1/hangzhou
日志输出:
8.2 打印写入数据
请求URL: http://127.0.0.1:18080/server/multi/f2
日志输出:
三、ReentrantReadWriteLock
1.服务端MultiReadWriteLockController
Java类MultiReadWriteLockController,接收restful请求并处理。
@RestController
@RequestMapping("/multiReadWrite")
@Slf4j
public class MultiReadWriteLockController {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
@GetMapping("/f1/{input}")
public String f1(@PathVariable("input") String input) {
log.info("MultiReadWriteLockController->f1,接收参数, input= " + input.toString());
lock.writeLock().lock();
try {
String strTime = DateUtil.format(
new Date(), "yyyy-MM-dd HH:mm:ss");
CashUtils.addStrTime(strTime);
sleepTime(2000);
} finally {
lock.writeLock().unlock();
}
log.info("MultiReadWriteLockController->f1,返回.");
return "杭州";
}
@GetMapping("/f2")
public String f2() {
log.info("MultiReadWriteLockController->f2,开始.");
lock.readLock().lock();
try {
CashUtils.printStrTime();
} finally {
lock.readLock().unlock();
}
log.info("MultiReadWriteLockController->f2,结束.");
return "杭州";
}
public void sleepTime(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.服务端CashUtils
Java类CashUtils,读写变量。
@Slf4j
public class CashUtils {
private static final List<String> strTime = new ArrayList<>();
public static void addStrTime(String varTime) {
strTime.add(varTime);
}
public static void printStrTime() {
for (String time : strTime) {
log.info("取String型时间: " + time);
}
}
}
3.客户端线程池类MultiThreadPractice
Java类MultiThreadPractice,创建线程池和启动线程执行。
@Slf4j
public class MultiThreadPractice {
public MultiThreadPractice(){}
// 线程池
private ExecutorService threadPool;
// 线程池大小
private int poolSize = 0;
public boolean create(int poolSize) {
try {
this.poolSize = poolSize;
this.threadPool = Executors.newScheduledThreadPool(this.poolSize, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable);
}
});
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void run(){
for (int i = 0; i < this.poolSize; i++) {
TaskExecutor executor;
try {
executor = new TaskExecutor();
this.threadPool.execute(executor);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.客户端线程接口实现类TaskExecutor
Java类TaskExecutor,线程具体执行,即使用restful客户端调用服务端代码。
@Slf4j
public class TaskExecutor implements Runnable {
@Override
public void run() {
try {
String threadName = Thread.currentThread().getName();
log.info("线程:" + Thread.currentThread().getName() + ",开始.");
RestClientUtils.f1(threadName);
log.info("线程:" + Thread.currentThread().getName() + ",完成.");
} catch (Exception e) {
log.info("线程执行异常.");
e.printStackTrace();
}
}
}
5.客户端Restful客户端工具RestClientUtils
Java类RestClientUtils,调用服务端代码。
调用的url: http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou
@Slf4j
public class RestClientUtils {
private static RestTemplate restTemplate = new RestTemplate();
public static void f1(String threadName){
String url = "http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou";
try {
Object obj = restTemplate.getForObject(url,String.class);
if (obj != null ) {
log.info(threadName + ",返回结果: " + obj.toString());
} else {
log.info(threadName + ",返回结果: 为空");
}
} catch (Exception e) {
log.info(threadName + ",调用rest异常.");
e.printStackTrace();
}
}
}
6.客户端启动类main函数
Java类ThreadMain,调用服务端代码。
public class ThreadMain {
public static void main(String[] args) {
MultiThreadPractice multi = new MultiThreadPractice();
//创建10个线程
multi.create(10);
//线程执行
multi.run();
}
}
7.加锁验证
加锁,多线程并发10个请求同时到达,每隔2秒处理一个请求,每隔2秒逐个返回,20秒后全部返回完毕。
7.1 并发请求
请求URL:http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou
日志输出:
7.2 打印写入数据
服务端存入数据,按照时间每隔2秒存入一条,可以调用打印方法确认。
请求URL: http://127.0.0.1:18080/server/multiReadWrite/f2
8.不加锁验证
不加锁,多线程并发10个请求同时到达,同时处理,都在2秒后同时返回。
8.1并发请求
请求URL:http://127.0.0.1:18080/server/multiReadWrite/f1/hangzhou
日志输出:
8.2打印写入数据
请求URL: http://127.0.0.1:18080/server/multiReadWrite/f2
输出日志:
以上,感谢。
2022年6月11日