java多线程

线程六种状态

  1. New:尚未启动的线程的线程状态(new Thread)
  2. Runnable:可运行线程的线程状态,等待cpu调度(调用star)
  3. Blokcked: 线程阻塞等待监视器锁定的线程专状态,处于synchronized同步代码块或方法中被阻塞。
  4. Waiting: 线程等待的线程状态。
    不带timeout参数的方式调用Object.wait, thread.jion, LockSupport.park
  5. Timed Waiting: 具有指定等待时间的等待线程的线程状态,下列超时的方式:Thread.sleep, Object.wait, Thread.join, LockSupport.parkNanos, LockSupport.parkUntil
  6. Terminated: 终止线程的线程状态, 线程正常完成执行或者出现异常
public class ScannerTest {
    public static void main(String[] arg) throws InterruptedException {
        test1();
    };
    
    public static void test1() throws InterruptedException{
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1运行");
            }
        });
        System.out.println("1调用前状态"+ thread1.getState().toString());
        Thread.sleep(1000L);
        thread1.start();
        System.out.println("2调用后状态"+ thread1.getState().toString());
        Thread.sleep(2000L);
        System.out.println("2s后的状态" + thread1.getState().toString());
    }
}

/**
1调用前状态NEW
2调用后状态RUNNABLE
线程1运行
2s后的状态TERMINATED
**/
public static void test2() throws InterruptedException {
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                    System.out.println("线程2运行" + Thread.currentThread().getState());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        thread2.start();
        System.out.println("调用后状态:" + thread2.getState().toString());
        Thread.sleep(2000L);
        System.out.println("等待2s后状态:" + thread2.getState().toString());
    }
/*
调用后状态:RUNNABLE
等待2s后状态:TIMED_WAITING
线程2运行RUNNABLE
*/

public static void test3() throws InterruptedException {
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("抢锁之前状态" + Thread.currentThread().getState());
                synchronized (ThreadTest.class) {
                    System.out.println("拿到锁继续执行" + Thread.currentThread().getState());
                }
            }

        });
        synchronized(ThreadTest.class) {
            thread3.start();
            Thread.sleep(2000L);
            System.out.println("调用等待2s状态" + thread3.getState().toString());
            Thread.sleep(4000L);
            
        }
    }
/*
抢锁之前状态RUNNABLE
调用等待2s状态BLOCKED
拿到锁继续执行RUNNABLE

*/

终止线程

1.过时的stop方法
如果线程里面的程序没有执行完,会被终止点,后面的不在继续执行

public class StopThread {
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.stop();
        while(thread.isAlive()) {} // 确保线程被终止了,
        //打印结果
        thread.print();
    }
}

class MyThread extends Thread{
    private int i=0,j=0;
    @Override
    public void run(){
        synchronized(this) {
            ++i;
            try {
                Thread.sleep(5000);
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            ++j;
        }
        System.out.println("释放");
    }
    public void print() {
        System.out.println("i=" + i + "j="+j);
    }
}

// i=1j=0

2.interrupt终止
把上面代码的thread.stop(); 换成 thread.interrupt();
会报错, 可以看到虽然终止了程序但是后面依然执行了,可以再异常里面做其他处理

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
释放
    at com.Hello.ScannerTest.MyThread.run(StopThread.java:23)
i=1j=1
  1. 用标志位的方式中断线程
    在线程外面用一个标志来判断线程是否可执行
public class StopTh {
    public volatile static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            try {
                while(flag) {
                    System.out.println("运行");
                    Thread.sleep(1000L);
                }
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(3000L);
        flag = false;
        System.out.println("运行结束");
    }
}

/*运行
运行
运行
运行结束
*/

线程封闭

多线程访问共享可变数据时,涉及到线程间数据同步问题
并不是所有时候,都要用到共享数据,若数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭

ThreadLocal

使用ThreadLocal 就算一个线程改变了值,但是对其他线程获取这个值原来的数据 没有任何影响

public class ThreadLocalTest {
    public static ThreadLocal<String> value = new ThreadLocal<>();
    
