(一)多线程原理与实战

程序、进程、线程

程序

程序是「静态」的,存放在硬盘中的可执行文件,包括「代码指令」和「数据」

进程

进程是「运行中的程序」,是程序的一次启动和执行:操作系统从磁盘载入程序到内存,分配必要的资源,开始运行程序的指令

进程的组成:「代码段」,「数据段」,「PCB」(id,name,status,priority,物理地址,context,file,others)(描述信息,调度信息,资源信息,上下文)

线程

线程是“进程代码段”的一段「执行流程」

线程的组成:「PC」,「栈内存」(方法的调用和返回对应了栈帧的入栈和出栈),「线程基本信息」(id,name,status,priority,others)

进程和线程的区别

  • 一个进程至少有一个线程

  • 线程之间共享同一块地址空间(方法去内存,堆内存),打开的文件和其他资源,故上下文切换速度较快

  • 调度执行的基本单位:线程

  • 资源分配的基本单位:进程

Thread in Java

Thread

image.png

public void start()用来启动一个线程,当调用start()方法后,JVM才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中会为相应的线程分配需要的资源

public void run()是线程代码的逻辑入口,不是给用户程序来调用的,当调用start()方法之后,只要该线程获得了CPU时间片,就会执行run()方法

创建一个空线程

public class Nov29 {
    public static void main(String[] args) {
        Thread emptyThread = new Thread("empty thread");
        emptyThread.setPriority(5);
        System.out.println("emptyThread.getId() = " + emptyThread.getId());
        System.out.println("emptyThread.getName() = " + emptyThread.getName());
        System.out.println("emptyThread.getPriority() = " + emptyThread.getPriority());
        emptyThread.start();
        System.out.println("end of emptyThread");
    }
}
复制代码

Thread类中,由于target对象为nullrun()方法什么也没做就结束了

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}
复制代码

继承Thread

  1. 继承Thread
  2. 重写run()方法
public class FirstThread extends Thread{
    @Override
    public void run(){
        System.out.println(" run() function in FirstThread ");
    }
}
复制代码
public class Nov29 {
    public static void main(String[] args) {
        Thread firstThread = new FirstThread();
        firstThread.start();
    }
}
复制代码

实现Runnable接口

  1. 定义一个类实现Runnable接口

  2. 实现run()方法

  3. 通过Thread类的构造方法public Thread(Runnable target)传入该类

函数式接口:有且仅有一个方法的接口

public class SecondThread implements Runnable{
    @Override
    public void run() {
        System.out.println(" run() in SecondThread ");
    }
}
复制代码
public class Nov29 {
    public static void main(String[] args) {
        SecondThread secondThread = new SecondThread();
        Thread thread = new Thread(secondThread);
        thread.start();
    }
}
复制代码

对比:

  1. 通过继承Thread类来创建线程,可以直接使用Thread类的实例的方法

  2. 实现Runnable接口来创建线程则不能直接调用Thread类的实例的方法,需要通过Thread类的静态方法public Thread currentThread()来获取当前类

  3. 在实际开发中,由于Java不允许多继承,所以一般使用实现Runnable接口的方法来创建线程

  4. 通过实现Runnable接口的方法来创建线程能更好地实现数据和逻辑的分离

  5. Thread继承了Runnable接口,其中的run()方法无返回值,若需要异步获取程序的执行结构,应该采用实现Callable的方式

数据和逻辑的分离

todo:这个不太理解,之后需要细看

匿名内部类

public class ThirdThread {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("new a thread using anonymous inner class");
                System.out.println("implement method in Runnable");
            }
        }).start();
    }
}
复制代码
class ThirdThread {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                System.out.println("new a thread using anonymous inner class");
                System.out.println("override method in Thread");
            }
        }.start();
    }
}
复制代码

Lambda表达式

一行代码搞定

public class Nov29 {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("new a thread using lambda")).start();
    }
}
复制代码

实现Callable接口

