0X JavaSE-- 并发编程(创建线程、控制线程、获取线程结果、线程六状态及切换)

进程和线程

其实大部分都是 OS 中的内容。

  • 线程在进程下进行
  • 进程之间不会相互影响,但进程结束会导致从属的所有线程结束。
  • 不同的进程数据很难共享
  • 同进程下的不同线程之间数据很容易共享
  • 进程使用内存地址可以限定使用量

线程生命周期

在这里插入图片描述

线程的六种状态及切换

在操作系统中,线程被视为轻量级的进程,以下是进程五种状态。
在这里插入图片描述
JVM 中的线程,定义了六种状态:

// Thread.State 源码
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

NEW

处于 NEW 状态的线程此时尚未启动。
这里的尚未启动指的是还没调用 Thread 实例的start()方法。

// 只是创建了线程而并没有调用 start 方法,此时线程处于 NEW 状态。
private void testStateNew() {
    Thread thread = new Thread(() -> {});
    System.out.println(thread.getState()); // out: NEW
}

RUNNABLE

/**
 * A thread in the runnable state is executing in the JVM
 * but it may be waiting for other resources from the operating system.   
 * such as processor.
 */

也就是说,JVM线程的 RUNNABLE状态其实包括了 OS进程的 ready 和 running 两个状态。

BLOCKED

处于 BLOCKED 状态的线程正等待锁(锁会在后面细讲)的释放以进入同步区。

WAITING

所谓等待态,等待的是转变为 RUNNABLE状态,但是变成 RUNNABLE 状态需要其他线程唤醒。

调用下面这 3 个方法会使线程进入等待状态:

Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;
Thread.join():底层调用的是 Object 的 wait 方法;后有叙述
LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。LockSupport 我们在后面会细讲。

TIMED_WAITING

超时等待状态。
线程等待一个具体的时间,时间到后会被自动唤醒。

调用如下方法会使线程进入超时等待状态:

Thread.sleep(long millis):使当前线程睡眠指定时间;
Object.wait(long timeout):线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒;
Thread.join(long millis):带上参数后的 join,进入的就是超时等待态了。
LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;LockSupport 我们在后面会细讲;
LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;

TERMINATED

终止状态。此时线程已执行完毕。

线程状态的转换

在这里插入图片描述
可以看到,RUNNABLE 实质上是线程六状态的核心,其与 BLOCKED、WATING、TIMED-WATING两两之间都存在互相转换的方法。

BLOCKED 与 RUNNABLE状态的转换

我们在上面说过:处于 BLOCKED 状态的线程在等待锁的释放。

假如这里有两个线程 a 和 b,a 线程提前获得了锁并暂未释放锁,此时 b 就处于 BLOCKED 状态。

题设:以下代码创建了两个子线程,这两个子线程的目标是运行 testMethod()方法。但是该方法已经加了锁(Synchronized),因此,相当于两个子线程抢占锁去执行方法。

问题:在下面的例子中,两句输出的内容为什么?

@Test
public void blockedTest() {
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            testMethod();
        }
    }, "threadA");

    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            testMethod();
        }
    }, "threadB");

    a.start();
    b.start();

    System.out.println(a.getName() + ":" + a.getState()); // 输出?
    System.out.println(b.getName() + ":" + b.getState()); // 输出?
}

