新版Java面試專題視頻教程——多線程篇②

新版Java面試專題視頻教程——多線程篇②

      • 0. 問題匯總
        • 0.1 線程的基礎知識
        • 0.2 線程中并發安全
        • 0.3 線程池
        • 0.4 使用場景
      • 1.線程的基礎知識
      • 2.線程中并發鎖
      • 3.線程池
        • 3.1 說一下線程池的核心參數(線程池的執行原理知道嘛)
        • 3.2 線程池中有哪些常見的阻塞隊列
          • ArrayBlockingQueue的LinkedBlockingQueue區別
        • 3.3 如何確定核心線程數
        • 3.4 線程池的種類有哪些
        • 3.5 為什么不建議用Executors創建線程池
      • 4.線程使用場景問題
        • 4.1 線程池使用場景CountDownLatch、Future(你們項目哪里用到了多線程)
          • 4.1.1 CountDownLatch
          • 4.1.2 案例一(es數據批量導入)
          • 4.1.3 案例二(數據匯總)
          • 4.1.4 案例二(異步調用)
        • 4.2 如何控制某個方法允許并發訪問線程的數量?
          • Semaphore兩個重要的方法
      • 5.其他
        • 5.1 談談你對ThreadLocal的理解
          • 5.1.1 概述
          • 5.1.2 ThreadLocal基本使用
            • 三個主要方法:
          • 5.1.3 ThreadLocal的實現原理&源碼解析
          • 5.1.4 ThreadLocal-內存泄露問題
            • 強引用、軟引用、弱引用的區別和解析
            • 內存泄漏問題
      • 6 真實面試還原
        • 6.1 線程的基礎知識
        • 6.2 線程中并發鎖
        • 6.3 線程池
        • 6.4 線程使用場景問題
        • 6.5 其他

在這里插入圖片描述

在這里插入圖片描述

0. 問題匯總

0.1 線程的基礎知識

線程與進程的區別
并行與并發的區別
線程創建的方式有哪些
runnable和callable有什么區別
線程包括哪些狀態
狀態之間是如何變化的
在java中wait和sleep方法的不同
新建三個線程,如何保證它們按順序執行
notify和notifyAll有什么區別
線程的run()和start()有什么區別
如何停止一個正在運行的線程

0.2 線程中并發安全

synchronized關鍵字的底層原理
你談談JMM (Java 內存模型)
CAS你知道嗎
什么是AQS
ReentrantLock的實現原理
synchronized和Lock有什么區別
死鎖產生的條件是什么
如何進行死鎖診斷
請談談你對volatile的理解
聊一下ConcurrentHashMap
導致并發程序出現問題的根本原因是什么

0.3 線程池

說一下線程池的核心參數(線程池的執行原理知道嘛)

線程池中有哪些常見的阻塞隊列

如何確定核心線程數

線程池的種類有哪些

為什么不建議用Executors創建線程池

0.4 使用場景

線程池使用場景(你們項目中哪墜用到了線程池)

如何控制某個方法允許并發訪問線程的數量

談談你對ThreadLocal的理解

在這里插入圖片描述

1.線程的基礎知識

新版Java面試專題視頻教程——多線程篇①

2.線程中并發鎖

新版Java面試專題視頻教程——多線程篇①
在這里插入圖片描述

3.線程池

3.1 說一下線程池的核心參數(線程池的執行原理知道嘛)

難易程度:☆☆☆
出現頻率:☆☆☆☆

線程池核心參數主要參考ThreadPoolExecutor這個類的7個參數的構造函數

在這里插入圖片描述

  • corePoolSize 核心線程數目

  • maximumPoolSize 最大線程數目 = (核心線程+救急線程的最大數目)

  • keepAliveTime 生存時間 - 救急線程的生存時間,生存時間內沒有新任務,此線程資源會釋放

  • unit 時間單位 - 救急線程的生存時間單位,如秒、毫秒等

  • workQueue 阻塞隊列 - 當沒有空閑核心線程時,新來任務會加入到此隊列排隊,隊列滿會創建救急線程執行任務

  • threadFactory程工廠 - 可以定制線程對象的創建,例如設置線程名字、是否是守護線程等

  • handler 拒絕策略 - 當所有線程都在繁忙,workQueue放滿時,會觸發拒絕策略

工作流程

在這里插入圖片描述

1,任務在提交的時候,首先判斷核心線程數是否已滿,如果沒有滿則直接添加到工作線程執行
2,如果核心線程數滿了,則判斷阻塞隊列是否已滿,如果沒有滿,當前任務存入阻塞隊列
3,如果阻塞隊列也滿了,則判斷線程數是否小于最大線程數,如果滿足條件,則使用臨時線程執行任務
如果核心或臨時線程執行完成任務后會檢查阻塞隊列中是否有需要執行的線程,如果有,則使用非核心線程執行任務
4,如果所有線程都在忙著(核心線程+臨時線程),則走拒絕策略

拒絕策略

1.AbortPolicy:直接拋出異常,默認策略

2.CallerRunsPolicy:用調用者所在的線程來執行任務;

3.DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,并執行當前任務;

4.DiscardPolicy:直接丟棄任務

參考代碼:


public class TestThreadPoolExecutor {static class MyTask implements Runnable {private final String name;private final long duration;public MyTask(String name) {this(name, 0);}public MyTask(String name, long duration) {this.name = name;this.duration = duration;}@Overridepublic void run() {try {LoggerUtils.get("myThread").debug("running..." + this);Thread.sleep(duration);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic String toString() {return "MyTask(" + name + ")";}}public static void main(String[] args) throws InterruptedException {AtomicInteger c = new AtomicInteger(1);ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,3,0,TimeUnit.MILLISECONDS,queue,r -> new Thread(r, "myThread" + c.getAndIncrement()),new ThreadPoolExecutor.AbortPolicy());showState(queue, threadPool);threadPool.submit(new MyTask("1", 3600000));showState(queue, threadPool);threadPool.submit(new MyTask("2", 3600000));showState(queue, threadPool);threadPool.submit(new MyTask("3"));showState(queue, threadPool);threadPool.submit(new MyTask("4"));showState(queue, threadPool);threadPool.submit(new MyTask("5",3600000));showState(queue, threadPool);threadPool.submit(new MyTask("6"));showState(queue, threadPool);}private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) {try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}List<Object> tasks = new ArrayList<>();for (Runnable runnable : queue) {try {Field callable = FutureTask.class.getDeclaredField("callable");callable.setAccessible(true);Object adapter = callable.get(runnable);Class<?> clazz = Class.forName("java.util.concurrent.Executors$RunnableAdapter");Field task = clazz.getDeclaredField("task");task.setAccessible(true);Object o = task.get(adapter);tasks.add(o);} catch (Exception e) {e.printStackTrace();}}LoggerUtils.main.debug("pool size: {}, queue: {}", threadPool.getPoolSize(), tasks);}}

