BCSP-玄子JAVA开发之面向对象编程CH10_ 多线程笔记

BCSP-玄子JAVA开发之面向对象编程CH10_ 多线程笔记

10.1 多线程概述

10.1.1 进程和线程

进程

  • 应用程序的执行实例
  • 有独立的内存空间和系统资源

线程

  • CPU调度和分派的基本单位
  • 进程中执行运算的最小单位,可完成一个独立的顺序控制流程

10.1.2 多线程运行机制

如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程

多个线程交替占用CPU资源,而非真正的并行执行

多线程好处

  • 充分利用CPU的资源

  • 简化编程模型

  • 良好的用户体验

进程与线程的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vUDtEQEL-1676363712830)(./assets/image-20230214134539630.png)]

并行执行

  • 通常表示同一个时刻有多条指令代码在处理器上同时运行

  • 往往需要多个处理器支持

并发运行

  • 表示在一个处理器中,操作系统为了提高程序的运行效率,将CPU的执行时间分成多个时间片,分配给同一进程的不同线程

  • 多个线程分享CPU时间,交替执行

宏观并行,微观串行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C4mDjF0M-1676363712832)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20230214022709196.png)]

10.1.3 多线程的优势

  • 系统资源利用率高
  • 用户交互性高
  • 极少数情况下开发更简单

10.2 多线程编程

10.2.1 Thread 类常用方法

Thread 类提供了大量的方法来控制和操作多线程

方法 描述 类型
Thread() 创建 Thread 对象 构造方法
Thread(Runnable target) 创建 Thread 对象,target 为 run() 方法被调用的对象 构造方法
Thread(Runnable target,String name) 创建 Thread 对象,target 为run() 方法被调用的对象,name 为新线程的名称 构造方法
void run() 执行任务操作的方法 实例方法
void start() 使该线程开始执行,JVM 将调用该线程的 run() 方法 实例方法
void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) 静态方法
Thread currentThread() 返回当前线程对象的引用 静态方法

10.2.2 主线程

Java程序启动时,一个线程立即随之启动,通常称之为程序的主线程

  • main()方法即为主线程入口
  • 主线程是产生其他子线程的线程
  • 主线程必须最后完成执行,因为它执行各种关闭动作

10.2.3 使用 Thread 类的方法获取主线程信息

package CH10_Thread.xz01_CurrentThread;

public class CurrentThread {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        // 获得主线程对象
        System.out.println("当前线程:" + t.getName());
        // 查看当前主线程 main
        t.setName("MyThread");
        // setName() 设置线程名
        System.out.println("当前线程:" + t.getName());
        // getName() 获取线程名
    }
}

10.2.4 实现多线程

Java中创建线程的两种方式

  1. 继承java.lang.Thread

  2. 实现java.lang.Runnable接口

使用线程的步骤

  1. 定义线程
  2. 创建线程对象
  3. 启动线程

10.2.5 继承 Thread 类创建线程

  1. 自定义线程类必须继承自 Thread 类
  2. 重写 run() 方法,编写线程执行体
  3. 创建线程对象,调用 start() 方法启动线程

线程从它的 run() 方法开始执行,即 Thread 类的 run() 方法是线程运行的起点

package CH10_Thread.xz02_MyThread;

public class MyThread extends Thread {
           	 		// 继承自Thread类
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程:"+ i);
        }
    }
    // 重写Thread类中run()方法
}
package CH10_Thread.xz02_MyThread;

public class XuanZi {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        // 启动线程
    }
}

10.2.6 线程休眠

让线程暂时睡眠指定时长,线程进入阻塞状态,睡眠时间过后线程会再进入可运行状态

public static void sleep(long millis)
// millis为休眠时长,以毫秒为单位
package CH10_Thread.xz03_Sleep;

public class MyThread extends Thread {
    // 继承自Thread类

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程:" + i);
            try {
                MyThread.sleep(1000);
                //线程休眠1秒
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

调用 sleep() 方法需处理 InterruptedException 异常

package CH10_Thread.xz03_Sleep;

public class XuanZi {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.start();
        // 已启动的线程对象不能重复调用start()方法
        // 否则会抛出IllegalThreadStateException异常
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQbVNOcO-1676363712833)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214143527.png)]

