复习多线程

1. 什么是进程

进程是系统进行资源分配和调用的独立单位,每一个进程都有它的独立内存空间和系统资源。

2. 线程与进程的关系

多核CPU是否可以让系统在同一时间执行多个任务 ?

多核CPU可以让系统在同一时间执行多个任务。多核CPU指的是一个处理器芯片内集成了多个处理核心,每个核心可以独立地执行指令和处理任务。这意味着在一个多核CPU系统中,可以同时运行多个线程或进程,每个核心可以处理一个任务。操作系统可以根据需要将不同的任务分配给不同的核心来执行,从而实现并行处理。这种并行处理可以显著提高系统的整体性能和响应能力,特别是在需要处理大量计算或者多个任务的情况下。然而,要实现真正的并行处理,除了多核CPU之外,还需要操作系统和应用程序的支持。操作系统需要能够有效地调度和管理多个任务,并将它们分配给可用的处理核心。应用程序也需要设计为能够充分利用多核CPU的并行处理能力,例如通过使用多线程或并行算法来实现任务的并行执行。

总之,多核CPU可以让系统在同一时间执行多个任务,但实现并行处理还需要操作系统和应用程序的支持。

什么是线程?

线程是进程里面的一条执行路径,每个线程同享里面的内存空间和系统资源。

一个进程可以有多个线程:各个线程有不同的分工。

理解线程和进程的关系:

进程 与 进程 之间的关系:进程之间的内存空间和系统资源是独立的

进程 与 进程 之间的关系:进程之间的内存空间和系统资源是独立的

一个进程可以有一条或一条以上的线程

进程里只有一条线程的情况下,这条线程就叫做主线程

进程里有多条线程的情况下,只有一条线程叫做主线程

Ps:线程是在进程里的,他们是包含关系

3. 线程的创建

  1. 线程类MyThread
package com.wz.thread01;

public class MyThread extends Thread{
    //线程抢到了资源才会运行run()方法
    @Override
    public void run() {
        System.out.println("run()方法被调用了!");
    }
}
package com.wz.thread01;

public class test01 {
    /**
     *创建Mythread 继承 Thread
     重写run()方法
     */
    public static void main(String[] args) {
        //创建线程对象
        MyThread myThread = new MyThread();
        //启动线程
        myThread.start();
    }
}
  1. 任务类Task
package com.wz.thread02;

public class Task implements Runnable{

    @Override
    public void run() {
        System.out.println("run()方法被调用了!");
    }
}
package com.wz.thread02;

public class test01 {
    /**
     *任务类:
     *      创建Task(任务类)实现Runnable接口
     *      重写run()方法
     *创建线程:
     *      创建任务类对象 --  Task task = new Task();
     *      创建线程对象 -- Thread thread = new Thread(task);
     *      启动线程 -- thread.start();
     */
    public static void main(String[] args) {
        Task task = new Task();
        Thread thread = new Thread(task);
        thread.start();
    }
}

4. 多线程之间的资源争抢(线程类)

需求:编写一个多线程的应用程序,主线程打印1-100之间的数字,子线程打印200-300之间的数字,观察其输出的结果,体会多线程互相争抢资源的场景

package com.wz.thread03;

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 200; i <=300 ; i++) {
            System.out.println("子线程:"+i);
        }
    }
}
package com.wz.thread03;

public class test01 {
    /**
     *知识点:线程之间的资源争抢
     * 需求:编写一个多线程的应用程序
     * 主线程打印1-100之间的数字
     * 子线程打印200-300之间的数字
     * 观察其输出的结果,体会多线程互相争抢资源的场景
     */
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 1; i <=100 ; i++) {
            System.out.println("主线程:"+i);
        }
    }
}

2023年7月4日,复习多线程,线程的创建,资源争抢,线程的优先级别,线程的命名_主线程

分析:

在循环上边和下边创建并启动线程MyThread的区别在于线程的执行顺序。

如果将创建并启动线程的代码放在循环上边:

MyThread myThread = new MyThread(); myThread.start(); for (int i = 1; i <=100 ; i++) { System.out.println("主线程:"+i); }

那么先创建并启动线程,然后再执行循环。也就是说,先启动子线程,再在主线程中执行循环打印数字。这样的执行顺序可能会导致子线程和主线程互相争抢资源,输出结果可能是无序的。

如果将创建并启动线程的代码放在循环下边:

for (int i = 1; i <=100 ; i++) { System.out.println("主线程:"+i); } MyThread myThread = new MyThread(); myThread.start();

那么先执行循环,再创建并启动线程。也就是说,先在主线程中执行循环打印数字,再启动子线程。这样的执行顺序可以保证主线程先输出数字,然后子线程输出数字,输出结果有序。

所以,根据代码的执行顺序,将创建并启动线程的代码放在循环上边和下边会影响线程的执行顺序和输出结果的有序性。

5. 小结

进程 与 进程 的关系:独享内存空间和系统资源

线程 与 进程 的关系:有一个进程中至少包含一个线程

线程 与 线程 的关系:在同一个进程里,多个线程共享内存空间和系统资源

一个进程中包含多个线程,只有一个主线程

经典面试题:请问当我们编写一个单纯的main方法时,此时该程序是否为单线程的?为什么?

垃圾回收器是一个后台线程