在這里插入圖片描述

3.2 線程池中有哪些常見的阻塞隊列

難易程度:☆☆☆
出現頻率:☆☆☆

workQueue - 當沒有空閑核心線程時,新來任務會加入到此隊列排隊,隊列滿會創建救急線程執行任務

比較常見的有4個,用的最多是ArrayBlockingQueue和LinkedBlockingQueue

  1. ArrayBlockingQueue:基于數組結構的有界(可指定容量大小)阻塞隊列,FIFO(先進先出)。
  2. LinkedBlockingQueue:基于鏈表結構的有界阻塞隊列,FIFO。
  3. DelayedWorkQueue :是一個優先級隊列,它可以保證每次出隊的任務都是當前隊列中執行時間最靠前
  4. SynchronousQueue:不存儲元素的阻塞隊列,每個插入操作都必須等待一個移出操作。
ArrayBlockingQueue的LinkedBlockingQueue區別
LinkedBlockingQueueArrayBlockingQueue
默認無界,支持有界強制有界
底層是鏈表底層是數組
是懶惰的,創建節點的時候添加數據提前初始化Node數組
入隊會生成新NodeNode需要是提前創建好的
兩把鎖(頭尾)一把鎖

左邊是LinkedBlockingQueue加鎖的方式,右邊是ArrayBlockingQueue加鎖的方式

  • LinkedBlockingQueue讀和寫各有一把鎖,性能相對較好
  • ArrayBlockingQueue只有一把鎖,讀和寫公用,性能相對于LinkedBlockingQueue差一些

在這里插入圖片描述

public class FixedThreadPoolCase {static class FixedThreadDemo implements Runnable{@Overridepublic void run() {String name = Thread.currentThread().getName();for (int i = 0; i < 2; i++) {System.out.println(name + ":" + i);}}}public static void main(String[] args) throws InterruptedException {//創建一個固定大小的線程池,核心線程數和最大線程數都是3ExecutorService executorService = Executors.newFixedThreadPool(3);for (int i = 0; i < 5; i++) {executorService.submit(new FixedThreadDemo());Thread.sleep(10);}executorService.shutdown();}}

在這里插入圖片描述

3.3 如何確定核心線程數

難易程度:☆☆☆☆
出現頻率:☆☆☆

在設置核心線程數之前,需要先熟悉一些執行線程池執行任務的類型

  • IO密集型任務

一般來說:文件讀寫、DB讀寫、網絡請求

推薦:核心線程數大小設置為2N+1 (N為計算機的CPU核數)

  • CPU密集型任務

一般來說:計算型代碼、Bitmap轉換、Gson轉換等

推薦:核心線程數大小設置為N+1 (N為計算機的CPU核數)

java代碼查看CPU核數

參考回答:

高并發、任務執行時間 -->( CPU核數+1 ),減少線程上下文的切換

并發不高、任務執行時間

