多线程面试题


在这里插入图片描述

1. 如何停止正在运行的线程

  • 设置一个共享变量作为线程退出的标记,当这个标记不满足时while循环,线程一直运行,另一个线程将这个共享变量设置为真,当然要保证两个线程间的可见性的话要加volatile,那这个一直运行的线程while不成立就会退出了,也就停止了
  • interrupt打断线程,对于阻塞的线程的话(sleep、wait、join)会抛出异常,对于正常的线程的话我们判断打断标记,为ture的话表示被打断了,这时候break就能退出线程。

2. 请你谈谈JMM(java内存模型)

JMM定义了对我们定义的一些共享变量的访问的规则吧,其实就是在多线程的环境下怎么去正确的读写这些共享变量,JMM呢就提供了这种安全的保障,其实每一个线程都在自己的工作内存中去操作,工作内存中就是主存中的这些共享变量的副本,那就保证了当前这个线程的操作的正确性,但是要保证我们操作了工作内存后和主存数据的一致性,像volatile吧就其实也保证了别的线程读到了也是正确的 。
在这里插入图片描述
在这里插入图片描述

3. AQS

AQS,它是一种在并发的环境下的这种安全的队列吧,是一个并发的基础的一个框架吧,提供了这种锁的机制,像它也支持多个条件变量,条件不满足进入,以及这个先进先出的队列,争抢不到就会进入,内部的话其实它是一个双向链表,state属性呢就表示当前有没有获得锁,其实也是基于CAS机制实现的,像在Java中,它的子类的话ReentrantLock、Countdownlatch、信号量啊都是基于AQS实现的。

4. ReentrantLock实现原理

主要利用CAS+AQS实现
ReentrantLock 里面的加锁啊解锁啊这些其实都是调用AQS里面的相关的方法的,像加锁lock方法,AQS又有不同的实现比如说公平的非公平的,首先它会以CAS方式修改state状态,如果修改成功那就表示加锁成功,将Owner线程设置为当前这个线程,如果加锁失败,就会创建一个Node对象然后加入到队列中去,这个队列其实是一个双向链表,并且再将Node加入到队列的这部分代码中,它是一直循环的是一个死循环,它会记录当前节点的前驱节点,自己呢(线程)就park住,也就实现了加锁。解锁呢也是一样,也是调用AQS中的方法,将state设置为0,将owner线程设置为null,然后unpark恢复头节点后面的线程就是唤醒一个嘛,那这个线程就能接着在park住的位置继续执行,它是在一个死循环里面,然后又循环再获取锁,修改state状态修改owner线程。

5. 死锁怎么检测

在这里插入图片描述

6 ConcurrentHashMap

在这里插入图片描述
1.8 在这里插入图片描述

7 线程池中可以用哪些阻塞队列?

LinkedBlockQueue锁头尾,其实符合我们的需求,生产者放消费者取,不能说只能有一个操作,它两是可以同时操作的,ArrayBlockQueue只有一把锁,直接吧整个数组给锁住了,并发性不太好。
在这里插入图片描述

8 如何确定核心线程数

在这里插入图片描述

9. 为什么不建议用Executors创建线程池

在这里插入图片描述

10 线程池使用场景

在这里插入图片描述

es数据批量导入口述:比如说我们要同步数据库数据到es,数据量太大的话就用到了线程池和CountDownLatch,我们先查出有多少条记录,然后看看能分多少页,每页的数据同步就用到了线程池中的线程来同步,这页的数据同步完毕调用CountDownLatch。countDown,主线程这里调用CountDownLatch的await方法,当所有数据同步完那计数也就变为0了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

package com.itheima.cdl.service.impl;

import com.alibaba.fastjson.JSON;
import com.itheima.cdl.mapper.ApArticleMapper;
import com.itheima.cdl.pojo.SearchArticleVo;
import com.itheima.cdl.service.ApArticleService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;

@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ExecutorService executorService;

    private static final String ARTICLE_ES_INDEX = "app_info_article";

    private static final int PAGE_SIZE = 2000;

    /**
     * 批量导入
     */
    @SneakyThrows
    @Override
    public void importAll() {

        //总条数
        int count = apArticleMapper.selectCount();
        //总页数
        int totalPageSize = count % PAGE_SIZE == 0 ? count / PAGE_SIZE : count / PAGE_SIZE + 1;
        //开始执行时间
        long startTime = System.currentTimeMillis();
        //一共有多少页,就创建多少个CountDownLatch的计数
        CountDownLatch countDownLatch = new CountDownLatch(totalPageSize);

        int fromIndex;
        List<SearchArticleVo> articleList = null;

        for (int i = 0; i < totalPageSize; i++) {
            //起始分页条数
            fromIndex = i * PAGE_SIZE;
            //查询文章
            articleList = apArticleMapper.loadArticleList(fromIndex, PAGE_SIZE);
            //创建线程,做批量插入es数据操作
            TaskThread taskThread = new TaskThread(articleList, countDownLatch);
            //执行线程
            executorService.execute(taskThread);
        }

        //调用await()方法,用来等待计数归零
        countDownLatch.await();

        long endTime = System.currentTimeMillis();
        log.info("es索引数据批量导入共:{}条,共消耗时间:{}秒", count, (endTime - startTime) / 1000);
    }

    class TaskThread implements Runnable {

        List<SearchArticleVo> articleList;
        CountDownLatch cdl;

        public TaskThread(List<SearchArticleVo> articleList, CountDownLatch cdl) {
            this.articleList = articleList;
            this.cdl = cdl;
        }

        @SneakyThrows
        @Override
        public void run() {
            //批量导入
            BulkRequest bulkRequest = new BulkRequest(ARTICLE_ES_INDEX);

            for (SearchArticleVo searchArticleVo : articleList) {
                bulkRequest.add(new IndexRequest().id(searchArticleVo.getId().toString())
                        .source(JSON.toJSONString(searchArticleVo), XContentType.JSON));
            }
            //发送请求,批量添加数据到es索引库中
            client.bulk(bulkRequest, RequestOptions.DEFAULT);

            //让计数减一
            cdl.countDown();
        }
    }
}