Java垃圾回收器(Garbage Collector)是Java虚拟机(JVM)的一部分,用于自动管理内存的分配和释放。垃圾回收器通过监视和回收不再使用的对象,释放它们占据的内存空间,以供其他对象使用。这样可以避免手动释放内存的繁琐和容易出错的过程,提高了开发效率和程序的健壮性。

Java的垃圾回收器采用了自动内存管理机制,也就是说,程序员不需要显式地释放已分配的内存。垃圾回收器会周期性地扫描堆内存中的对象,标记那些仍然被引用的对象,然后回收那些没有被引用的对象,释放它们占据的内存空间。

Java的垃圾回收器具有以下特点:

  1. 自动化:垃圾回收器自动管理内存,程序员不需要手动释放内存。
  2. 并发性:垃圾回收器可以在程序运行的同时执行垃圾回收操作,减少对程序执行的影响。
  3. 分代式:垃圾回收器将堆内存分为不同的代(Generation),根据对象的生命周期进行不同的回收策略,提高回收效率。
  4. 复制算法或标记清除算法:垃圾回收器使用不同的算法来标记和回收不再使用的对象。
  5. 可配置性:Java提供了不同的垃圾回收器实现,可以根据应用程序的需求选择合适的回收器。

常见的Java垃圾回收器包括Serial、Parallel、CMS、G1等,每种回收器都有其特定的优缺点和适用场景。选择合适的垃圾回收器可以根据应用程序的内存使用模式、延迟要求和吞吐量要求等因素进行综合考虑。

6. 线程的优先级别

线程的优先级别有10档:1~10

a.setPriority(Thread.MAX_PRIORITY);//10 b.setPriority(Thread.NORM_PRIORITY);//5 c.setPriority(Thread.MIN_PRIORITY);//1

7. 线程的命名

需求:在主线程中创3个子线程,并且设置不同优先级,观察其优先级对线程执行结果的”影响”。

package com.wz.thread04;

public class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    public MyThread() {
    }
    @Override
    public void run() {
        for (int i = 1; i <=100 ; i++) {
            //获取当前线程对象
            Thread thread = Thread.currentThread();
            //获取当前线程名
            String str = thread.getName();
            System.out.println(str+":"+i);
        }
    }
}
package com.wz.thread04;

public class test01 {
    /**
     * 知识点:线程的命名
     *需求:在主线程中创3个子线程
     * 并且设置不同优先级
     * 观察其优先级对线程执行结果的”影响”。
     */
    public static void main(String[] args) {
        MyThread a = new MyThread("A");
        MyThread b = new MyThread("B");
        MyThread c = new MyThread("C");
        a.setPriority(Thread.MAX_PRIORITY);
        b.setPriority(Thread.NORM_PRIORITY);
        c.setPriority(Thread.MIN_PRIORITY);
        a.start();
        b.start();
        c.start();
    }
}

2023年7月4日,复习多线程,线程的创建,资源争抢,线程的优先级别,线程的命名_垃圾回收器_02

给线程命名的好处有以下几点:

  1. 可读性:给线程命名可以更好地理解和区分不同线程的作用和功能。在复杂的多线程程序中,线程命名可以帮助开发人员更快地理解代码的含义,方便调试和维护。
  2. 调试和日志跟踪:线程命名可以在调试和日志跟踪时提供更有用的信息。通过线程名,可以在日志中准确地追踪和定位特定线程的行为,帮助排查问题和分析程序的运行情况。
  3. 监控和管理:给线程命名可以方便地对线程进行监控和管理。例如,在Java管理扩展(JMX)中,可以通过线程名来获取和管理线程的状态和性能指标,对线程进行监控和调优。
  4. 与其他系统集成:在与其他系统集成时,线程命名可以提供更好的可识别性和可管理性。例如,在分布式系统中,可以通过线程命名来识别和跟踪不同系统组件的线程,方便分析和排查问题。

总之,给线程命名可以提高代码的可读性和可维护性,方便调试和日志跟踪,同时也有助于监控和管理线程,提高系统的可管理性和可扩展性。

使用匿名内部类

package com.wz.thread05;

public class test01 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"中的run()方法被调用了");
            }
        },"线程一");
        thread.start();
    }
}

2023年7月4日,复习多线程,线程的创建,资源争抢,线程的优先级别,线程的命名_线资源争抢_03

使用匿名内部类的好处有以下几点:

  1. 简洁性:使用匿名内部类可以减少代码的量,特别适合于那些只需要使用一次的类或接口。不需要显式地定义一个新的类,而是直接在代码中实现接口或继承类,并在创建对象时进行实例化。
  2. 封装性:使用匿名内部类可以将实现代码封装在一个地方,不会对其他部分产生影响。在上述示例中,匿名内部类实现了Runnable接口,将线程执行的逻辑封装在了匿名内部类的run方法中。
  3. 可读性:匿名内部类可以更直观地展示代码的逻辑关系,使得代码更易于理解和维护。在上述示例中,可以清楚地看到匿名内部类是用来为线程提供执行逻辑的。
  4. 灵活性:由于匿名内部类是在使用时才创建的,因此可以根据需要动态地定制类的行为。可以在匿名内部类中使用外部的局部变量,且这些变量会被自动转换为final类型。

总之,使用匿名内部类可以简化代码、封装逻辑、提高可读性和灵活性。特别适合于只需要使用一次,并且不需要单独定义一个类的场景。