    public static void main(String[] arg0) {
        new Thread(new Runnable() {
            @Override
            public void run () {
                value.set("11111");
                try {
                    Thread.sleep(1000L);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(value.get());
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run () {
                try {
                    Thread.sleep(1000L);
                    System.out.println(value.get());
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

// 11111
// null

栈封闭

局部变量的固有属性之一就是封闭在线程中
他们位于线程的栈中,其他线程无法访问这个栈

线程池

线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务,
工作线程:线程池中线程,可以虚幻的执行任务,在没有任务时处于等待状态;
任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,他主要规定了任务的入口,任务完成后的收尾工作,任务的执行状态等;
任务队列:用于存放没有处理的任务,提供一种缓冲机制;

线程池类的结构:
接口:Executor 最上层的接口,之定义了execute
接口:ExecutorService 继承了Exexutor ,拓展了Callable,Future,关闭方法;
接口:ScheduledExecutorService 继承ExecutorService,增加了定时任务相关的方法,
实现类:ThreadPoolExecutor 基础,标准的线程池实现
实现类: ScheduledThreadPoolExecutor 继承了ThreadPoolExecutor,实现了ScheduledExecutorService中相关定时任务的方法

Runnale 和Callable 一些区别

public class RunnableTest {
    static ThreadPoolExecutor pool = 
            new ThreadPoolExecutor(1, 2, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    public static void main(String args[])throws ExecutionException,InterruptedException {
        Future run_future = pool.submit(new MyRunnable());
        System.err.println("run_future:" + run_future.get());
        Future call_futter =pool.submit(new MyCallable());
        System.err.println("call_future:" + call_futter.get());
    }
}

class MyCallable implements Callable<Integer>{
    @Override
    public Integer call()throws Exception{
        return 1;
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        return;
    }
}
/*
run_future:null
call_future:1

*/

ThreadPoolExecutor

public class threadPool {
public static void main(String[] arg) throws Exception {
test();
}
private static void test() throws Exception{
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
5, //核心线程数,超时不会被清空
10, // 最大线程数10
5, // 保持的时间,超时 没执行的任务被清空
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(5), // 等待队列容量为5
// 最多容纳15个任务,查出的会被拒绝执行
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("有任务被拒绝执行了");

            }
        }
    );
    testCommon(threadPool);
}
public static void testCommon(ThreadPoolExecutor threadPool) throws Exception{
    for(int i=0;i<30; i++) {
        int n = i;
        threadPool.submit(new Runnable() {
            @Override
            public void run () {
                try {
                    System.out.println("任务"+ n +"开始执行");
                    Thread.sleep(3000L);
                    System.out.println("任务"+ n +"执行完成");
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println("任务" + i + "提交");
    }
    while(true) {
        Thread.sleep(1000);
        System.out.println("》》》线程数量"+threadPool.getPoolSize());
        System.out.println(">>>任务队列" + threadPool.getQueue().size());
    }
}

}

/*

任务0提交
任务0开始执行
任务1提交
任务2提交
任务1开始执行
任务3提交
任务2开始执行
任务4提交
任务5提交
任务6提交
任务7提交
任务3开始执行
任务4开始执行
任务8提交
任务9提交
任务10提交
任务11提交
任务12提交
任务10开始执行
任务13提交
任务11开始执行
任务12开始执行
任务14提交
有任务被拒绝执行了
任务15提交
任务13开始执行
任务16提交
有任务被拒绝执行了
有任务被拒绝执行了
有任务被拒绝执行了
有任务被拒绝执行了
有任务被拒绝执行了
任务17提交
任务14开始执行
任务18提交
任务19提交
任务20提交
任务21提交
有任务被拒绝执行了
有任务被拒绝执行了
任务22提交
任务23提交
任务24提交
任务25提交
任务26提交
有任务被拒绝执行了
有任务被拒绝执行了
有任务被拒绝执行了
有任务被拒绝执行了
有任务被拒绝执行了
任务27提交
有任务被拒绝执行了
任务28提交
有任务被拒绝执行了
任务29提交
》》》线程数量10
>>>任务队列5
》》》线程数量10
>>>任务队列5
任务1执行完成
任务0执行完成
任务2执行完成
任务7开始执行
任务3执行完成
任务5开始执行
任务6开始执行
任务8开始执行
任务13执行完成
任务4执行完成
任务14执行完成
任务9开始执行
任务12执行完成
任务10执行完成
任务11执行完成
》》》线程数量10
>>>任务队列0
》》》线程数量10
>>>任务队列0
》》》线程数量10
>>>任务队列0
任务7执行完成
任务5执行完成
任务8执行完成
任务6执行完成
任务9执行完成
》》》线程数量10
>>>任务队列0
*/

SynchronousQueue

SynchronousQueue不是一个真正的队列,因为它不会为队列中元素维护存储空间。 它维护一组线程,这些线程在等待着把元素加入或移出队列。put() 往queue放进去一个element以后就一直wait直到有其他thread进来把这个element取走。take() 取出并且remove掉queue里的element(认为是在queue里的。。。),取不到东西他会一直等。

public static void test3() throws InterruptedException{
        SynchronousQueue<String> queue = new SynchronousQueue<String>();
        new Thread(new Runnable() {
            @Override
            public void run () {
                try {
                    System.out.println("加入之前");
                    queue.put("新数据1");
                    System.out.println("加入之后");
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        Thread.currentThread().sleep(100L);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    Thread.sleep(3000);
                    System.out.println("取出数据之前");
                    String takedValue = queue.take();
                    System.out.println("取出来的数据: "+takedValue);
                    System.out.println("取出数据之后");
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
/*
加入之前
取出数据之前
取出来的数据: 新数据1
取出数据之后
加入之后

*/

Executors工具类

newFixedThreadPool(int nThreads)创建一个固定大小,任务队列容量无界的线程池,核心线程数=最大线程数
newCachedThreadPool 创建的是一个大小无界的缓冲线程池,他的任务队列是一个同步队列,任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如果无 则创建新线程执行,池中的线程空袭七年超过60s将被销毁释放,线程数随任务的多少而变化,适用于执行耗时较小的异步任务,池的核心线程数为0,最大线程数=Integet.MAX_VALUE
newSingleThreadExecutor() 只有一个线程来执行无边界任务队列的单一线程池,该线程池确保任务按加入的顺序一个一个的依次执行,当唯一的线程因任务异常终止时,将创建一个新的线程来继续执行后续的任务,与newFixedThreadPool的区别在于,单一线程池的池大小在newSingleThreadExecutor方法中编码,不能在改变的
newScheduledThreadPool(int corePoolSize) 能定时执行任务的的线程池,该池的核心线程数由参数指定,最大线程为Integer.MAX_VALUE

如果确定合适数量的线程

计算型任务:cpu数量的1-2倍
IO型任务:相对比计算型任务,需多一些线程,要根据具体的IO阻塞时长进行考量决定,也可以考虑根据需要在一个最小数量和最大数量间自动增减线程数
如tomcat中默认的最大线程数为200

线程消息通知

suspend和resume,

suspend挂起目标线程,通过resume可以恢复线程执行
已被弃用,主要是容易写出锁死的代码
正常使用:

public class Demo {
    public static String ice = null;
    public static void main(String[] arg) throws Exception {
        Demo demo = new Demo();
        demo.test1();
    }
    public void test1() throws Exception{
        Thread thread = new Thread(()->{
            if(ice==null) {
                System.out.println("没有数据:"+ice);
                Thread.currentThread().suspend(); // 挂起线程
            }
            System.out.println("有数据:"+ice);
        });
        thread.start();
        Thread.sleep(1000);
        ice = "1";
        thread.resume(); // 通知有数据了
        System.out.println("发送通知");
    }
}
/*
没有数据:null
发送通知
有数据:1

*/

异常使用:
suspend和resume会出现一种问题,当线程自己拿了锁,并挂起来了,其他线程再去拿锁的话去通知前一个线程会出现抢不到锁的情况

public class Demo {
    public static String ice = null;
    public static void main(String[] arg) throws Exception {
        Demo demo = new Demo();
        demo.test1();
    }
    public void test1() throws Exception{
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                if(ice==null) {
                    System.out.println("没有数据:"+ice);
                    synchronized(Demo.class) {
                        Thread.currentThread().suspend();
                    }
                }
                System.out.println("有数据:"+ice);
            }
        });
        thread.start();
        Thread.sleep(1000L);
        ice = "1";
        synchronized(Demo.class) {
                        System.out.println("发送通知");
            thread.resume(); // 通知有数据了
        }
    }
}

/*
没有数据:null

*/

还有一种情况是线程里面在6s中的时候执行suspend,但是在3s的时候其他线程就已经调用resume通知了的话,挂起的线程也是接收不到消息的

wait/notify机制

wait 方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁,
notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程
注意1:虽然会wait自动解锁,但是对顺序有要求,如果notify被调用之后,才开始wait方法的调用,线程会永远处于waiting状态
注意2:这些方法只能由同一对象锁的持有者线程调用,也就是卸载同步快里面,否则会抛出lllegalMStateException异常

wait/notify只能在synchronized关键字中使用

public void test2() {
        try {
            wait();
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
/*
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at com.Hello.ScannerTest.Demo.test2(Demo.java:11)
    at com.Hello.ScannerTest.Demo.main(Demo.java:7)

*/

public void test2() {
        try {
            synchronized(this) {
                wait();
            }
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
// 无异常

正常用法

public void test3() throws Exception {
        new Thread(()->{
            if(ice==null) {
                synchronized(this){
                    try {
                        System.out.println("没用数据:"+ice);
                        this.wait();
                        System.out.println("拿到数据:"+ice);
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                
            }
        }).start();
        Thread.sleep(1000L);
        ice = "1";
        synchronized(this) {
            System.out.println("拿到锁,通知");
            this.notifyAll();
        }
    }
/*
没用数据:null
拿到锁,通知
拿到数据:1

*/

注意:此处使用的是lambda表达式,里面的this会指向父级,所以这里两个synchronized里面的this都是在同一个作用域下的,所以这里的this会通知到,没问题,也就是上面所说的需要注意的第二点,方法只能在同一个线程线程锁的对象里面调用
错误示范:

public void test3() throws Exception {
        new Thread(new Runnable(){
            @Override
            public void run () {
                if(ice==null) {
                    synchronized(this){
                        try {
                            System.out.println("没用数据:"+ice);
                            this.wait();
                            System.out.println("拿到数据:"+ice);
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    
                }
            }
        }).start();
        Thread.sleep(1000L);
        ice = "1";
        synchronized(this) {
            System.out.println("拿到锁,通知");
            this.notifyAll();
        }
    }

/*
没用数据:null
拿到锁,通知

*/

需要改成这样

public class Demo {
    public static String ice = null;
    public static void main(String[] arg) throws Exception {
        Demo demo = new Demo();
        demo.test3();
    }
    public void test3() throws Exception {
        new Thread(new Runnable(){
            @Override
            public void run () {
                if(ice==null) {
                    synchronized(Demo.class){
                        try {
                            System.out.println("没用数据:"+ice);
                            Demo.class.wait();
                            System.out.println("拿到数据:"+ice);
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    
                }
            }
        }).start();
        Thread.sleep(1000L);
        ice = "1";
        synchronized(Demo.class) {
            System.out.println("拿到锁,通知");
            Demo.class.notifyAll();
        }
    }
}
/*
没用数据:null
拿到锁,通知
拿到数据:1
*/

park/ unpark机制

线程调用park则等待“许可”, unpark为线程提供许可
调用unpark之后再调用park线程会直接运行
提前调用的unpark不叠加,连续多次调用unpark之后,第一次调用park后会拿到“许可”,直接运行,后续调用会进入等待

package com.Hello.ScannerTest;

import java.util.concurrent.locks.LockSupport;

public class ParkUnparkDemo {
    public static void main(String args[]) throws Exception {
        ParkUnparkDemo demo = new ParkUnparkDemo();
        demo.test1();
    }
    public static Object ice = null;
    
    public void test1() throws Exception{
        
        Thread consume = new Thread(() -> {
            if(ice == null) {
                System.out.println("准备挂起");
                LockSupport.park();
            }
            System.out.println("完成---");
        });
        consume.start();
        Thread.sleep(3000);
        ice = new Object();
        
        LockSupport.unpark(consume);
        System.out.println("解锁--");
    }
}
/*
准备挂起
解锁--
完成---
*/

注意: 上面的代码用的if语句来判断,时候进入等待状态,这样的做法有问题,官方建议应该在循环中检查等待条件, 因为处于等待状态的线程可能会收到错误警报和唤醒,如果不在循环中检测等待条件,程序会在没有满足条件下推出,
为唤醒: 是指线程并非因为notify, notifyall, unpark等api调用而意外唤醒,是更底层原因导致的
如上面代码

Thread consume = new Thread(() -> {
            if(ice == null) { 
                System.out.println("准备挂起");
                LockSupport.park();
            }
            System.out.println("完成---");
        });
/*
如果ice不满足条件, Locksupport.park()突然被唤醒,那么就会执行下面的 “完成---”, 
*/
while(ice == null) { 
                System.out.println("准备挂起");
                LockSupport.park();
            }
当if改成while后,即使被唤醒了,还是不满足条件,程序继续被挂起,等待着
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 线程池ThreadPoolExecutor corepoolsize:核心池的大小,默认情况下,在创建了线程池之后...
    irckwk1阅读 724评论 0 0
  • Java 多线程 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程 并行与并发:并行:多个cpu实例或者...
    LarryLeo_9605阅读 300评论 0 3
  • Java 多线程 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程 并行与并发: 并行:多个cpu实例或...
    编程小世界阅读 125评论 0 0
  • 0. 前言 Java 为了实现跨平台,在语言层面上实现了多线程。我们只需要熟悉 Java 这一套多线程机制就行了,...
    WolfXu阅读 1,372评论 0 6
  • Java 多线程 线程和进程的区别 线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使...
    安安zoe阅读 2,200评论 1 18