数据汇总
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

异步调用
在这里插入图片描述
配合注解@Async开启异步调用。
在这里插入图片描述

11. 如何控制某个方法允许并发访问线程的数量

在这里插入图片描述

12. ThreadLocal

口述:ThreadLocal其实也是在并发的情况下对共享变量访问的一种方式吧,一种安全的方式吧,像加锁啊也能实现,只是说加锁的话其实是一种耗时的方式吧,这里的话其他线程就被挡外面了,那ThreadLocal其实是一种空间换时间的思想,其实就是将共享变量呢拷贝了一份,那对共享变量的访问肯定是安全的,它也是一种并发下的安全的保障吧。其实ThreadLocal它底层是用到了ThreadLocalMap,这个map呢在1.7之前呢是由ThreadLocal维护,在1.8之后呢是由Thread维护,map呢作为Thread的一个成员变量,map的生命周期就和线程是一样的,这样做的目的的话其实就是当线程销毁的时候map也能及时的销毁释放内存,而这个map中是维护了这样一个entry数组,并且我们存储的值呢就存储在数组中,是以key为ThreadLocal对象value值的这种形式存储的,而且这个key引用的ThreadLocal也是弱引用,也是为了尽量的去避免内存泄漏,我们在使用ThreadLocal的时候一定得去调用remove方法,来去释放掉entry,避免内存泄漏吧。
在这里插入图片描述

为什么不直接用线程id作为ThreadLocalMap的key呢?
口述:其实在1.7map中是采用线程id来作为key的,并且map是由ThreadLocal维护的,但是有些不足之后,在1.8也就做了改进,map是用ThreadLocal对象来作为key的,并且这个map是由Thread来维护,这样做的好处其实就是说如果我们有很多线程的话那entey中就得存好多个线程,开销有点大,key改为ThreadLocal对象的话不会出现很多的情况,那维护开销也就没那么大了,并且的话ThreadLocalMap改为了由Thread来维护,那就很好实现我们的需求吧,就是当Thread用完了要销毁的时候ThreadLocalMap也就销毁了。
在这里插入图片描述


entry的key为弱引用,目的是什么?
口述:其实设计成弱引用的目的也就是为了能及时的回收threadlocal对象吧,因为threadlocalMap它是由线程维护的,它作为thread的一个成员变量,map的生命周期和线程是一样的,map中的key它是指向threadlocal对象的,设计成弱引用其实也就是为了能及时的回收掉threadlocal对象,因为弱引用在下次GC就会被回收,那既然这个key指向的threadlocal对象被回收了,在下次调用threadlocal的get、set方法时,会将key为null的value也回收掉,也算是一种保障吧,避免内存泄漏的保障。

在这里插入图片描述


内存泄漏的本质是什么?当前线程没结束 + entry没删除。
在这里插入图片描述
口述
内存泄漏和key的引用为强引用和弱引用没有关系,若是强引用即使ThreadLocal不用了,但是key还是指向堆中的ThreadLocal对象的,是不会回收的,如果Key使用弱引用:使用弱引用作为Entry的Key,可以多一层保障:弱引用ThreadLocal不会轻易内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。但是虽然说在我们下次调用的时候会去清除过期Entry,但也是可能发生内存泄漏,如果说不调用threadlocal的get、set方法呢,那这个key为null的value就永远不能回收掉了,额也不是永远吧,当线程结束也就回收掉了,但如果说这个线程是线程池中的核心线程,线程不会销毁,那这个value就不会回收,所以在使用ThreadLocal时候要配合remove去删除不用的entry。


ThreadLocal使用场景:
在这里插入图片描述

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
牙科就诊管理系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的Mysql数据库进行程序开发。实现了用户在线查看数据。管理员管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等功能。牙科就诊管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 管理员在后台主要管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等。 牙医列表页面,此页面提供给管理员的功能有:查看牙医、新增牙医、修改牙医、删除牙医等。公告信息管理页面提供的功能操作有:新增公告,修改公告,删除公告操作。公告类型管理页面显示所有公告类型,在此页面既可以让管理员添加新的公告信息类型,也能对已有的公告类型信息执行编辑更新,失效的公告类型信息也能让管理员快速删除。药品管理页面,此页面提供给管理员的功能有:新增药品,修改药品,删除药品。药品类型管理页面,此页面提供给管理员的功能有:新增药品类型,修改药品类型,删除药品类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值