多线程
Java原创Java多线程synchronized大约 5 分钟约 1495 字
1. synchronized线程锁
1.1. 使用
class data{
private Integer d=0;
public void d1(){
synchronized (d) {
d=1;
System.out.println(d);
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void d2() {
d=2;
System.out.println(d);
}
}
public class demo5 {
public static void main(String[] args) {
data d=new data();
new Thread(()->{
d.d1();
},"a").start();
new Thread(()->{
d.d2();
},"b").start();
}
}
1.2. 作用域
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象
public class TestDemo{
public void methond() {
// 进入代码块会锁 this 指向对象中的锁;
// 出代码块会释放 this 指向的对象中的锁
synchronized (this) {
}
}
public static void main(String[] args) {
TestDemo demo = new TestDemo();
demo.methond();
}
}
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
public class TestDemo{
public synchronized void methond() {
}
public static void main(String[] args) {
TestDemo demo = new TestDemo();
demo.methond();
// 进入方法会锁 demo 指向对象中的锁;
// 出方法会释放 demo 指向的对象中的锁
}
}
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
public class TestDemo{
public synchronized static void methond() {
}
public static void main(String[] args) {
methond();
// 进入方法会锁 TestDemo.class 指向对象中的锁;
//出方法会释放 TestDemo.class 指向的对象中的锁
}
}
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象
public class TestDemo{
public static void methond() {
// 进入代码块会锁 TestDemo.class 指向对象中的锁;
//出代码块会释放 TestDemo.class 指向的对象中的锁
synchronized (TestDemo.class) {
}
}
public static void main(String[] args) {
TestDemo demo = new TestDemo();
demo.methond();
}
}
1.3. 总结
- 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;
- 如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
- 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
- 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
1.4. 拓展
public class TestDemo {
static class Counter{
public int count = 0;
public void add(){
synchronized (this){
count++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
synchronized (counter){
counter.add();
}
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
synchronized (counter){
counter.add();
}
}
}
};
//启动两个线程
t1.start();
t2.start();
//等待两个线程结束
t1.join();
t2.join();
System.out.println(counter.count);
}
}
此时可以看出上述代码,加了两次锁,会发生什么呢?
但是运行代码发现程序依然正确运行?? 为什么
但是上述分析死锁的思路是对的
只是因为synchronized内部使用特殊手段来处理了这种情况 。
这样的操作特性我们叫做 可重入锁
synchronized 内部记录了当前这把锁是哪个线程持有的~
如果当前加锁线程和持有锁的线程是同一个线程~
此时就并不是真的进行“加锁操作”,而是把一个计数器加一;
如果后续该线程继续尝试获取锁,继续判定加锁线程和持有锁线程是不是同一个线程,只要是同一个线程,就不真的加锁,而是计数器+1;
如果该线程调用解锁操作,也不是立刻就解锁,而是计数器减1
直到计数器减成0了,才认为真的要“释放锁”,才允许其他线程来获取锁~~
2. EnableAsync
2.1. 配置线程池
application-dev.properties添加线程池配置信息 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
# 异步线程配置
# 配置核心线程数
async.executor.thread.core_pool_size = 30
# 配置最大线程数
async.executor.thread.max_pool_size = 30
# 配置队列大小
async.executor.thread.queue_capacity = 99988
# 配置线程池中的线程的名称前缀
async.executor.thread.name.prefix = async-importDB-
2.2. spring容器注入线程池bean对象
@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {
@Value("${async.executor.thread.core_pool_size}")
private int corePoolSize;
@Value("${async.executor.thread.max_pool_size}")
private int maxPoolSize;
@Value("${async.executor.thread.queue_capacity}")
private int queueCapacity;
@Value("${async.executor.thread.name.prefix}")
private String namePrefix;
@Bean(name = "asyncServiceExecutor")
public Executor asyncServiceExecutor() {
log.warn("start asyncServiceExecutor");
//在这里修改
ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(corePoolSize);
//配置最大线程数
executor.setMaxPoolSize(maxPoolSize);
//配置队列大小
executor.setQueueCapacity(queueCapacity);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix(namePrefix);
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
2.3. 创建异步线程 业务类
@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService {
@Override
@Async("asyncServiceExecutor")
public void executeAsync(List<LogOutputResult> logOutputResults, LogOutputResultMapper logOutputResultMapper, CountDownLatch countDownLatch) {
try{
log.warn("start executeAsync");
//异步线程要做的事情
logOutputResultMapper.addLogOutputResultBatch(logOutputResults);
log.warn("end executeAsync");
}finally {
countDownLatch.countDown();// 很关键, 无论上面程序是否异常必须执行countDown,否则await无法释放
}
}
}
2.4. 创建多线程批量插入具体业务方法
@Override
public int testMultiThread() {
List<LogOutputResult> logOutputResults = getTestData();
//测试每100条数据插入开一个线程
List<List<LogOutputResult>> lists = ConvertHandler.splitList(logOutputResults, 100);
CountDownLatch countDownLatch = new CountDownLatch(lists.size());
for (List<LogOutputResult> listSub:lists) {
asyncService.executeAsync(listSub, logOutputResultMapper,countDownLatch);
}
try {
countDownLatch.await(); //保证之前的所有的线程都执行完成,才会走下面的;
// 这样就可以在下面拿到所有线程执行完的集合结果
} catch (Exception e) {
log.error("阻塞异常:"+e.getMessage());
}
return logOutputResults.size();
}