1.在單處理器時期,操做系統就能處理多線程併發任務,處理器給每一個線程分配CPU時間片,線程在CPU時間片內執行任務linux
2.時間片決定了一個線程能夠連續佔用處理器運行的時長算法
3.上下文的內容編程
4.當CPU數量遠遠不止1個的狀況下,操做系統將CPU輪流分配給線程任務,此時的上下文切換會變得更加頻繁緩存
1.在操做系統中,上下文切換的類型能夠分爲進程間的上下文切換和線程間的上下文切換bash
2.線程狀態:NEW、RUNNABLE、RUNNING、BLOCKED、DEAD多線程
3.線程上下文切換:RUNNING -> BLOCKED -> RUNNABLE -> 被調度器選中執行併發
4.誘因:程序自己觸發的自發性上下文切換、系統或虛擬機觸發的非自發性上下文切換ide
樣例代碼高併發
public static void main(String[] args) {
new MultiThreadTesterAbstract().start();
new SerialThreadTesterAbstract().start();
// multi thread take 5401ms
// serial take 692ms
}
static abstract class AbstractTheadContextSwitchTester {
static final int COUNT = 100_000_000;
volatile int counter = 0;
void increaseCounter() {
counter++;
}
public abstract void start();
}
static class MultiThreadTesterAbstract extends AbstractTheadContextSwitchTester {
@Override
public void start() {
Stopwatch stopwatch = Stopwatch.createStarted();
Thread[] threads = new Thread[4];
for (int i = 0; i < 4; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
while (counter < COUNT) {
synchronized (this) {
if (counter < COUNT) {
increaseCounter();
}
}
}
}
});
threads[i].start();
}
for (int i = 0; i < 4; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("multi thread take {}ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
}
static class SerialThreadTesterAbstract extends AbstractTheadContextSwitchTester {
@Override
public void start() {
Stopwatch stopwatch = Stopwatch.createStarted();
for (int i = 0; i < COUNT; i++) {
increaseCounter();
}
log.info("serial take {}ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
}
1.串行的執行速度比並發執行的速度要快,由於線程的上下文切換致使了額外的開銷工具
2.Redis的設計很好地體現了單線程串行的優點
vmstat
cs:系統的上下文切換頻率
root@5d15480e8112:/# vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
3 0 0 693416 33588 951508 0 0 77 154 116 253 1 1 98 0 0
pidstat
-w Report task switching activity (kernels 2.6.23 and later only). The following values may be displayed:
UID
The real user identification number of the task being monitored.
USER
The name of the real user owning the task being monitored.
PID
The identification number of the task being monitored.
cswch/s
Total number of voluntary context switches the task made per second. A voluntary context switch occurs when a task blocks because it requires a
resource that is unavailable.
nvcswch/s
Total number of non voluntary context switches the task made per second. A involuntary context switch takes place when a task executes for the
duration of its time slice and then is forced to relinquish the processor.
Command
The command name of the task.
root@5d15480e8112:/# pidstat -w -l -p 1 2 5
Linux 4.9.184-linuxkit (5d15480e8112) 09/16/2019 _x86_64_ (2 CPU)
07:28:03 UID PID cswch/s nvcswch/s Command
07:28:05 0 1 0.00 0.00 /bin/bash
07:28:07 0 1 0.00 0.00 /bin/bash
07:28:09 0 1 0.00 0.00 /bin/bash
07:28:11 0 1 0.00 0.00 /bin/bash
07:28:13 0 1 0.00 0.00 /bin/bash
Average: 0 1 0.00 0.00 /bin/bash
鎖分離
鎖分段
能夠經過Object對象的wait、notify、notifyAll來實現線程間的通訊,例如生產者-消費者模型
public class WaitNotifyTest {
public static void main(String[] args) {
Vector<Integer> pool = new Vector<>();
Producer producer = new Producer(pool, 10);
Consumer consumer = new Consumer(pool);
new Thread(producer).start();
new Thread(consumer).start();
}
}
@AllArgsConstructor
class Producer implements Runnable {
private final Vector<Integer> pool;
private Integer size;
@Override
public void run() {
for (; ; ) {
try {
produce((int) System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void produce(int i) throws InterruptedException {
while (pool.size() == size) {
synchronized (pool) {
pool.wait();
}
}
synchronized (pool) {
pool.add(i);
pool.notifyAll();
}
}
}
@AllArgsConstructor
class Consumer implements Runnable {
private final Vector<Integer> pool;
@Override
public void run() {
for (; ; ) {
try {
consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void consume() throws InterruptedException {
synchronized (pool) {
while (pool.isEmpty()) {
pool.wait();
}
}
synchronized (pool) {
pool.remove(0);
pool.notifyAll();
}
}
}
1.wait/notify的使用致使了較多的上下文切換
2.消費者第一次申請到鎖,卻發現沒有內容可消費,執行wait,這會致使線程掛起,進入阻塞狀態,這是一次上下文切換
3.當生產者得到鎖並執行notifyAll以後,會喚醒處於阻塞狀態的消費者線程,又會發生一次上下文切換
4.被喚醒的線程在繼續運行時,須要再次申請相應對象的內部鎖,此時可能須要與其餘新來的活躍線程競爭,致使上下文切換
5.若是多個消費者線程同時被阻塞,用notifyAll將喚醒全部阻塞線程,但此時依然沒有內容可消費
6.優化方法