package CH10_Thread.xz04_InterruptedException;

public class XuanZi {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.interrupt();
        // 打断子线程
        // 如果调用sleep()方法控制线程休眠时间的线程
        // 被其他线程中断,则会产生InterruptedException异常
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程" + i);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYsfovcc-1676363712833)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214150331.png)]

10.2.7 继承 Thread 类创建多线程

多个线程交替执行,不是真正的“并行”,线程每次执行时长由分配的CPU时间片长度决定

package CH10_Thread.xz05_Threads;

public class XuanZi {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程A:");
        MyThread myThread1 = new MyThread("线程B:");
        myThread.start();
        myThread1.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程:" + i);
        }
    }
}
package CH10_Thread.xz05_Threads;

public class MyThread extends Thread {

    public MyThread(String name) {
        super.setName(name);
        // 构造传参
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(super.getName() + i);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnQV2fE4-1676363712834)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214151502.png)]

调用 start() 方法后,每个线程独立完成各自的操作,相互间没有影响,并行执行

10.2.8 启动线程不可以直接调用 run() 方法

线程对象调用 start() 方法是启动线程,run()方法是实例方法,在实际应用中切不要混淆

package CH10_Thread.xz06_run;

public class XuanZi {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程A:");
        myThread.run();
        // 调用run 方法
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程:" + i);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzVLAb3c-1676363712834)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214151857.png)]

用 run() 替换 start() ,属于单线程执行模式

10.2.9 实现Runnable接口创建线程

Runnable 接口位于 java.lang 包,只提供一个抽象方法 run() 的声明

实现步骤

  1. 定义 MyRunnable 类实现 Runnable 接口

  2. 实现 run() 方法,编写线程执行体

  3. 创建线程对象,调用 start() 方法启动线程

package CH10_Thread.xz07_MyRunnable;


public class XuanZi {
    public static void main(String[] args) {
        Runnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        //创建线程对象
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程:" + i);
        }
    }
}
package CH10_Thread.xz07_MyRunnable;

public class MyRunnable implements Runnable {
    //实现Runnable接口
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程:" + i);
        }
        //Runnable接口的run()方法
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2X3KMcTG-1676363712834)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214153358.png)]

10.2.10 比较两种创建线程的方式

继承 Thread 类

  • 编写简单,可直接操作线程

  • 适用于单继承

实现 Runnable 接口

  • 避免单继承局限性

  • 便于共享资源

推荐使用实现 Runnable 接口方式创建线程

10.3 线程的生命周期

通常,线程的生命周期有五种状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXLBBudK-1676363712835)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214125554.png)]

处于运行状态的线程会让出CPU控制权

  • 线程运行完毕

  • 有比当前线程优先级更高的线程抢占了CPU

  • 线程休眠

  • 线程因等待某个资源而处于阻塞状态

10.4 线程调度

指按照特定机制为多个线程分配CPU的使用权,每个线程执行时都具有一定的优先级

10.4.1 常用的线程操作方法

方法 说明
int getPriority() 返回线程的优先级
void setPrority(int new Priority) 更改线程的优先级
boolean isAlive() 测试线程是否处于活动状态
void join() 进程中的其它线程必须等待该线程终止后才能执行
void interrupt() 中断线程
void yield() 暂停当前正在执行的线程对象,并执行其他线程

10.4.2 线程优先级

线程优先级由1~10表示,1最低,默认优先级为5,优先级高的线程获得CPU资源的概率较大

使用Thread类的静态常量设置线程的优先级

  • MAX_PRIORITY:值是10,表示优先级最高

  • MIN_PRIORITY:值是1,表示优先级最低

  • NORM_PRIORITY:值是5,表示普通优先级

尽管为线程设定了不同的优先级,但实际上并不能精确控制这些线程的执行先后顺序

在不同的计算机或同一计算机不同时刻中运行本程序,都会得到不同的执行序列

10.4.3 线程的强制运行

使当前线程暂停执行,等待其他线程结束后再继续执行本线程

join()方法的重载方法

