多线程笔记

Myth丶恋晨 2022-10-23 09:54 240阅读 0赞

文章目录

  • 一、多线程概述
    • 1.线程与进程
      • 1.1线程(Thread)
      • 1.2进程(Process)
    • 2.线程调度
      • 2.1分时调度
      • 2.2抢占式调度(Java使用)
    • 3.同步与异步
    • 4.并发与并行
    • 5.守护线程和用户线程
  • 二、Java中的多线程
    • 1.如何实现多线程
      • 1.1继承Thread类
      • 1.2 实现Runnable接口
      • 1.3实现Runnable与继承Thread相比的优点
      • 1.4带返回值的线程Callable
      • 1.5Runnable与Callable相比
    • 2.线程安全问题
      • 2.1同步代码块(隐式锁)
      • 2.2同步方法(隐式锁)
      • 2.3显式锁Lock
    • 3.公平锁与非公平锁
    • 4.多线程通信问题
    • 5.线程的六种状态
  • 三、线程池
    • 1.线程池概述
    • 2.缓存线程池
    • 3.定长线程池
    • 4.单线程线程池
    • 5.周期定长线程池
  • 四、Lambda表达式
  • 总结

一、多线程概述

1.线程与进程

1.1线程(Thread)

  • 线程是CPU调度的最小单位
  • 是进程中的一个执行路径,一个进程启动后,里面的若干执行路径又可以被划分为若干个线程
  • 一个进程中至少有一个线程
  • 同一个进程中的多个线程可以共享部分内存(方法区,堆),每个线程之间有些内存又是独立的(栈:虚拟机栈,本地方法栈,程序计数器)

1.2进程(Process)

  • 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,进程与进程之间是无法直接共享内存的
  • 每个进程之间是独立的,操作系统分配资源时,是以进程为单位的
  • 两个进程之间进行切换,通信(交换数据)等操作时,成本比较高
  • 进程是线程的容器,进程中可容纳若干个线程。是程序的实体,程序是死的,静态的,进程是正在执行的。

2.线程调度

  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对于我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能提高程序运行效率,让CPU的使用率更高。

2.1分时调度

所有线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间

2.2抢占式调度(Java使用)

  • 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)

3.同步与异步

同步和异步通常用来形容一次方法的调用

  • 同步:排队执行,效率低但安全。同步调用一旦开始,调用者必须等到方法调用返回后,才能继续执行。
  • 异步:同时执行,效率高但数据不安全。一旦开始,方法调用会立即返回,调用者可以继续后续工作,异步方法通常在另一个线程中真实执行,对于调用者来说似乎是瞬间完成了。

4.并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生
  • 并行:指两个或多个事件在同一时刻发生(同时发生),真正意义上的同时执行

5.守护线程和用户线程

  • 用户线程:当一个进程不包含任何存活的用户线程时,进程结束
  • 守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡

    public static void main(String[] args){

    1. Thread t=new Thread();
    2. t.setDaemon(true);//在启动线程前设置该线程为守护线程,当该进程中的用户线程都结束后,守护线程自动死亡
    3. t.start();

    }

二、Java中的多线程

1.如何实现多线程

1.1继承Thread类

  1. public class Demo{
  2. public static void main(String[] args){
  3. MyThread my=new MyThread();
  4. my.start();//启动线程,此时run和main是并发执行的
  5. }
  6. }
  7. public class MyThread extends Thread{
  8. @Override
  9. public void run(){
  10. //支线线程要执行的任务
  11. }
  12. }

1.2 实现Runnable接口

  1. public class Demo{
  2. public static void main(String[] args){
  3. //1.创建一个任务
  4. MyRunnable my=new MyRunnable();
  5. //2.创建一个线程并给他分配一个任务
  6. Thread t=new Thread(my);
  7. t.start();
  8. }
  9. }
  10. public class MyRunnable implements Runnable{
  11. @Override
  12. public void run(){
  13. //支线线程要执行的任务
  14. }
  15. }

