java多线程知识点总结

时间:2023-02-26 18:31:54

1.几个基本的概念

进程(Process):

一个程序运行起来时在内存中开辟一段空间用来运行程序,这段空间包括heap、stack、data segment和code segment。例如,开一个QQ就表明开了一个QQ进程。

线程(Thread):

每个进程中都至少有一个线程。线程是指程序中代码运行时的运行路径,一个线程表示一条路径。例如QQ进程中,发送消息、接收消息、接收文件、发送文件等各种独立的功能都需要一个线程来执行。

进程和线程的区别:

从资源的角度来考虑,进程主要考虑的是CPU和内存,而线程主要考虑的是CPU的调度,某进程中的各个线程之间可以共享这个进程的很多资源。

从粒度粗细来考虑,进程的粒度较粗,进程上下文切换时消耗的CPU资源较多。线程的粒度要小的多,虽然线程也会切换,但因为共享进程的上下文,相比进程上下文切换而言,同进程内的线程切换时消耗的资源要小的多的多。在JAVA中,除了java运行时启动的JVM是一个进程,其他所有任务都以线程的方式执行,也就是说java应用程序是单进程的,甚至可以说没有进程的概念。

线程组(ThreadGroup):

线程组提供了一些批量管理线程的方法,因此通过将线程加入到线程组中,可以更方便的管理这些线程

线程的状态

就绪态、运行态、睡眠态。还可以分为存活和死亡,死亡表示线程结束,非死亡则存活,因此存活包含就绪、运行、睡眠。

 java多线程知识点总结

java thread的运行周期中, 有几种状态, 在 java.lang.Thread.State 中有详细定义和说明:

NEW 状态是指线程刚创建, 尚未启动

RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等

BLOCKED  这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区

WAITING  这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束

TIMED_WAITING  这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态

TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

 

中断睡眠(interrupt):

将线程从睡眠态强制唤醒,唤醒后线程将进入就绪队列等待cpu调度。

并发操作:

多个线程同时操作一个资源。这会带来多线程安全问题,解决方法是使用线程同步。

线程同步:

让线程中的某些任务原子化,即要么全部执行完毕,要么不开始执行。通过互斥锁来实现同步,通过监视这个互斥锁是否被谁持有来决定是否从睡眠态转为就绪态(即从线程池中出去),也就是是否有资格去获取cpu的执行权。线程同步解决了线程安全的问题,但降低了程序的效率。

死锁:

线程全睡眠了无法被唤醒,导致程序卡死在某一处无法再执行下去。典型的是两个同步线程,线程1持有A锁,且等待B锁,但线程2持有B锁且等待A锁,这样的僵局会造成死锁。但需要注意的是,死锁并非都是因为僵局,只要两边的线程都无法继续向下执行代码(或者两边的线程池都无法被唤醒,这是等价的概念,因为锁等待也会让进程进入睡眠态),则都是死锁

 

还需需要明确的一个关键点是:CPU对就绪队列中每个线程的调度是随机的(对我们人类来说),且分配的时间片也是随机的(对人类来说)。

 

 

2.创建线程的两种方法

继承Thread类

创建线程方式一:

  1. 继承Thread类(在java.lang包中),并重写该类的run()方法,其中run()方法即线程需要执行的任务代码。
  2. 然后new出这个类对象。这表示创建线程对象。
  3. 调用start()方法开启线程来执行任务(start()方法会调用run()以便执行任务)。
java多线程知识点总结java多线程知识点总结
class MyThread extends Thread {
    String name;
    String gender;

    MyThread(String name,String gender){
        this.name = name;
        this.gender = gender;
    }

    public void run(){
        int i = 0;
        while(i<=20) {
            //除了主线程main,其余线程从0开始编号,currentThread()获取的是当前线程对象
            System.out.println(Thread.currentThread().getName()+"-----"+i+"------"+name+"------"+gender);
            i++;
        }
    }
}

public class CreateThread {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("malong","Male");
        MyThread mt2 = new MyThread("Gaoxiao","Female");

        mt1.start();
        mt2.start();
        System.out.println("main thread over");
    }
}
View Code

 

创建线程方式二:

  1. 实现Runnable接口,并重写run()方法。
  2. 创建子类对象。
  3. 创建Thread对象来创建线程对象,并将实现了Runnable接口的对象作为参数传递给Thread()构造方法。
  4. 调用start()方法开启线程来执行run()中的任务。
java多线程知识点总结java多线程知识点总结
class MyThread implements Runnable {
    String name;
    String gender;

    MyThread(String name,String gender){
        this.name = name;
        this.gender = gender;
    }

    public void run(){
        int i = 0;
        while(i<=200) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
            i++;
        }
    }
}

public class CreateThread2 {
    public static void main(String[] args) {
        //创建子类对象
        MyThread mt = new MyThread("malong","Male");
        //创建线程对象
        Thread th1 = new Thread(mt);
        Thread th2 = new Thread(mt);

        th1.start();
        th2.start();
        System.out.println("main thread over");
    }
}
View Code

 

这两种创建线程的方法,无疑第二种(实现Runnable接口)要好一些,因为第一种创建方法继承了Thread后就无法继承其他父类。