// 同步方法争夺锁
private synchronized void testMethod() {
    try {
        Thread.sleep(2000L);// 模拟任务耗时
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

思考1:
线程 A 先创建,创建后运行 run方法,因此自然能抢到锁去运行测试方法。
由于测试方法强制休眠 2秒,在这个过程整个程序其他部分已经执行完了。
因此输出 A 为TIMED_WAITING ,而 B 由于等待锁,自然是 BLOCKED.

其实不然,有两点需要注意:

一是不能忽略 main线程
二是启动线程后执行 run方法还是需要消耗一定时间的。
测试方法的 main 线程只保证了 a,b 两个线程调用 start 方法(转化为 RUNNABLE 状态)

如果 CPU 执行效率高一点,还没等两个线程真正开始争夺锁(开始 run方法),就已经打印此时两个线程的状态(RUNNABLE)了。
如果 CPU 执行效率低一点,其中某个线程也是可能打印出 BLOCKED 状态的(此时两个线程已经开始争夺锁了)。

BLOCKED 状态的产生需要两个线程争夺锁才行。那我们处理下测试方法里的 main 线程就可以了,让它“休息一会儿”,调用 Thread.sleep()方法即可。

public void blockedTest() throws InterruptedException {
    ······
    a.start();
    Thread.sleep(1000L); // 需要注意这里main线程休眠了1000毫秒,而testMethod()里休眠了2000毫秒
    b.start();
}

这样,就是确保运行结果与我们预期一致:A 为 TIME-WAITING,B 为 BLOCKED。
在这个例子中两个线程的状态转换如下

a 的状态转换过程:RUNNABLE(a.start()) -> TIMED_WATING(Thread.sleep())->RUNABLE(sleep()时间到)->BLOCKED(未抢到锁) -> TERMINATED
b 的状态转换过程:RUNNABLE(b.start()) -> BLOCKED(未抢到锁) ->TERMINATED
同样,这里的输出也可能有多钟结果。

WAITING 状态与 RUNNABLE 状态的转换

根据转换图我们知道有 3 个方法可以使线程从 RUNNABLE 状态转为 WAITING 状态。

  1. Object.wait :调用 wait方法前线程必须持有对象的锁。
    线程调用 wait方法时,会释放当前的锁,直到有其他线程调用 notify()/notifyAll()方法唤醒等待锁的线程。

需要注意的是,其他线程调用 notify()方法只会唤醒单个等待锁的线程,如有有多个线程都在等待这个锁的话不一定会唤醒到之前调用wait()方法的线程。
同样,调用 notifyAll()方法唤醒所有等待锁的线程之后,也不一定会马上把时间片分给刚才放弃锁的那个线程,具体要看系统的调度。

此外,有参的 wait方法会使线程进入 TIMED-WATING。

  1. Thread.join()
    假设 A线程正在占用 CPU,运行 B.join方法,则 A线程会把 CPU 让出,直到 B线程执行完毕(转换为 TERMINATED 状态)。

若带有时间参数,则指定等待这么一段时间,进入的就是 TIMED-WATING状态。

TIMED_WAITING 与 RUNNABLE 状态转换

TIMED_WAITING 与 WAITING 状态类似,只是 TIMED_WAITING 状态等待的时间是指定的。

  1. Thread.sleep(long)
    使当前线程睡眠指定时间。需要注意这里的“睡眠”只是暂时使线程停止执行,并不会释放锁。时间到后,线程会重新进入 RUNNABLE 状态。

  2. Object.wait(long)
    wait(long)方法使线程进入 TIMED_WAITING状态。这里的wait(long)方法与无参方法 wait()相同的地方是,都可以通过其他线程调用notify()或notifyAll()方法来唤醒。

不同的地方是,有参方法 wait(long)就算其他线程不来唤醒它,经过指定时间 long 之后它会自动唤醒,拥有去争夺锁的资格。

  1. Thread.join(long)
    join(long)使当前线程执行指定时间,并且使线程进入 TIMED_WAITING 状态。

线程中断

在某些情况下,我们在线程启动后发现并不需要它继续执行下去时,需要中断线程。目前在 Java 里还没有安全方法来直接停止线程,但是 Java 提供了线程中断机制来处理需要中断线程的情况。

需要注意,通过中断操作并不能直接终止一个线程,而是通知需要被中断的线程自行处理。
通知的形式即:将线程的中断状态设置为 true,让线程自行决定如何中断。

Thread.interrupt():中断线程。这里的中断线程并不会立即停止线程,而是设置线程的中断状态为 true(默认是 flase);
Thread.currentThread().isInterrupted():测试当前线程是否被中断。线程的中断状态会受这个方法的影响,调用一次可以使线程中断状态变为 true,调用两次会使这个线程的中断状态重新转为 falseThread.isInterrupted():测试当前线程是否被中断。与上面方法不同的是调用这个方法并不会影响线程的中断状态。

用户线程和守护线程

  • 用户线程:运行在前台的线程,当所有用户线程结束时,JVM退出。
  • 守护线程:运行在后台的线程,当所有用户线程结束时,守护线程自动终止。守护线程是一种特殊的线程,它的存在是为了服务于用户线程,当所有用户线程都结束时,守护线程会自动终止。

创建进程的三种方式

继承 Thread 类

创建一个 A类继承 Thread 类,并重写 run 方法;
此后,需要新建线程时,new A类即可获得一个线程。

// 创建三个线程,分别执行各自的run方法。每个线程会打印100次消息,包括线程名和计数器的当前值。

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":第" + i + "次运行");
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        //创建 MyThread 对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        //设置线程的名字
        t1.setName("1号线程");
        t2.setName("2号线程");
        t3.setName("3号线程");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

对结果的分析:

  • 每个线程独立运行,且不依赖于其他线程的状态。
  • 输出顺序不固定,具体执行顺序取决于线程调度。
    在这里插入图片描述

实现 Runnable 接口

创建一个 A类实现 Runnable 接口,并重写 run 方法;
此后,需要新建线程时,传入 A类实例作为参数 New Thread 即可获得一个线程。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":第" + i + "次运行");
        }
    }
}