1.3实现Runnable与继承Thread相比的优点

  1. 1. 通过创建任务,然后给线程分配任务的方式来实现多线程,更适合多个线程同时执行相同任务的情况
  2. 2. 可以避免单继承带来的局限性
  3. 3. 任务与线程本身是分离的,提高了程序的健壮性
  4. 4. 线程池技术接受Runnable类型的任务,不接受Thread类型的线程

1.4带返回值的线程Callable

  1. public static void main(String[] args){
  2. Callable<?> my=new MyCallable();
  3. FutureTask<?> future=new FutureTask<>(my);
  4. new Thread(future).start();
  5. //task.get();此时主线程会停下来等子线程的返回值
  6. //task.isDone();判断该线程任务是否执行完毕
  7. }
  8. Class MyCallable implements Callable<T>{
  9. @Override
  10. public <T> call() throws Exception{
  11. return T;
  12. }
  13. }

1.5Runnable与Callable相比

  • 相同点:
  1. 都是接口
  2. 都可以编写多线程程序
  3. 都采用Thread.start()启动线程

    • 不同点:
  4. Runnable没有返回值;Callable可以返回执行结果

  5. Callable接口的call()允许抛出异常;Runnable的run()不能抛出

2.线程安全问题

2.1同步代码块(隐式锁)

  • 多个线程应持同一个锁对象才有效
  • 线程同步可以解决线程安全问题,但会降低效率(排队减速)

    synchronized(锁对象){

    1. //可能发生线程安全问题的代码

    }

2.2同步方法(隐式锁)

  • 非静态方法,同步方法的锁是this
  • 如果是静态修饰的,那么锁对象是类名.class

    //将需要同步的部分抽成一个方法
    权限修饰符 synchronized 返回值类型 方法名(){

    1. //可能发生线程安全问题的代码

    }

2.3显式锁Lock

  1. //Lock子类ReentrantLock
  2. private Lock l=new ReentrantLock();
  3. l.lock();
  4. /*要加锁的代码*/
  5. l.unlock;

3.公平锁与非公平锁

  • 公平锁:有先来后到的顺序

    //fair默认是false(不公平锁),当传入参数是true时,为公平锁
    Lock l=new ReentrantLock(boolean fair);

  • 非公平锁:线程一起抢,谁抢到锁算谁的。

