| # 多线程 |
| ## 一、进程和线程 |
| 0. 比喻: 进程好比航空母舰,线程好比舰载机 |
| 1. 进程(process):进程是操作系统进行资源分配和调度的最小单元 |
| 2. 线程(Thread):线程是CPU调度执行任务的最小单元 |
| |
| 3. 进程和线程之间的关系 |
| 1). 一个软件启动,至少会启动一个进程 |
| 2). 一个进程运行,至少有一个线程 |
| 3). 进程的资源是向系统申请的,线程的资源是进程提供的 |
| |
| ## 二、并行和并发 |
| 1. 并行:同一时间点,一起执行 |
| (在同一时刻,有多个指令在多个CPU上同时执行) |
| 2. 并发:同一个时间段中,一起发生 |
| (在一段时间内,有多个指令在单个CPU上交替执行) |
| 3. 结合计算机的知识 |
| 0). 前提 |
| I. 一个线程只能执行一个任务 |
| II. 线程执行需要CPU分配执行权 |
| 1). CPU:8核16线程 |
| I. CPU的核心数有8个 |
| II. 同时支持16个线程执行 |
| 2). 并行:并行16个线程 |
| 3). 并发:并发2700个线程 |
| |
| ## 三、CPU的调度算法 |
| 0. 介绍:指的是CPU给线程分配执行权的方式 |
| 1. 分时调度 |
| 平均分 |
| 2. 抢占式调度(java) |
| CPU倾向分配给优先级高的线程 |
| 随机性 |
| ## 四、学习多线程的原因 |
| 1. 让我们的java程序可以同时执行多个任务 |
| 2. 在硬件资源充足的前提下,可以提高效率 |
| |
| ## 五、线程理解 |
| 1. 没有编写线程代码的清空 |
| 1). main线程: main方法所在的线程 |
| 2). GC线程: garbage collection 垃圾回收器线程 |
| 2. 一个线程只能一件事 |
| 3. 多线程并发是没有规律: 抢占式调度具有极强的随机性 |
| 我们无法预测某个时间哪个线程在运行 |
| 4. 线程之间是相互独立的 |
| 1). 一个进程只要有一个线程在执行,这个进程就不会结束 |
| 2). 同一个进程中,线程的栈内存是独立的,堆内存是共享的 |
| 线程A异常不处理,被JVM中止,不会影响其他线程 |
| |
| ## 六、多线程的实现方式(一) |
| 1. 实现多线程的方式有哪些? |
| 1). 继承Thread类的方式进行实现 |
| 2). 实现Runnable接口的方式进行实现 |
| 3). 利用Callable和Future接口方式实现 |
| 2. Thread类 |
| 表示线程的类 |
| 3. 方法介绍 |
| void run() |
| 在线程开始后,此方法将被调用执行 |
| void start() |
| 在此线程开始执行,Java虚拟机会调用此线程的run方法() |
| 4. 实现步骤 |
| 1). 定义一个类MyThread继承Thread类 |
| 2). 在MyThread类中重写run()方法 |
| 3). 创建MyThread类的对象 |
| 4). 启动线程 |
| 5. 第一种: 继承Thread类的方式进行实现 |
| 1). 定义一个类继承Thread |
| 2). 重写run方法(编写任务) |
| 3). 创建线程对象,并调用start方法 |
| 问题: run方法和start方法的区别 |
| I. run方法: 只是普通方法,不会开启线程 |
| II. start方法: |
| JVM底层会创建一个线程,并调用此线程的run方法 |
| 6. 第二种: 实现Runnable接口 |
| 1). 编写一个类实现Runnable接口 |
| 2). 重写run方法(编写任务) |
| 3). 创建线程对象,将Runnable实现类对象作为参数传入,并启动 |
| |
| ## 七、多线程的实现方式——两个小问题 |
| 1. 为什么要重写run()方法? |
| 因为run()是用来封装被线程执行的代码 |
| 2. run()方法和start()方法的区别? |
| run():封装线程执行的代码,直接调用,相当于普通方法的调用 |
| start():启动线程;然后由JVM调用此线程的run()方法 |
| |
| ## 八、多线程的实现方式(二): 实现Runnable接口 |
| 1. Thread构造方法 |
| Thread(Runnable target) |
| 分配一个新的Thread对象 |
| 2. 实现步骤 |
| 1). 定义一个类MyRunnable实现Runnable接口 |
| 2). 在MyRunnable类中重写run()方法 |
| 3). 创建MyRunnable类的对象 |
| 4). 创建Thread类的对象,把MyRunnable对象作为构造方法的参数 |
| 5). 启动线程 |
| |
| ## 九、多线程的实现方式(三): 实现Callable接口 |
| 1. 方法 |
| V call() |
| 计算结果,如果无法计算结果,则抛出一个异常 |
| FutureTask(Callable<V> callable) |
| 创建一个FutureTask,一旦运行就执行给定的Callable |
| V get() |
| 如有必要,等待计算完成,然后获得其结果 |
| 2. 实现步骤 |
| 1). 定义一个类MyCallable实现Callable接口 |
| 2). 在MyCallable类中重写call()方法 |
| 3). 创建MyCallable类的对象 |
| 4). 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数 |
| 5). 启动线程 |
| 6). 再调用get()方法,就可以获取线程结束之后的结果 |
| 3. 注意事项 |
| get()方法的调用一定要在Thread类对象调用start()方法之后 |
| |
| ## 十、三种实现方式的对比 |
| 1. 实现Runnable、Callable接口 |
| - 好处:扩展性强,实现该接口的同时还可以继承其他的类 |
| - 缺点:编程相对复杂,不能直接使用Thread类中的方法 |
| > 推荐Runnable! |
| 2. 继承Thread类 |
| - 好处:编程比较简单,可以直接使用Thread类中的方法 |
| - 缺点:扩展性较差,不能再继承其他的类 |
| 3. 实际开发过程中如何选择? |
| 如果不需要任务的返回结果,就实现Runnable接口,需要的话,实现Callable接口。 |
| 4. 如何去写多线程程序 |
| 1). 确定要做什么,执行什么任务 |
| 2). 要不要用多线程,要任务返回的结果,如果要 实现Callable接口,如果不要 实现Runnable接口 |
| 3). 实现call方法或run方法 |
| 4). 交给线程类,执行start(),开启线程干活 |
| |
| ## 十一、线程名字 |
| 1. 默认 |
| 1). 主线程: main |
| 2). 子进程: |
| Thread-0, 1, 2, 3, 4... |
| 2. 设置 |
| 1). void setName(String name) |
| 设置线程名字 |
| 2). Thread.currentThread().getName() |
| 获取当前线程名字 |
| |
| ## 十二、线程类中的常见方法 |
| 1. 设置和获取线程名称(重点) |
| - 方法介绍 |
| void setName(String name) |
| 将此线程的名称更改为等于参数name |
| String getName() |
| 返回此线程的名称 |
| 2. Thread方法——获得当前线程对象(重点) |
| - 方法介绍 |
| static Thread currentThread() |
| 返回对当前正在执行的线程对象的引用 |
| 3. 线程休眠(重点) |
| - 方法介绍 |
| static void sleep(long millis) |
| 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
| 4. 线程优先级 |
| - 方法介绍 |
| final int getPriority() |
| 返回此线程的优先级 |
| final void setPriority(int newPriority) |
| 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 |
| 5. 守护线程 |
| 1). 什么是守护线程? |
| 守护其他线程,当普通线程执行完毕了,守护线程就没有存在的必要了 |
| 2). 如何使用守护线程? |
| void setDaemon(boolean on) |
| 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将推出 |
| |
| ## 十三、线程同步 |
| 1. 线程安全问题——原因分析 |
| 所谓的线程安全问题(线程同步问题)指的是多个线程争抢同一资源,而出现的数据结果与预期不一致的情况。 |
| > 首先要有多线程,其次多个线程要争抢同一资源 |
| 2. 卖票出现重复票和乱序问题的原因 |
| 卖票的过程中有多条线程操作了共享数据 |
| java用的CPU调度算法是抢占式调度,不具备规律性,很随机 |
| 解决: 控制不了CPU,就控制我们自己的程序 |
| 方案: 同步锁 |
| 3. 同步代码块解决数据安全问题 |
| 1). 如何解决上述问题? |
| I. 任意时刻只有一条线程可以操作(增、删、改、查)共享变量 |
| II. Java中如何解决? |
| 同步代码块格式: |
| synchronized(任意对象) { |
| 操作共享数据的代码 |
| } |
| synchronized(任意对象): 就相当于给代码加锁了,任意对象就可以看成是一把锁 |
| 2). synchronized同步代码块的特点 |
| 默认情况下是打开的,只要有一个线程进去执行代码了,锁就会关闭 |
| 当线程执行完出来时,锁才会自动打开 |
| 3). 什么情况下会出现线程安全问题呢? |
| 多条线程操作共享数据(可以拆分为三个条件): |
| I. 多线程环境 |
| II. 有共享数据 |
| III. 多条线程操作了共享数据 |
| 因此,在卖票的时候,加不加Thread.sleep()代码中都会有线程安全问题,只不过加上之后,线程执行的速度慢了下来,我们可以观察到问题 |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通