//创建MyRunnable类
MyRunnable mr = new MyRunnable();
//创建 Thread 类的有参构造,第二个参数指定线程名
Thread t1 = new Thread(mr, "1号线程");
Thread t2 = new Thread(mr, "2号线程");
Thread t3 = new Thread(mr, "3号线程");
//启动线程
t1.start();
t2.start();
t3.start();

实现 Callable 接口

实现 Callable 接口,重写 call方法,这种方式可以通过 FutureTask 获取任务执行的返回值。

public class CallerTask implements Callable<String> {
    public String call() throws Exception {
        return "Hello,i am running!";
    }

    public static void main(String[] args) {
        //创建异步任务
        FutureTask<String> task=new FutureTask<String>(new CallerTask());
        //启动线程
        new Thread(task).start();
        try {
            //等待执行完成,并获取返回结果
            String result=task.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

获取线程执行结果

先前提到:在直接继承 Thread和实现 Runnable 接口中,我们更倾向于后者。
然而,这两种方法都有一个缺陷:线程结束后之后无法获取执行结果。

// Runnable 的 run 方法的返回值为 void,注定线程结束后无法获取执行结果.
public interface Runnable {
    public abstract void run();
}

如果需要获取执行结果,就必须通过共享变量或者线程通信的方式来达到目的,这样使用起来就比较麻烦。

J5 提供了 Callable、Future、FutureTask,它们可以在任务执行完后得到执行结果。

有返回值的 Callable

Callable 接口位于 java.util.concurrent 包下。它定义了一个 call方法,由于 call方法可以返回任意值类型,自然,Callable任务可以返回任意值类型。

// call 方法的返回值为: V 类型(值类型)
public interface Callable<V> {
    V call() throws Exception;
}
  • Callable 一般会配合 ExecutorService 使用。
    • ExecutorServiceExecutor 都是接口,并且 ExecutorService 扩展Executor。二者位于 java.util.concurrent 包下。是 Java 线程池框架的核心接口,用来异步执行任务。(后面在线程池的时候会细讲,这里记住就行)
  • Callable接口相较于Runnable接口主要有以下几点优势:
    • 与 Future 和 FutureTask 的集成:由于 Callable可以返回结果,它通常与 Future 和 FutureTask 类一起使用。FutureTask 是一个包装器,它同时实现了 Runnable 和 Future 接口,允许你启动一个 Callable 任务,并且可以获取到这个任务的 Future 对象。通过 Future 对象,可以查询任务是否完成、取消任务、获取任务的结果(这可能需要阻塞直到结果可用)。这种机制非常适合管理异步任务和获取它们的执行结果。

demo

需求:创建一个线程,在线程 run/call中实现加法,得到的结果传出线程后,再输出。

Callable接口示例中,call方法能够返回加法的结果,并且我们可以通过 Future.get方法来获取这个结果。这样,即使是在异步执行任务的情况下,也能方便地得到并处理任务的输出,这是 Runnable接口所不具备的特性。

public class {
    public static void main(String[] args) {
	        ExecutorService executorService = Executors.newSingleThreadExecutor();

            // 创建 Callable任务
            Callable<Integer> cTask = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int number1 = 5;
                    int number2 = 7;
                    return number1 + number2; // 直接在call方法中返回加法结果
                }
            };

            // 提交任务到线程池并获取Future对象
            Future<Integer> future = executorService.submit(cTask);

            try {
                // 从 Future对象中获取加法结果
                Integer result = future.get();
                System.out.println("加法结果为: " + result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } finally {
                // 关闭线程池
                executorService.shutdown();
            }
    }
}

在 Runnable 实现中,由于 run方法没有返回值,只能借助共享变量的方式传输结果。

public class RunnableExample implements Runnable {
    private int number1;
    private int number2;
    public static int result; // 共享结果变量

    public RunnableExample(int num1, int num2) {
        this.number1 = num1;
        this.number2 = num2;
    }

    @Override
    public void run() {
        result = number1 + number2;
    }

    public static void main(String[] args) {
        // 创建 Runnable实例
        RunnableExample rTask = new RunnableExample(5, 7);
        // 创建一个新的线程来执行加法任务
        Thread rThread = new Thread(rTask);
        // 启动线程
        rThread.start();
        
        // 确保加法线程执行完毕(这里简单用join方法等待,实际应用中可能需要更复杂的同步机制)
        try {
            rThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 打印最终结果
        System.out.println("主线程获取到的加法结果为: " + RunnableExample.result);
    }
}

异步计算结果 Future 接口

在前面的例子中,我们通过 Future 来获取 Callable 任务的执行结果。

  • Future 位于 java.util.concurrent 包下,它是一个接口
    • 接口自然没有构造方法,因为接口不能被实例化。通常情况下,Future对象是通过 ExecutorService.submit 得到的,或者通过 FutureTask类来实现和使用。
  • Future 提供了三种功能:
    • 判断任务是否完成;
    • 取消任务/判断任务是否被取消;
    • 获取任务执行结果。
public interface Future<V> {
// 如果取消任务成功则返回 true,如果取消任务失败则返回 false。参数 mayInterruptIfRunning 表示是否允许取消正在执行却没有执行完毕的任务
    boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
    boolean isCancelled();
    boolean isDone();
// 获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;    
    V get() throws InterruptedException, ExecutionException;
// 获取执行结果,如果在指定时间内,还没获取到结果,就直接返回 null。
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
// 使用 Future 的第一种方法:executorService.submit()返回的是一个 Future对象
		ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable<Integer> cTask = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {   }
        };
        
        Future<Integer> future = executorService.submit(cTask);
      
 // 使用 Future 的第二种方法:通过 FutureTask类来实现和使用
 // 后面可知,FutureTask 实际上是 Future 的唯一实现
 // 因此,用 FutureTask 调用 FutureAPI 的过程,就是使用 Future.

异步计算结果 FutureTask 实现类

  • 由于 Future 只是一个接口,不允许 new 出实例。
  • FutureTask 实现了 RunnableFuture接口 --> 而RunnableFuture接口扩展了 Runnable 和 Future接口。
    • 因此实际上,FutureTask 是 Future 接口的唯一实现类。
    • 所以它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

FutureTask 的实现:

public class FutureTask<V> implements RunnableFuture<V>

FutureTask 类实现了 RunnableFuture 接口, RunnableFuture 接口的实现:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

Constructor

// Callable 可以返回结果并且可以抛出异常。
// 希望任务完成后返回结果的,使用这个构造方法
public FutureTask(Callable<V> callable) {}
// Runnable 不返回结果也不抛出异常;result参数是在任务成功完成后通过 FutureTask.get方法返回的结果。
// 当希望执行一个 Runnable任务,但仍然需要在任务完成后返回一个固定的结果时,使用这个构造方法。
public FutureTask(Runnable runnable, V result) {}

demo

当需要异步执行一个计算,并在稍后的某个时间点获取其结果时,就可以使用 FutureTask。

// 创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);

// 创建一系列 Callable
Callable<Integer>[] tasks = new Callable[5];
for (int i = 0; i < tasks.length; i++) {
    final int index = i;
    tasks[i] = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            TimeUnit.SECONDS.sleep(index + 1);
            return (index + 1) * 100;
        }
    };
}

// 将 Callable 包装为 FutureTask,并提交到线程池
FutureTask<Integer>[] futureTasks = new FutureTask[tasks.length];
for (int i = 0; i < tasks.length; i++) {
    futureTasks[i] = new FutureTask<>(tasks[i]);
    executorService.submit(futureTasks[i]);
}

// 获取任务结果
for (int i = 0; i < futureTasks.length; i++) {
    System.out.println("Result of task" + (i + 1) + ": " + futureTasks[i].get());
}

// 关闭线程池
executorService.shutdown();
Result of task1: 100
Result of task2: 200
Result of task3: 300
Result of task4: 400
Result of task5: 500
// 使用 FutureTask 的第二种构造方法.这种方法不能动态输出不同结果.
// 创建一个 Runnable 任务
        Runnable runnable = new Runnable() {
            public void run() {
                System.out.println("Running the task...");
                // 任务的具体操作.....
                System.out.println("Task completed.");
            }
        };