public final void join()
public final void join(long mills)
public final void join(long mills, int nanos)
// 阻塞主线程,子线程强制执行
  • millis:以毫秒为单位的等待时长

  • nanos:要等待的附加纳秒时长

  • 需处理 InterruptedException 异常

10.8.4 线程的礼让

暂停当前线程,允许其他具有相同优先级的线程获得运行机会,该线程处于就绪状态,不转为阻塞状态

yield()方法定义

public static void yield()

只是提供一种可能,但是不能保证一定会实现礼让

执行Thread.yield()方法后,多个线程间交替执行较为频繁,可以提高程序的并发性

10.4.5 比较sleep()方法和yield()方法

共同点

  • Thread 类的静态方法

  • 会使当前处于运行状态的线程放弃CPU使用权,将运行机会让给其他线程

不同点

  • sleep() 方法会给其他线程运行机会,不考虑其他线程的优先级,因此较低优先级线程可能会获得运行机会

  • yield() 方法只会将运行机会让给相同优先级或者更高优先级的线程

  • 调用 sleep() 方法需处理 InterruptedException 异常,而调用 yield() 方法无此要求

10.5 线程同步

当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用,相当于将线程中需要一次性完成不允许中断的操作加上一把锁,以解决冲突

使用 synchronized 关键字,为当前的线程声明一把锁

实现方式

  • 同步代码块

  • 同步方法

10.5.1 同步代码块

使用 synchronized 关键字修饰的代码块

synchronized(syncObject) {
// syncObject:需同步的对象,通常为this
// 需要同步的代码
}

一般情况下,只有获得锁的线程可以操作共享数据,执行完同步代码块中所有代码后,才会释放锁,使其他线程获得锁

10.5.2 同步方法

使用 synchronized 修饰的方法控制对类成员变量的访问

访问修饰符 synchronized 返回类型 方法名(参数列表){……}
synchronized 访问修饰符 返回类型 方法名(参数列表){……}

10.5.3 线程同步的特征

  • 不同的线程在执行以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁而相互牵制

  • 多个并发线程访问同一资源的同步代码块或同步方法时

    • 同一时刻只能有一个线程进入 synchronized(this)同步代码块

    • 当一个线程访问一个 synchronized(this)同步代码块时,其他 synchronized(this)同步代码块同样被锁定

    • 当一个线程访问一个 synchronized(this)同步代码块时,其他线程可以访问该资源的非 synchronized(this)同步代码

  • 如果多个线程访问的不是同一共享资源,无需同步

使用同步代码块和同步方法完成线程同步,二者的实现结果没有区别

不同点

  • 同步方法便于阅读理解

  • 同步代码块更精确地限制访问区域,会更高效

10.5.4 线程安全的类型

如果程序所在的进程中,有多个线程同时运行,每次运行结果和单线程时运行结果是一样的,且其他变量的值也和预期相同,则当前程序是线程安全的

查看 ArrayList 类的 add() 方法定义

public boolean add(E e) {
   ensureCapacityInternal(size + 1); 
    // 集合扩容,确保能新增数据
   elementData[size++] = e;
    // 在新增位置存放数据
    return true;
}

ArrayList 类的 add() 方法为非同步方法

当多个线程向同一个 ArrayList 对象添加数据时,可能出现数据不一致问题

ArrayList 为非线程安全的类型

方法是否同步 效率比较 适合场景
线程安全 多线程并发共享资源
非线程安全 单线程

为达到安全性和效率的平衡,可以根据实际场景选择合适的类型

10.6 常见类型对比

10.6.1 Hashtable && HashMap

Hashtable

  • 继承关系

    • 实现了 Map 接口,Hashtable 继承 Dictionary 类
  • 线程安全,效率较低

  • 键和值都不允许为 null

HashMap

  • 继承关系

    • 实现了 Map 接口,继承 AbstractMap 类
  • 非线程安全,效率较高

  • 键和值都允许为null

10.6.2 StringBuffer && StringBuilder

前者线程安全,后者非线程安全

在单线程环境下,StringBuilder 执行效率更高

BCSP-玄子JAVA开发之面向对象编程CH10_ 多线程笔记 2.14

你可能感兴趣的:(JAVA,java,jvm,开发语言)