4.多线程通信问题

  1. //生产者与消费者问题
  2. public class Demo {
  3. public static void main(String[] args) {
  4. Plant p=new Plant();
  5. new Waiter(p).start();
  6. new Cook(p).start();
  7. }
  8. }
  9. class Cook extends Thread{
  10. Plant p;
  11. public Cook(Plant p) {
  12. this.p = p;
  13. }
  14. @Override
  15. public void run() {
  16. while(true){
  17. synchronized (p){
  18. //假设平台上一次最多可以放十盘菜
  19. if(p.getCount()>=10){
  20. try {
  21. p.wait();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. p.setCount(p.getCount()+1);
  27. System.out.println("厨师生产了一盘菜,平台上还有:"+p.getCount()+"盘菜");
  28. p.notify();
  29. }
  30. }
  31. }
  32. }
  33. class Waiter extends Thread{
  34. Plant p;
  35. public Waiter(Plant p) {
  36. this.p = p;
  37. }
  38. @Override
  39. public void run() {
  40. while(true){
  41. synchronized (p){
  42. if(p.getCount()<=0){
  43. try {
  44. p.wait();
  45. } catch (InterruptedException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. p.setCount(p.getCount()-1);
  50. System.out.println("服务员取走了一盘菜,平台上还有:"+p.getCount()+"盘菜");
  51. p.notify();
  52. }
  53. }
  54. }
  55. }
  56. class Plant{
  57. public int count=0;
  58. public int getCount() {
  59. return count;
  60. }
  61. public void setCount(int count) {
  62. this.count = count;
  63. }
  64. }

5.线程的六种状态

  • NEW 线程刚被创建,但尚未启动
  • RUNNABLE 正在执行的线程
  • BLOCKED 被阻塞等待监视器锁定的线程
  • WAITING 休眠的线程
  • TIMED WAITING 有限期的休眠线程
  • TERMINATED 死亡

三、线程池

1.线程池概述

  • 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
  • 线程池的好处
  1. 降低资源消耗
  2. 提高响应速度
  3. 提高线程的可管理性

2.缓存线程池

  • 长度无限制

    //1.判断线程池是否存在空闲线程
    //2.存在则使用
    //3.不存在,则创建线程 并放入池中,然后使用
    public class Demo {

    1. public static void main(String[] args) {
    2. ExecutorService service= Executors.newCachedThreadPool();
    3. service.execute(new Runnable(){
    4. @Override
    5. public void run(){
    6. //任务代码
    7. }
    8. });
    9. }

    }

3.定长线程池

  • 长度是指定的数值

    / 1.判断线程池是否存在空闲线程 2.存在则使用 3.不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用 4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 /
    public class Demo {

    1. public static void main(String[] args) {
    2. ExecutorService service= Executors.newFixedThreadPool(nThread);
    3. service.execute(new Runnable(){
    4. @Override
    5. public void run(){
    6. //任务代码
    7. }
    8. });
    9. }

    }

4.单线程线程池

  1. /* 1.判断线程池的那个线程是否空闲 2.空闲则使用 3.不空闲,则等待池中的单个线程空闲后使用 */
  2. public class Demo {
  3. public static void main(String[] args) {
  4. ExecutorService service= Executors.newSingleThreadExecutor();
  5. service.execute(new Runnable(){
  6. @Override
  7. public void run(){
  8. //任务代码
  9. }
  10. });
  11. }
  12. }

5.周期定长线程池

  1. /* 周期性任务执行时:定时执行,当某个时机触发时,自动执行某任务 1.判断线程池是否存在空闲线程 2.存在则使用 3.不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用 4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 */
  2. public class Demo {
  3. public static void main(String[] args) {
  4. ScheduledExecutorService service = Executors.newScheduledThreadPool(corePoolSize);
  5. /** * 1.定时执行一次 * 参数1.定时执行的任务 * 参数2.时长数字 * 参数3.时长数字的时间单位,TimeUnit常量指定 */
  6. service.schedule(new Runnable(){
  7. @Override
  8. public void run(){
  9. //任务代码
  10. }
  11. },5,TimeUnit.SECONDS);
  12. /** * 2.周期性执行任务 * 参数1.任务 * 参数2.延迟时长数字(第一次执行在什么时间后) * 参数3.周期时长数字(每隔多久执行一次) * 参数4.时长数字的时间单位,TimeUnit常量指定 */
  13. service.scheduleAtFixedRate(new Runnable(){
  14. @Override
  15. public void run(){
  16. //任务代码
  17. }
  18. },5,1,TimeUnit.SECONDS);
  19. }
  20. }

四、Lambda表达式

  • 函数式编程思想

    //想要实现Lambda表达式,接口中只能包含一个方法
    public class Demo {

    1. public static void main(String[] args) {
    2. /*冗余的做法 print(new MyMath() { @Override public int sum(int x, int y) { return x+y; } },1,2);*/
    3. print((int x,int y)->{
    4. return x+y;
    5. },1,2);
    6. }
    7. public static void print(MyMath m,int x,int y){
    8. int num=m.sum(x,y);
    9. System.out.println(num);
    10. }
    11. static interface MyMath{
    12. int sum(int x,int y);
    13. }

    }


总结

发表评论

表情:
评论列表 (有 0 条评论,240人围观)

还没有评论,来说两句吧...

相关阅读

    相关 线笔记

    [源码地址][Link 1] 避免使用同步的方式 一:线程封闭 线程封闭:访问共享的可变数据时,需要使用同步,避免使用同步的方式就是不共享数据,仅是单线程访问数据

    相关 线笔记总结

    多线程笔记总结 1、Java中如果我们自己没有产生线程,那么系统就会给我产生一个线程(主线程,main方法就是在主线程上运行),我们的程序都是由线程来执行的。 2、进

    相关 线笔记

    > > 1. 进程和线程的区别 > > > (1) 根本区别:进程作为资源分配的单位,线程调度和执行的单位 > > >   > > > 2. java 下实现多线程