  • IO密集型的任務 --> (CPU核數 * 2 + 1)
  • 計算密集型任務 --> ( CPU核數+1

并發高、業務執行時間長,解決這種類型任務的關鍵不在于線程池而在于整體架構的設計,看看這些業務里面某些數據是否能做緩存是第一步,增加服務器是第二步,至于線程池的設置,設置參考(2)

3.4 線程池的種類有哪些

難易程度:☆☆☆
出現頻率:☆☆☆

在java.util.concurrent.Executors類中提供了大量創建連接池的靜態方法,常見就有四種

  1. 創建使用固定線程數的線程池
    在這里插入圖片描述
  • 核心線程數與最大線程數一樣,沒有救急線程 那0L和unit就沒意義
  • 阻塞隊列是LinkedBlockingQueue,最大容量Integer.MAX_VALUE
  • 適用場景:適用于任務量已知,相對耗時的任務
  • 案例:
public class FixedThreadPoolCase {static class FixedThreadDemo implements Runnable{@Overridepublic void run() {String name = Thread.currentThread().getName();for (int i = 0; i < 2; i++) {System.out.println(name + ":" + i);}}}public static void main(String[] args) throws InterruptedException {//創建一個固定大小的線程池,核心線程數和最大線程數都是3ExecutorService executorService = Executors.newFixedThreadPool(3);for (int i = 0; i < 5; i++) {executorService.submit(new FixedThreadDemo());Thread.sleep(10);}executorService.shutdown();}
}
  1. 單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO)執行
    在這里插入圖片描述
  • 核心線程數和最大線程數都是1
  • 阻塞隊列是LinkedBlockingQueue,最大容量為Integer.MAX_VALUE
  • 適用場景:適用于按照順序執行的任務
  • 案例:
public class NewSingleThreadCase {static int count = 0;static class Demo implements Runnable {@Overridepublic void run() {count++;System.out.println(Thread.currentThread().getName() + ":" + count);}}public static void main(String[] args) throws InterruptedException {//單個線程池,核心線程數和最大線程數都是1ExecutorService exec = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {exec.execute(new Demo());Thread.sleep(5);}exec.shutdown();}
}

在這里插入圖片描述

  1. 可緩存線程池
    在這里插入圖片描述
  • 核心線程數為0
  • 最大線程數是Integer.MAX_VALUE
  • 阻塞隊列為SynchronousQueue:不存儲元素的阻塞隊列,每個插入操作都必須等待一個移出操作。
  • 適用場景:適合任務數比較密集,但每個任務執行時間較短的情況
  • 案例:
public class CachedThreadPoolCase {static class Demo implements Runnable {@Overridepublic void run() {String name = Thread.currentThread().getName();try {//修改睡眠時間,模擬線程執行需要花費的時間Thread.sleep(100);System.out.println(name + "執行完了");} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {//創建一個緩存的線程,沒有核心線程數,最大線程數為Integer.MAX_VALUEExecutorService exec = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {exec.execute(new Demo());Thread.sleep(1);}exec.shutdown();}
}

在這里插入圖片描述

  1. 提供了“延遲”和“周期執行”功能的ThreadPoolExecutor。
    在這里插入圖片描述
  • 適用場景:有定時和延遲執行的任務
  • 案例
public class ScheduledThreadPoolCase {static class Task implements Runnable {@Overridepublic void run() {try {String name = Thread.currentThread().getName();System.out.println(name + ", 開始:" + new Date());Thread.sleep(1000);System.out.println(name + ", 結束:" + new Date());} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {//按照周期執行的線程池,核心線程數為2,最大線程數為Integer.MAX_VALUEScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);System.out.println("程序開始:" + new Date());/*** schedule 提交任務到線程池中* 第一個參數:提交的任務* 第二個參數:任務執行的延遲時間* 第三個參數:時間單位*/scheduledThreadPool.schedule(new Task(), 0, TimeUnit.SECONDS);scheduledThreadPool.schedule(new Task(), 1, TimeUnit.SECONDS);scheduledThreadPool.schedule(new Task(), 5, TimeUnit.SECONDS);Thread.sleep(5000);// 關閉線程池scheduledThreadPool.shutdown();}
}

在這里插入圖片描述

3.5 為什么不建議用Executors創建線程池

難易程度:☆☆☆
出現頻率:☆☆☆

參考阿里開發手冊《Java開發手冊-嵩山版》

在這里插入圖片描述
在這里插入圖片描述

4.線程使用場景問題

4.1 線程池使用場景CountDownLatch、Future(你們項目哪里用到了多線程)

難易程度:☆☆☆
出現頻率:☆☆☆☆

4.1.1 CountDownLatch

CountDownLatch(閉鎖/倒計時鎖)用來進行線程同步協作,等待所有線程完成倒計時(一個或者多個線程,等待其他多個線程完成某件事情之后才能執行)

  • 其中構造參數用來初始化等待計數值
  • await() 用來等待計數歸零
  • countDown() 用來讓計數減一

在這里插入圖片描述

案例代碼:

public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {//初始化了一個倒計時鎖 參數為 3CountDownLatch latch = new CountDownLatch(3);new Thread(() -> {System.out.println(Thread.currentThread().getName()+"-begin...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//count--latch.countDown();System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount());}).start();new Thread(() -> {System.out.println(Thread.currentThread().getName()+"-begin...");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}//count--latch.countDown();System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount());}).start();new Thread(() -> {System.out.println(Thread.currentThread().getName()+"-begin...");try {Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}//count--latch.countDown();System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount());}).start();String name = Thread.currentThread().getName();System.out.println(name + "-waiting...");//等待其他線程完成latch.await();System.out.println(name + "-wait end...");}}
4.1.2 案例一(es數據批量導入)

在我們項目上線之前,我們需要把數據庫中的數據一次性的同步到es索引庫中,但是當時的數據好像是1000萬左右,一次性讀取數據肯定不行(oom異常),當時我就想到可以使用線程池的方式導入,利用CountDownLatch來控制,就能避免一次性加載過多,防止內存溢出

整體流程就是通過CountDownLatch+線程池配合去執行

在這里插入圖片描述

詳細實現流程:

在這里插入圖片描述

詳細實現代碼,請查看當天代碼

package com.itheima.cdl.service.impl;import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl implements ApArticleService {@Autowiredprivate ApArticleMapper apArticleMapper;@Autowiredprivate RestHighLevelClient client;@Autowiredprivate ExecutorService executorService;private static final String ARTICLE_ES_INDEX = "app_info_article";private static final int PAGE_SIZE = 2000;/*** 批量導入*/@SneakyThrows@Overridepublic 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@Overridepublic 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();}}
}
4.1.3 案例二(數據匯總)

在一個電商網站中,用戶下單之后,需要查詢數據,數據包含了三部分:訂單信息、包含的商品、物流信息;這三塊信息都在不同的微服務中進行實現的,我們如何完成這個業務呢?

在這里插入圖片描述

MQ跟多線程異步改寫的區別:MQ主要解決跨進程之間的消息同步問題,將其改寫成了異步 側重于服務間通訊 而多線程主要解決的是當前進程快速響應

package com.itheima.cdl.controller;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;@RestController
@RequestMapping("/order_detail")
@Slf4j
public class OrderDetailController {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate ExecutorService executorService;@SneakyThrows@GetMapping("/get/detail_new/{id}")public Map<String, Object> getOrderDetailNew() {long startTime = System.currentTimeMillis();Future<Map<String, Object>> f1 = executorService.submit(() -> {Map<String, Object> r =restTemplate.getForObject("http://localhost:9991/order/get/{id}", Map.class, 1);return r;});Future<Map<String, Object>> f2 = executorService.submit(() -> {Map<String, Object> r =restTemplate.getForObject("http://localhost:9991/product/get/{id}", Map.class, 1);return r;});Future<Map<String, Object>> f3 = executorService.submit(() -> {Map<String, Object> r =restTemplate.getForObject("http://localhost:9991/logistics/get/{id}", Map.class, 1);return r;});Map<String, Object> resultMap = new HashMap<>();resultMap.put("order", f1.get());resultMap.put("product", f2.get());resultMap.put("logistics", f3.get());long endTime = System.currentTimeMillis();log.info("接口調用共耗時:{}毫秒",endTime-startTime);return resultMap;}@SneakyThrows@GetMapping("/get/detail/{id}")public Map<String, Object> getOrderDetail() {long startTime = System.currentTimeMillis();Map<String, Object> order = restTemplate.getForObject("http://localhost:9991/order/get/{id}", Map.class, 1);Map<String, Object> product = restTemplate.getForObject("http://localhost:9991/product/get/{id}", Map.class, 1);Map<String, Object> logistics = restTemplate.getForObject("http://localhost:9991/logistics/get/{id}", Map.class, 1);long endTime = System.currentTimeMillis();Map<String, Object> resultMap = new HashMap<>();resultMap.put("order", order);resultMap.put("product", product);resultMap.put("logistics", logistics);log.info("接口調用共耗時:{}毫秒",endTime-startTime);return resultMap;}
}
  • 在實際開發的過程中,難免需要調用多個接口來匯總數據,如果所有接口(或部分接口)的沒有依賴關系,就可以使用線程池+future來提升性能
  • 報表匯總

在這里插入圖片描述

4.1.4 案例二(異步調用)

在這里插入圖片描述

在進行搜索的時候,需要保存用戶的搜索記錄,而搜索記錄不能影響用戶的正常搜索,我們通常會開啟一個線程去執行歷史記錄的保存,在新開啟的線程在執行的過程中,可以利用線程提交任務

package com.itheima.cdl.service.impl;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Service
@Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService {@Autowiredprivate RestHighLevelClient client;private static final String ARTICLE_ES_INDEX = "app_info_article";private int userId = 1102;@Autowiredprivate ApUserSearchService apUserSearchService;/*** 文章搜索* @return*/@Overridepublic List<Map> search(String keyword) {try {SearchRequest request = new SearchRequest(ARTICLE_ES_INDEX);//設置查詢條件BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//第一個條件if(null == keyword || "".equals(keyword)){request.source().query(QueryBuilders.matchAllQuery());}else {request.source().query(QueryBuilders.queryStringQuery(keyword).field("title").defaultOperator(Operator.OR));//保存搜索歷史apUserSearchService.insert(userId,keyword);}//分頁request.source().from(0);request.source().size(20);//按照時間倒序排序request.source().sort("publishTime", SortOrder.DESC);//搜索SearchResponse response = client.search(request, RequestOptions.DEFAULT);//解析結果SearchHits searchHits = response.getHits();//獲取具體文檔數據SearchHit[] hits = searchHits.getHits();List<Map> resultList = new ArrayList<>();for (SearchHit hit : hits) {//文檔數據Map map = JSON.parseObject(hit.getSourceAsString(), Map.class);resultList.add(map);}return resultList;} catch (IOException e) {throw new RuntimeException("搜索失敗");}}
}
package com.itheima.cdl.service.impl;
import com.itheima.cdl.service.ApUserSearchService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class ApUserSearchServiceImpl implements ApUserSearchService {/*** 保存搜索歷史記錄* @param userId* @param keyword*/@Async("taskExecutor")//異步調用@Overridepublic void insert(Integer userId, String keyword) {//保存用戶記錄  mongodb或mysql//執行業務log.info("用戶搜索記錄保存成功,用戶id:{},關鍵字:{}",userId,keyword);}
}

4.2 如何控制某個方法允許并發訪問線程的數量?

難易程度:☆☆☆
出現頻率:☆☆

Semaphore [?s?m??f?r] 信號量,是JUC包下的一個工具類底層是AQS,我們可以通過其限制執行的線程數量,達到限流的效果

當一個線程執行時先通過其方法進行獲取許可操作,獲取到許可的線程繼續執行業務邏輯,當線程執行完成后進行釋放許可操作,未獲取達到許可的線程進行等待或者直接結束

Semaphore兩個重要的方法

lsemaphore.acquire(): 請求一個信號量,這時候的信號量個數-1(一旦沒有可使用的信號量,也即信號量個數變為負數時,再次請求的時候就會阻塞,直到其他線程釋放了信號量)

lsemaphore.release():釋放一個信號量,此時信號量個數+1

線程任務類:

public class SemaphoreCase {public static void main(String[] args) {// 1. 創建 semaphore 對象Semaphore semaphore = new Semaphore(3); //容量是3// 2. 10個線程同時運行for (int i = 0; i < 10; i++) {new Thread(() -> {try {// 3. 獲取許可semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}try {System.out.println("running...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("end...");} finally {// 4. 釋放許可semaphore.release();}}).start();}}
}

5.其他

5.1 談談你對ThreadLocal的理解

難易程度:☆☆☆
出現頻率:☆☆☆☆

5.1.1 概述

ThreadLocal是多線程中對于解決線程安全的一個操作類,它會為每個線程都分配一個獨立的線程副本從而解決了變量并發訪問沖突的問題。ThreadLocal 同時實現了線程內的資源共享

案例:使用JDBC操作數據庫時,會將每一個線程的Connection放入各自的ThreadLocal中,從而保證每個線程都在各自的 Connection 上進行數據庫的操作,避免A線程關閉了B線程的連接。

在這里插入圖片描述

5.1.2 ThreadLocal基本使用
三個主要方法:
  • set(value) 設置值
  • get() 獲取值
  • remove() 清除值
public class ThreadLocalTest {static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {new Thread(() -> {String name = Thread.currentThread().getName();threadLocal.set("itcast");print(name);System.out.println(name + "-after remove : " + threadLocal.get());}, "t1").start();new Thread(() -> {String name = Thread.currentThread().getName();threadLocal.set("itheima");print(name);System.out.println(name + "-after remove : " + threadLocal.get());}, "t2").start();}static void print(String str) {//打印當前線程中本地內存中本地變量的值System.out.println(str + " :" + threadLocal.get());//清除本地內存中的本地變量threadLocal.remove();}
}

5.1.3 ThreadLocal的實現原理&源碼解析

ThreadLocal本質來說就是一個線程內部存儲類,從而讓多個線程只操作自己內部的值,從而實現線程數據隔離

在ThreadLocal中有一個內部類叫做ThreadLocalMap,類似于HashMap

ThreadLocalMap中有一個屬性table數組,這個是真正存儲數據的位置

set方法

在這里插入圖片描述

get方法/remove方法

在這里插入圖片描述

5.1.4 ThreadLocal-內存泄露問題

Java對象中的四種引用類型:強引用、軟引用、弱引用、虛引用

強引用、軟引用、弱引用的區別和解析
  • 強引用:

最為普通的引用方式,表示一個對象處于有用且必須的狀態,如果一個對象具有強引用,則GC并不會回收它。即便堆中內存不足了,寧可出現OOM,也不會對其進行回收

在這里插入圖片描述

  • 弱引用:

表示一個對象處于可能有用且非必須的狀態。在GC線程掃描內存區域時,一旦發現弱引用,就會回收到弱引用相關聯的對象。對于弱引用的回收,無關內存區域是否足夠,一旦發現則會被回收

在這里插入圖片描述

內存泄漏問題

每一個Thread維護一個ThreadLocalMap,在ThreadLocalMap中的Entry對象繼承了WeakReference。其中key為使用弱引用的ThreadLocal實例value為線程變量的副本

在這里插入圖片描述

在使用ThreadLocal的時候,強烈建議:務必手動remove 防止內存泄漏

6 真實面試還原

6.1 線程的基礎知識

聊一下并行和并發有什么區別?

候選人:
是這樣的~~
現在都是多核CPU,在多核CPU下
并發是同一時間應對多件事情的能力,多個線程輪流使用一個或多個CPU
并行是同一時間動手做多件事情的能力,4核CPU同時執行4個線程

說一下線程和進程的區別?

候選人:
嗯,好~
進程是正在運行程序的實例,進程中包含了線程,每個線程執行不同的任務
不同的進程使用不同的內存空間,在當前進程下的所有線程可以共享內存空間
線程更輕量,線程上下文切換成本一般上要比進程上下文切換低(上下文切換指的是從一個線程切換到另一個線程)

如果在java中創建線程有哪些方式?

候選人:
在java中一共有四種常見的創建方式,分別是:繼承Thread類、實現runnable接口、實現Callable接口、線程池創建線程。通常情況下,我們項目中都會采用線程池的方式創建線程。

好的,剛才你說的runnable 和 callable 兩個接口創建線程有什么不同呢?

候選人:
是這樣的~
最主要的兩個線程一個是有返回值,一個是沒有返回值的。
Runnable 接口run方法無返回值;Callable接口call方法有返回值,是個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果
還有一個就是,他們異常處理也不一樣。Runnable接口run方法只能拋出運行時異常,也無法捕獲處理;Callable接口call方法允許拋出異常,可以獲取異常信息
在實際開發中,如果需要拿到執行的結果,需要使用Callalbe接口創建線程,調用FutureTask.get()得到可以得到返回值,此方法會阻塞主進程的繼續往下執行,如果不調用不會阻塞。

線程包括哪些狀態,狀態之間是如何變化的?

候選人:
在JDK中的Thread類中的枚舉State里面定義了6中線程的狀態分別是:新建、可運行、終結、阻塞、等待和有時限等待六種。
關于線程的狀態切換情況比較多。我分別介紹一下
當一個線程對象被創建,但還未調用 start 方法時處于 新建狀態,調用了 start 方法,就會由 新建進入 可運行狀態。如果線程內代碼已經執行完畢,由 可運行進入 終結狀態。當然這些是一個線程正常執行情況。
如果線程獲取鎖失敗后,由 可運行進入 Monitor 的阻塞隊列 阻塞,只有當持鎖線程釋放鎖時,會按照一定規則喚醒阻塞隊列中的 阻塞線程,喚醒后的線程進入 可運行狀態
如果線程獲取鎖成功后,但由于條件不滿足,調用了 wait() 方法,此時從 可運行狀態釋放鎖 等待狀態,當其它持鎖線程調用 notify() 或 notifyAll() 方法,會恢復為 可運行狀態
還有一種情況是調用 sleep(long) 方法也會從 可運行狀態進入 有時限等待狀態,不需要主動喚醒,超時時間到自然恢復為 可運行狀態

嗯,好的,剛才你說的線程中的 wait 和 sleep方法有什么不同呢?

候選人:
它們兩個的相同點是都可以讓當前線程暫時放棄 CPU 的使用權,進入阻塞狀態。
不同點主要有三個方面:
第一:方法歸屬不同
sleep(long) 是 Thread 的靜態方法。而 wait(),是 Object 的成員方法,每個對象都有
第二:線程醒來時機不同
線程執行 sleep(long) 會在等待相應毫秒后醒來,而 wait() 需要被 notify 喚醒,wait() 如果不喚醒就一直等下去
第三:鎖特性不同
wait 方法的調用必須先獲取 wait 對象的鎖,而 sleep 則無此限制
wait 方法執行后會釋放對象鎖,允許其它線程獲得該對象鎖(相當于我放棄 cpu,但你們還可以用)
而 sleep 如果在 synchronized 代碼塊中執行,并不會釋放對象鎖(相當于我放棄 cpu,你們也用不了)

好的,我現在舉一個場景,你來分析一下怎么做,新建 T1、T2、T3 三個線程,如何保證它們按順序執行?

候選人:
嗯~~,我思考一下 (適當的思考或想一下屬于正常情況,脫口而出反而太假[背誦痕跡])
可以這么做,在多線程中有多種方法讓線程按特定順序執行,可以用線程類的 join()方法在一個線程中啟動另一個線程,另外一個線程完成該線程繼續執行。
比如說:
使用join方法,T3調用T2,T2調用T1,這樣就能確保T1就會先完成而T3最后完成

在我們使用線程的過程中,有兩個方法。線程的 run()和 start()有什么區別?

候選人:
start方法用來啟動線程,通過該線程調用run方法執行run方法中所定義的邏輯代碼。start方法只能被調用一次。run方法封裝了要被線程執行的代碼,可以被調用多次。

那如何停止一個正在運行的線程呢?

候選人
有三種方式可以停止線程
第一:可以使用退出標志,使線程正常退出,也就是當run方法完成后線程終止,一般我們加一個標記
第二:可以使用線程的stop方法強行終止,不過一般不推薦,這個方法已作廢
第三:可以使用線程的interrupt方法中斷線程,內部其實也是使用中斷標志來中斷線程
我們項目中使用的話,建議使用第一種或第三種方式中斷線程

6.2 線程中并發鎖

講一下synchronized關鍵字的底層原理?

候選人
嗯~~好的,
synchronized 底層使用的JVM級別中的Monitor 來決定當前線程是否獲得了鎖,如果某一個線程獲得了鎖,在沒有釋放鎖之前,其他線程是不能或得到鎖的。synchronized 屬于悲觀鎖。
synchronized 因為需要依賴于JVM級別的Monitor ,相對性能也比較低。

好的,你能具體說下Monitor 嗎?

候選人
monitor對象存在于每個Java對象的對象頭中,synchronized 鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對象可以作為鎖的原因
monitor內部維護了三個變量
WaitSet:保存處于Waiting狀態的線程
EntryList:保存處于Blocked狀態的線程
Owner:持有鎖的線程
只有一個線程獲取到的標志就是在monitor中設置成功了Owner,一個monitor中只能有一個Owner
在上鎖的過程中,如果有其他線程也來搶鎖,則進入EntryList 進行阻塞,當獲得鎖的線程執行完了,釋放了鎖,就會喚醒EntryList 中等待的線程競爭鎖,競爭的時候是非公平的。

好的,那關于synchronized 的鎖升級的情況了解嗎?

候選人
嗯,知道一些(要謙虛)
Java中的synchronized有偏向鎖、輕量級鎖、重量級鎖三種形式,分別對應了鎖只被一個線程持有、不同線程交替持有鎖、多線程競爭鎖三種情況。
重量級鎖:底層使用的Monitor實現,里面涉及到了用戶態和內核態的切換、進程的上下文切換,成本較高,性能比較低。
輕量級鎖:線程加鎖的時間是錯開的(也就是沒有競爭),可以使用輕量級鎖來優化。輕量級修改了對象頭的鎖標志,相對重量級鎖性能提升很多。每次修改都是CAS操作,保證原子性
偏向鎖:一段很長的時間內都只被一個線程使用鎖,可以使用了偏向鎖,在第一次獲得鎖時,會有一個CAS操作,之后該線程再獲取鎖,只需要判斷mark word中是否是自己的線程id即可,而不是開銷相對較大的CAS命令
一旦鎖發生了競爭,都會升級為重量級鎖

好的,剛才你說了synchronized它在高并發量的情況下,性能不高,在項目該如何控制使用鎖呢?

候選人
嗯,其實,在高并發下,我們可以采用ReentrantLock來加鎖。

嗯,那你說下ReentrantLock的使用方式和底層原理?

候選人
好的,
ReentrantLock是一個可重入鎖:,調用 lock 方 法獲取了鎖之后,再次調用 lock,是不會再阻塞,內部直接增加重入次數 就行了,標識這個線程已經重復獲取一把鎖而不需要等待鎖的釋放。
ReentrantLock是屬于juc報下的類,屬于api層面的鎖,跟synchronized一樣,都是悲觀鎖。通過lock()用來獲取鎖,unlock()釋放鎖。
它的底層實現原理主要利用 CAS+AQS隊列來實現。它支持公平鎖和非公平鎖,兩者的實現類似
構造方法接受一個可選的公平參數( 默認非公平鎖),當設置為true時,表示公平鎖,否則為非公平鎖。公平鎖的效率往往沒有非公平鎖的效率高。

好的,剛才你說了CAS和AQS,你能介紹一下嗎?

候選人
好的。
CAS的全稱是: Compare And Swap(比較再交換);它體現的一種樂觀鎖的思想,在無鎖狀態下保證線程操作數據的原子性。
CAS使用到的地方很多:AQS框架、AtomicXXX類
在操作共享變量的時候使用的自旋鎖,效率上更高一些
CAS的底層是調用的Unsafe類中的方法,都是操作系統提供的,其他語言實現
AQS的話,其實就一個jdk提供的類AbstractQueuedSynchronizer,是阻塞式鎖和相關的同步器工具的框架。
內部有一個屬性 state 屬性來表示資源的狀態,默認state等于0,表示沒有獲取鎖,state等于1的時候才標明獲取到了鎖。通過cas 機制設置 state 狀態
在它的內部還提供了基于 FIFO 的等待隊列,是一個雙向列表,其中
tail 指向隊列最后一個元素
head 指向隊列中最久的一個元素
其中我們剛剛聊的ReentrantLock底層的實現就是一個AQS。

synchronized和Lock有什么區別 ?

候選人
嗯,好的,主要有三個方面不太一樣
第一,語法層面
synchronized 是關鍵字,源碼在 jvm 中,用 c++ 語言實現,退出同步代碼塊鎖會自動釋放
Lock 是接口,源碼由 jdk 提供,用 java 語言實現,需要手動調用 unlock 方法釋放鎖
第二,功能層面
二者均屬于悲觀鎖、都具備基本的互斥、同步、鎖重入功能
Lock 提供了許多 synchronized 不具備的功能,例如獲取等待狀態、公平鎖、可打斷、可超時、多條件變量,同時Lock 可以實現不同的場景,如 ReentrantLock, ReentrantReadWriteLock
第三,性能層面
在沒有競爭時,synchronized 做了很多優化,如偏向鎖、輕量級鎖,性能不賴
在競爭激烈時,Lock 的實現通常會提供更好的性能
統合來看,需要根據不同的場景來選擇不同的鎖的使用。

死鎖產生的條件是什么?

候選人
嗯,是這樣的,一個線程需要同時獲取多把鎖,這時就容易發生死鎖,舉個例子來說:
t1 線程獲得A對象鎖,接下來想獲取B對象的鎖
t2 線程獲得B對象鎖,接下來想獲取A對象的鎖
這個時候t1線程和t2線程都在互相等待對方的鎖,就產生了死鎖

那如果產出了這樣的,如何進行死鎖診斷?

候選人
這個也很容易,我們只需要通過jdk自動的工具就能搞定
我們可以先通過jps來查看當前java程序運行的進程id
然后通過jstack來查看這個進程id,就能展示出來死鎖的問題,并且,可以定位代碼的具體行號范圍,我們再去找到對應的代碼進行排查就行了。

請談談你對 volatile 的理解

候選人
嗯~~
volatile 是一個關鍵字,可以修飾類的成員變量、類的靜態成員變量,主要有兩個功能
第一:保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的,volatile關鍵字會強制將修改的值立即寫入主存。
第二: 禁止進行指令重排序,可以保證代碼執行有序性。底層實現原理是,添加了一個 內存屏障,通過插入內存屏障禁止在內存屏障 前后的指令執行重排序優化

本文作者:接《集合相關面試題》

那你能聊一下ConcurrentHashMap的原理嗎?

候選人
嗯好的,
ConcurrentHashMap 是一種線程安全的高效Map集合,jdk1.7和1.8也做了很多調整。
JDK1.7的底層采用是 分段的數組+ 鏈表 實現
JDK1.8 采用的數據結構跟HashMap1.8的結構一樣,數組+鏈表/紅黑二叉樹。
在jdk1.7中 ConcurrentHashMap 里包含一個 Segment 數組。Segment 的結構和HashMap類似,是一 種數組和鏈表結構,一個 Segment 包含一個 HashEntry 數組,每個 HashEntry 是一個鏈表結構 的元素,每個 Segment 守護著一個HashEntry數組里的元素,當對 HashEntry 數組的數據進行修 改時,必須首先獲得對應的 Segment的鎖。
Segment 是一種可重入的鎖 ReentrantLock,每個 Segment 守護一個HashEntry 數組里得元 素,當對 HashEntry 數組的數據進行修改時,必須首先獲得對應的 Segment 鎖
在jdk1.8中的ConcurrentHashMap 做了較大的優化,性能提升了不少。首先是它的數據結構與jdk1.8的hashMap數據結構完全一致。其次是放棄了Segment臃腫的設計,取而代之的是采用Node + CAS + Synchronized來保 證并發安全進行實現,synchronized只鎖定當前鏈表或紅黑二叉樹的首節點,這樣只要hash不沖 突,就不會產生并發 , 效率得到提升

6.3 線程池

線程池的種類有哪些?

候選人
嗯!是這樣
在jdk中默認提供了4中方式創建線程池
第一個是:newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回 收空閑線程,若無可回收,則新建線程。
第二個是:newFixedThreadPool 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列 中等待。
第三個是:newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
第四個是:newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任 務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

線程池的核心參數有哪些?

候選人
在線程池中一共有7個核心參數:
corePoolSize 核心線程數目 - 池中會保留的最多線程數
maximumPoolSize 最大線程數目 - 核心線程+救急線程的最大數目
keepAliveTime 生存時間 - 救急線程的生存時間,生存時間內沒有新任務,此線程資源會釋放
unit 時間單位 - 救急線程的生存時間單位,如秒、毫秒等
workQueue - 當沒有空閑核心線程時,新來任務會加入到此隊列排隊,隊列滿會創建救急線程執行任務
threadFactory 線程工廠 - 可以定制線程對象的創建,例如設置線程名字、是否是守護線程等
handler 拒絕策略 - 當所有線程都在繁忙,workQueue 也放滿時,會觸發拒絕策略
在拒絕策略中又有4中拒絕策略
當線程數過多以后,第一種是拋異常、第二種是由調用者執行任務、第三是丟棄當前的任務,第四是丟棄最早排隊任務。默認是直接拋異常。

如何確定核心線程池呢?

候選人
是這樣的,我們公司當時有一些規范,為了減少線程上下文的切換,要根據當時部署的服務器的CPU核數來決定,我們規則是:CPU核數+1就是最終的核心線程數。

線程池的執行原理知道嗎?

候選人
嗯~,它是這樣的
首先判斷線程池里的核心線程是否都在執行任務,如果不是則創建一個新的工作線程來執行任務。如果核心線程都在執行任務,則線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊 列里。如果工作隊列滿了,則判斷線程池里的線程是否都處于工作狀態,如果沒有,則創建一個新的工作線程來執行任 務。如果已經滿了,則交給拒絕策略來處理這個任務。

為什么不建議使用Executors創建線程池呢?

候選人
好的,其實這個事情在阿里提供的最新開發手冊《Java開發手冊-嵩山版》中也提到了
主要原因是如果使用Executors創建線程池的話,它允許的請求隊列默認長度是Integer.MAX_VALUE,這樣的話,有可能導致堆積大量的請求,從而導致OOM(內存溢出)。
所以,我們一般推薦使用ThreadPoolExecutor來創建線程池,這樣可以明確規定線程池的參數,避免資源的耗盡。

6.4 線程使用場景問題

如果控制某一個方法允許并發訪問線程的數量?

候選人
嗯~~,我想一下
在jdk中提供了一個Semaphore[sem?f??r]類(信號量)
它提供了兩個方法,semaphore.acquire() 請求信號量,可以限制線程的個數,是一個正數,如果信號量是-1,就代表已經用完了信號量,其他線程需要阻塞了
第二個方法是semaphore.release(),代表是釋放一個信號量,此時信號量的個數+1

好的,那該如何保證Java程序在多線程的情況下執行安全呢?

候選人
嗯,剛才講過了導致線程安全的原因,如果解決的話,jdk中也提供了很多的類幫助我們解決多線程安全的問題,比如:
JDK Atomic開頭的原子類、synchronized、LOCK,可以解決原子性問題
synchronized、volatile、LOCK,可以解決可見性問題
Happens-Before 規則可以解決有序性問題

你在項目中哪里用了多線程?

候選人
嗯~~,我想一下當時的場景[根據自己簡歷上的模塊設計多線程場景]
參考場景一:
es數據批量導入
在我們項目上線之前,我們需要把數據量的數據一次性的同步到es索引庫中,但是當時的數據好像是1000萬左右,一次性讀取數據肯定不行(oom異常),如果分批執行的話,耗時也太久了。所以,當時我就想到可以使用線程池的方式導入,利用CountDownLatch+Future來控制,就能大大提升導入的時間。
參考場景二:
在我做那個xx電商網站的時候,里面有一個數據匯總的功能,在用戶下單之后需要查詢訂單信息,也需要獲得訂單中的商品詳細信息(可能是多個),還需要查看物流發貨信息。因為它們三個對應的分別三個微服務,如果一個一個的操作的話,互相等待的時間比較長。所以,我當時就想到可以使用線程池,讓多個線程同時處理,最終再匯總結果就可以了,當然里面需要用到Future來獲取每個線程執行之后的結果才行
參考場景三:
《黑馬頭條》項目中使用的
我當時做了一個文章搜索的功能,用戶輸入關鍵字要搜索文章,同時需要保存用戶的搜索記錄(搜索歷史),這塊我設計的時候,為了不影響用戶的正常搜索,我們采用的異步的方式進行保存的,為了提升性能,我們加入了線程池,也就說在調用異步方法的時候,直接從線程池中獲取線程使用

6.5 其他

談談你對ThreadLocal的理解

候選人
嗯,是這樣的~~
ThreadLocal 主要功能有兩個,第一個是可以實現資源對象的線程隔離,讓每個線程各用各的資源對象,避免爭用引發的線程安全問題,第二個是實現了線程內的資源共享

好的,那你知道ThreadLocal的底層原理實現嗎?

候選人
嗯,知道一些~
在ThreadLocal內部維護了一個一個 ThreadLocalMap 類型的成員變量,用來存儲資源對象
當我們調用 set 方法,就是以 ThreadLocal 自己作為 key,資源對象作為 value,放入當前線程的 ThreadLocalMap 集合中
當調用 get 方法,就是以 ThreadLocal 自己作為 key,到當前線程中查找關聯的資源值
當調用 remove 方法,就是以 ThreadLocal 自己作為 key,移除當前線程關聯的資源值

好的,那關于ThreadLocal會導致內存溢出這個事情,了解嗎?

候選人
嗯,我之前看過源碼,我想一下~~
是應為ThreadLocalMap 中的 key 被設計為弱引用,它是被動的被GC調用釋放key,不過關鍵的是只有key可以得到內存釋放,而value不會,因為value是一個強引用。
在使用ThreadLocal 時都把它作為靜態變量(即強引用),因此無法被動依靠 GC 回收,建議主動的remove 釋放 key,這樣就能避免內存溢出。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/696860.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696860.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696860.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

高級語言期末2014級A卷

1.編寫函數 int delarr(int a[] ,int n)&#xff0c;刪除有n個元素的正整型數組a中所有素數&#xff0c;要求&#xff1a; 1&#xff09;數組a中剩余元素保持原來次序&#xff1b; 2&#xff09;將處理后的數組輸出&#xff1b; 3&#xff09;函數值返回剩余元素個數&#xff1…

MySQL索引面試題(高頻)

文章目錄 前言什么時候需要&#xff08;不需要&#xff09;)使用索引&#xff1f;有哪些優化索引的方法前綴索引優化索引覆蓋優化索引失效場景 總結 前言 今天來講一講 MySQL 索引的高頻面試題。主要是針對前一篇文章 MySQL索引入門&#xff08;一文搞定&#xff09;進行查漏補…

虛擬機的內存結構

一、摘要 熟悉 Java 語言特性的同學都知道&#xff0c;相比 C、C 等編程語言&#xff0c;Java 無需通過手動方式回收內存&#xff0c;內存中所有的對象都可以交給 Java 虛擬機來幫助自動回收&#xff1b;而像 C、C 等編程語言&#xff0c;需要開發者通過代碼手動釋放內存資源&…

MedicalGPT 訓練醫療大模型,實現了包括增量預訓練、有監督微調、RLHF(獎勵建模、強化學習訓練)和DPO(直接偏好優化)

MedicalGPT 訓練醫療大模型&#xff0c;實現了包括增量預訓練、有監督微調、RLHF(獎勵建模、強化學習訓練)和DPO(直接偏好優化)。 MedicalGPT: Training Your Own Medical GPT Model with ChatGPT Training Pipeline. 訓練醫療大模型&#xff0c;實現了包括增量預訓練、有監督微…

Linux第63步_為新創建的虛擬機添加必要的目錄和安裝支持linux系統移植的軟件

1、創建必要的目錄 1)、創建“/home/zgq/linux/”目錄 打開終端&#xff0c;進入“/home/zgq/”目錄 輸入“mkdir linux回車”&#xff0c;創建“/home/zgq/linux/”目錄 輸入“ls回車”&#xff0c;列舉“/home/zgq/”目錄的所有文件和文件夾 創建好“/home/zgq/linux/”…

EIS(防抖):meshflow算法 C++實現

視頻防抖的應用 對視頻防抖的需求在許多領域都有。 這在消費者和專業攝像中是極其重要的。因此&#xff0c;存在許多不同的機械、光學和算法解決方案。即使在靜態圖像拍攝中&#xff0c;防抖技術也可以幫助拍攝長時間曝光的手持照片。 在內窺鏡和結腸鏡等醫療診斷應用中&…

Go 中的 init 如何用?它的常見應用場景有哪些呢?

嗨&#xff0c;大家好&#xff01;我是波羅學。本文是系列文章 Go 技巧第十六篇&#xff0c;系列文章查看&#xff1a;Go 語言技巧。 Go 中有一個特別的 init() 函數&#xff0c;它主要用于包的初始化。init() 函數在包被引入后會被自動執行。如果在 main 包中&#xff0c;它也…

QT基本組件

四、基本組件 Designer 設計師&#xff08;重點&#xff09; Qt包含了一個Designer程序&#xff0c;用于通過可視化界面設計開發界面&#xff0c;保存文件格式為.ui&#xff08;界面文件&#xff09;。界面文件內部使用xml語法的標簽式語言。 在Qt Creator中創建文件時&#xf…

滾雪球學Java(67):深入理解 TreeMap:Java 中的有序鍵值映射表

咦咦咦&#xff0c;各位小可愛&#xff0c;我是你們的好伙伴——bug菌&#xff0c;今天又來給大家普及Java SE相關知識點了&#xff0c;別躲起來啊&#xff0c;聽我講干貨還不快點贊&#xff0c;贊多了我就有動力講得更嗨啦&#xff01;所以呀&#xff0c;養成先點贊后閱讀的好…

機器人內部傳感器閱讀筆記及心得-位置傳感器-旋轉變壓器、激光干涉式編碼器

旋轉變壓器 旋轉變壓器是一種輸出電壓隨轉角變化的檢測裝置&#xff0c;是用來檢測角位移的&#xff0c;其基本結構與交流繞線式異步電動機相似&#xff0c;由定子和轉子組成。 旋轉變壓器的原理如圖1所示&#xff0c;定子相當于變壓器的一次側&#xff0c;有兩組在空間位置上…

MyBatis-Plus 優雅實現數據加密存儲

文章目錄 前言一、數據庫字段加解密實現1. 定義加密類型枚舉2. 定義AES密鑰和偏移量3. 配置定義使用的加密類型4. 加密解密接口5. 解密解密異常類6. 加密解密實現類6.1 AES加密解密實現類6.2 Base64加密解密實現類 7. 實現數據庫的字段保存加密與查詢解密處理類8. MybatisPlus配…

使用python進行量化交易

yfinance yfinance國內不能使用&#xff0c;可以使用tushare、akshare代替 import yfinance as yf# 輸入股票代碼 stock_symbol AAPL # 替換為你想要查詢的股票代碼# 獲取股票數據 data yf.download(stock_symbol)# 打印實時數據 print(data)pip install akshare import …

Selenium安裝與配置

文章目錄 一、selenium安裝1. Python環境準備&#xff1a;2. 安裝Selenium&#xff1a;3. 瀏覽器驅動安裝&#xff1a;4. 驗證安裝&#xff1a; 二、常見問題1. Selenium版本與瀏覽器驅動程序不兼容&#xff1a;2. 瀏覽器驅動程序路徑未正確設置&#xff1a; Selenium是一個用于…

2024年1月手機市場行業分析:蘋果手機份額驟降,國產高端手機成功逆襲!

小米Ultra發布。 一方面&#xff0c;我們有望看到國產手機再一次超越自己的決心&#xff0c;繼續創新追逐高端&#xff1b;另一方面&#xff0c;我們也不得不正視目前手機市場所面臨的危機狀態。 2024年1月的線上手機市場遠不如去年。根據鯨參謀數據顯示&#xff0c;今年1月京…

Qt(C++)面試題 | 精選25項常問

面試是每個求職者都必須經歷的一關,而QT面試更是需要面試者有深厚的編程基礎和豐富的實戰經驗。下面我們為大家整理了25道QT面試題,希望能夠幫助大家在求職路上獲得成功。 ?Qt 中常用的五大模塊是哪些? Qt 中常用的五大模塊包括: QtCore:提供了 Qt 的核心功能,例如基本的…

Java面試題之分布式/微服務篇

經濟依舊不景氣啊&#xff0c;如此大環境下Java還是這么卷&#xff0c;又是一年一次的金三銀四。 兄弟們&#xff0c;你準備好了嗎&#xff1f;沖沖沖&#xff01;歐里給&#xff01; 分布式/微服務相關面試題解 題一&#xff1a;CAP理論&#xff0c;BASE理論題二&#xff1a;…

深度神經網絡

包括&#xff1a;深度前饋神經網絡、深度卷積神經網絡、深度循環神經網絡 深度神經網絡全面概述&#xff1a;從基本概念到實際模型和硬件基礎-騰訊云開發者社區-騰訊云

MQL語言實現JSON協議庫

文章目錄 一、MQL語言實現JSON協議的意義二、定義JSON數據枚舉類型簡單數據類型復雜數據類型枚舉數據類型定義類變量清理與賦值方法構造與析構方法重載運算符添加與設置方法序列化與反序列方法 一、MQL語言實現JSON協議的意義 數據交互&#xff1a;JSON是一種輕量級的數據交換格…

【2024軟件測試面試必會技能】Postman(1): postman的介紹和安裝

Postman的介紹 Postman 是一款谷歌開發的接口測試工具,使API的調試與測試更加便捷。 它提供功能強大的 Web API & HTTP 請求調試。它能夠發送任何類型的HTTP 請求 (GET, HEAD, POST, PUT..)&#xff0c;附帶任何數量的參數 headers。 postman是一款支持http協議的接口調試…

【PTA|函數題|期末復習】指針

目錄 6-1 計算兩數的和與差&#xff08;5分&#xff09; 函數接口定義&#xff1a; 裁判測試程序樣例&#xff1a; 輸入樣例&#xff1a; 輸出樣例&#xff1a; 代碼 6-2 拆分實數的整數與小數部分 (5分) 函數接口定義&#xff1a; 裁判測試程序樣例&#xff1a; 輸入…