public class FourthThread {
    // 1. 新建Callable实现类
    static class RunnableTask implements Callable<Long> {
        // 2. 重写其中的call()方法,可以有返回值
        @Override
        public Long call() throws Exception {
            Thread.sleep(3000);
            return Long.MAX_VALUE;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 3. 使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例。
        RunnableTask runnableTask = new RunnableTask();
        FutureTask<Long> longFutureTask = new FutureTask<>(runnableTask);
        Thread thread = new Thread(longFutureTask);
        // 4. 调用Thread实例的start()方法启动新线程,启动新线程的run()方法并发执行
        // 其内部的执行过程为:Thread实例的run(),FutureTask实例的run()方法,Callable实现类的call()方法
        thread.start();
        // 5. 调用FutureTask对象的get()方法阻塞性地获得并发线程的执行结果
        System.out.println("longFutureTask.get() = " + longFutureTask.get());
    }
}
复制代码

使用线程池

不推荐使用 Executors 创建线程池

public class ThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("无返回值execute()");
            }
        });
        System.out.println("有返回值submit()" + pool.submit(new FourthThread.RunnableTask()).get());
    }
}
复制代码
  • execute() ,无返回值,参数只能是Runnable实现类,不能异步控制只能等其自动停止

  • submit() ,有返回值,参数可以是有返回值的Callable实现类,可以异步控制

Java线程原理

Java线程和操作系统线程

Java虚拟机把Java线程一对一映射到操作系统线程,线程的调度交给操作系统

在JVM看来,操作系统视角下的 running 状态和 ready 状态统一称为 runnable 状态

优先级

Java线程具有优先级,优先级高的获得调度机会统计规律上大于优先级低的线程,但不绝对

生命周期

image.png

public enum State {
    NEW,
    RUNNABLE, // start()方法调用后,就是runnable状态,但并不一定立刻获得CPU时间片执行run()方法
    BLOCKED, 
    WAITING,
    TIMED_WAITING,
    TERMINATED; // 抛出异常或run()方法执行完
}
复制代码

Java线程基本操作

操作

  • sleep:使线程休眠

  • yield:让出CPU时间片,让操作系统重新调度一次

  • interrupt:将线程状态设置为中断,不会像stop()方法那样中止一个线程,线程状态设置为中断后线程自己判断是继续执行还是终止

  • join:合并两个线程,让其中一个等待另一个执行完毕再执行;或者设置一个时间,时间到了就不等了

  • daemon:守护线程(为用户进程提供服务的进程,与用户进程的关系是和JVM线程的关系一个是主动一个是被动)

调试

➜  ~ jps
1217 Launcher
1218 ThreadSleep
1220 Jps
807
➜  ~ jstack 1218
2021-11-30 11:20:29
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.301-b09 mixed mode):

"Attach Listener" #12 daemon prio=9 os_prio=31 tid=0x00007fbfcc9fd800 nid=0x4503 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #11 prio=5 os_prio=31 tid=0x00007fbfcc9fa000 nid=0x1003 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-0" #10 prio=5 os_prio=31 tid=0x00007fbfca8eb800 nid=0x3c03 waiting on condition [0x000070000d21f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at ThreadSleep.lambda$main$0(ThreadSleep.java:5)
	at ThreadSleep$$Lambda$1/1828972342.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

"Service Thread" #9 daemon prio=9 os_prio=31 tid=0x00007fbfca86b800 nid=0x4703 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007fbfcc04b000 nid=0x4803 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007fbfcc8b5000 nid=0x3703 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007fbfcc04a000 nid=0x3503 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007fbfca85a000 nid=0x4903 runnable [0x000070000cc0d000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	- locked <0x0000000795713788> (a java.io.InputStreamReader)
	at java.io.InputStreamReader.read(InputStreamReader.java:184)
	at java.io.BufferedReader.fill(BufferedReader.java:161)
	at java.io.BufferedReader.readLine(BufferedReader.java:324)
	- locked <0x0000000795713788> (a java.io.InputStreamReader)
	at java.io.BufferedReader.readLine(BufferedReader.java:389)
	at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:49)

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fbfcc049000 nid=0x4a03 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fbfca81c800 nid=0x5103 in Object.wait() [0x000070000c901000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x0000000795588ee0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
	- locked <0x0000000795588ee0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fbfcb01b000 nid=0x5203 in Object.wait() [0x000070000c7fe000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x0000000795586c00> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x0000000795586c00> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=31 tid=0x00007fbfca817800 nid=0x2b03 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fbfcc00a000 nid=0x2407 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fbfcc80e800 nid=0x2003 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fbfca80e000 nid=0x2203 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fbfcc80f000 nid=0x2a03 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007fbfcc04e000 nid=0x3b03 waiting on condition

JNI global references: 319
复制代码

猜你喜欢

转载自juejin.im/post/7036204670943150088