        // 使用 Runnable 创建 FutureTask,并指定结果
        FutureTask<String> futureTask = new FutureTask<>(runnable, "Result");

        // 创建并启动一个线程来执行 FutureTask
        Thread thread = new Thread(futureTask);
        thread.start();

        try {
            // 获取任务结果
            String result = futureTask.get();
            System.out.println("Task result: " + result);// out : Task result:result:
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

一些补充说明

  • 为什么要重写 run 方法?
    • 因为默认的 run 方法不会做任何事情。为了让线程执行一些实际的任务,我们需要提供自己的 run 方法实现,这就需要重写 run 方法。
// 在这个例子中,我们重写了run()方法,使其打印出一条消息。
// 当我们创建并启动这个线程的实例时,它就会打印出这条消息。
public class MyThread extends Thread {
  public void run() {
    System.out.println("MyThread running");
  }
}
  • run 方法和 start 方法的区别:
    • start 方法:调用 start 方法后,JVM 会为新线程分配资源,并在后台启动一个新的执行路径。然后,run 方法将在新线程中执行。
    • run 方法:直接调用 run 方法不会创建新线程。它只是像普通方法一样在当前线程中执行。
  • 通过继承 Thread 的方法和实现 Runnable 接口的方式创建多线程,后者更好。
    • 尽管 Runnable 代码相较之下更加复杂,仍推荐使用。
    • Runnable 避免了 Java 单继承的局限性。由于 Java 不支持多重继承,因此如果 A类已经继承了另一个类,就不能再继承 Thread 类了。
    • Runnable 适合资源共享。同一个 Runnable 实例可以被多个线程共享。Callable 接口与 Runnable 非常相似,但可以返回一个结果。

控制线程的方法

  • sleep :使当前正在执行的线程暂停指定的毫秒数,也就是进入休眠的状态。使用 sleep 要处理异常。
  • join:调用 join 方法的线程会强制占用 CPU ,直到执行完成才允许别的线程抢占 CPU。使用 join 要处理异常。
  • setDaemon:将此线程标记为守护线程。像 Java 中的垃圾回收线程,就是典型的守护线程。
  • yield 是一个静态方法,用于提示 JVM 当前线程愿意放弃其当前的时间片,允许其他线程执行。提示二字意味着:它只是向线程调度器提出建议,调度器可能会忽略这个建议。具体行为取决于操作系统和 JVM 的线程调度策略。
try {//sleep 会发生异常要处理
    Thread.sleep(20);//暂停20毫秒
} catch (InterruptedException e) {
    e.printStackTrace();
}

        //创建MyRunnable类
        MyRunnable mr = new MyRunnable();
        //创建 Thread 类的有参构造,并设置线程名
        Thread t1 = new Thread(mr, "1号线程");
        Thread t2 = new Thread(mr, "2号线程");
        Thread t3 = new Thread(mr, "3号线程");
        //启动线程
        t1.start();
        try {
            t1.join(); //等待 t1 执行完才会轮到 t2,t3 抢
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
        t3.start();

多线程带来的问题

在这里插入图片描述

可见性

可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

每个线程都有属于自己的工作内存,工作内存和主内存间需要通过 store 和 load 等进行交互。

为了解决多线程的可见性问题,Java 提供了 volatile关键字。
当一个共享变量被 volatile 修饰时,它会保证修改的值立即更新到主存当中,这样的话,当有其他线程需要读取时,就会从内存中读到新值。

普通的共享变量不能保证可见性,因为变量被修改后什么时候刷回到主存是不确定的,因此另外一个线程读到的可能就是旧值。

当然 Java 的锁机制如 synchronized 和 lock 也是可以保证可见性的。

活跃性问题

上面讲到为了解决可见性的问题,我们可以采取加锁的方式来解决,但如果加锁使用不当也容易引入其他问题,比如『死锁』。

在讲『死锁』之前,需要先引入另外一个概念:活跃性问题。
活跃性是指某件正确的事情最终会发生,但当某个操作无法继续下去的时候,就会发生活跃性问题。

  • 说程序具有活跃性,实质是说该程序内所有进程所需的资源都能得到满足,以完整运行整个程序。
  • 死锁:多个线程因为环形等待锁的关系而永远地阻塞下去。
  • 活锁:线程没有阻塞,个进程或线程虽然能够继续执行,但由于频繁地进行状态切换或者处理冲突,实际上没有进展。类似于两个让道的行人,都试图避开对方,结果不断改变方向但始终无法通过。
  • 饥饿:如果一个线程无其他异常却迟迟不能继续运行,那基本上是处于饥饿状态了。

上下文切换开销

前面讲到了线程安全和死锁、活锁这些问题。

但即使这些都没有发生,多线程并发不一定比单线程串行执行更快。
因为多线程有创建线程和线程上下文切换的开销。

CPU 为了保证雨露均沾,通常会给不同的线程分配时间片。
当 CPU 从执行一个线程切换到执行另一个线程时,CPU 需要保存当前线程的本地数据,程序指针等状态,并加载下一个要执行线程的本地数据,程序指针等,也就是『上下文切换』。

减少上下文切换的方法有:

  • 无锁并发编程:可以参照 ConcurrentHashMap 中锁分段的思想:不同的线程处理不同段的数据,这样在多线程竞争的条件下,可以减少上下文切换的时间。
  • 利用 Atomic + CAS 算法来更新数据,采用乐观锁的方式,可以有效减少一部分不必要的锁竞争带来的上下文切换。(后面详述)
  • 使用最少线程:这个好理解。
  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值