Java多线程Thread详细讲解(万字教程)二

简介: Java多线程Thread详细讲解(万字教程)

Java如何开启线程? 方式2:实现Runnable接口!(详细讲解)


这种方式的本质还是第一种,即Thread类实例调用start方法,从而去调用本地线程资源。因为有且仅有Thread类能通过start0()方法向操作系统申请线程资源(本地方法)!


JVM对于操作系统来说就是一个程序,是一个进程,JVM进程中可以有很多个线程,我们调用start方法其实就是去申请这个线程资源。


Runnable接口:


@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}


看上面的注释就知道怎么用了,当一个对象实现Runnable接口,你就去创建一个Thread,启动这个Thread,会导致run方法被调用。


我们按照官方的指导来做:


public class MyThread02 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行了!");
    }
    public static void main(String[] args) {
        new Thread(new MyThread02()).start();
    }
}


看到了吧,这种方式其实还是第一种。不同点在于,第一种方式是直接在Thread子类中重写的Run方法,而这种方式是你自己弄个类,里面有个run方法,传到Thread类中去,最后在Thread类启动的时候,会去调用那个类的run方法。换句话说,如果没有Thread类,这个Runnable实现类是没法自己跑起来的。


所有的秘密就在Thread类的构造函数里面。


public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}


调了init方法


private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
    init(g, target, name, stackSize, null);
}


又是重载,继续找:


private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    //省略其他代码...
    this.target = target;
}

原来,传入的Runnable对象是放到属性target中了。


/* What will be run. */
private Runnable target;


我们再看下Thread类中的run方法:


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


如果是方式1,这个run方法被重写了,启动start方法的时候就直接调用那个被重写后的run方法了。现在使用方式2的话,就还是调用Thread原生的run方法,做了一个中介,又去调用target的run方法,而target是我们自己传进去的,相当于把具体run的逻辑写到外部一个类里面去了。


以上就是第二种方式的原理,下面介绍一下如何简写代码?


比如,我们可以直接把Runnable实现类用匿名的方式new出来(少些一个类):


public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "执行了!");
        }
    }).start();
}


因为Runnable接口只有一个抽象方法,所以还可以直接用Lamda表达式:


new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "执行了!");
}).start();


6.png


如果还不明白原理,可以看下这张图,安排的明明白白啦。


Java如何开启线程? 方式3:用线程池Executor


什么是线程池:  java.util.concurrent.Executors 提供了一个 java.util.concurrent.Executor 接口的实现用于创建线程池。


一个线程池包括以下四个基本组成部分:

               1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

               2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

               3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

               4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。


7.png


8.png


线程池的作用:


线程池作用就是限制系统中执行线程的数量。

    根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。


为什么要用线程池:


1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。


2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。


Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。


微信截图_20230504214123.png


要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。


1. newSingleThreadExecutor


创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。


2.newFixedThreadPool


创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。


3. newCachedThreadPool


创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,


那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。


4.newScheduledThreadPool


创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。


为了方便演示,我们就创建只有一个线程的线程池:


package com.javaxbfs.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyThread03 implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //提交线程任务-Runnable对象
        Future<?> future = executorService.submit(new MyThread03());
        while(!future.isDone()){
           Thread.sleep(200);
        }
        // 关闭线程池
        executorService.shutdown();
        System.out.println("线程池关闭");
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "========>正在执行!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "========>执行成功!");
    }
}

效果:


pool-1-thread-1========>正在执行!
pool-1-thread-1========>执行成功!
线程池关闭


这边我们使用的是Runnable对象,线程池还可以接受Callable对象。


package com.javaxbfs.thread;
import java.util.concurrent.*;
public class MyThread04 implements Callable {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //创建线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //提交线程任务-Runnable对象
        Future<String> future = executorService.submit(new MyThread04());
        while(!future.isDone()){
           Thread.sleep(200);
        }
        String result = future.get();
        System.out.println("这里拿到线程任务的返回值:" + result);
        // 关闭线程池
        executorService.shutdown();
        System.out.println("线程池关闭");
    }
    @Override
    public String call() {
        System.out.println(Thread.currentThread().getName() + "========>正在执行!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "========>执行成功!");
        return "SUCCESS";
    }
}


效果:


pool-1-thread-1========>正在执行!
pool-1-thread-1========>执行成功!
这里拿到线程任务的返回值:SUCCESS
线程池关闭


两种方式我都写了例子,关于这个后面还会细说。


总结

1. 不管你是用上面介绍的三种方法的哪一种,都绕不开Thread类,因为只有Thread类的start方法可以去申请本地线程资源!


2. 用Runnable可以把任务代码写在Thread类外面,降低耦合度,更加灵活。


3.Callable一般配合线程池使用,重写的call方法可以添加返回值。


4.线程执行入口永远是Thread类的run方法,这个run方法要么被你重写,要么调用target的run方法,target就是实现runnable接口的对象。



封装Thread类的start方法(经典技巧)


Thread类的start方法是去请求本地资源的,很多源码喜欢把这个细节封装掉,现在让我们来模仿一下这种技巧。


package com.javaxbfs.thread;
public class MyThread05 implements Runnable{
    public void begin(){
        new Thread(this).start();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行了!");
    }
    public static void main(String[] args) {
        MyThread05 myThread05 = new MyThread05();
        myThread05.begin();
    }
}


思路特别简单,不需要你手动去new Thread类了,而是直接在Runnable类中添加begin方法,把这个事情给做了。


相关文章
|
11天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
13天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
13天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
10天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
10天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
安全 Java 程序员
4月7日云栖精选夜读:给 Java 学习者的超全教程整理
作为Java程序员的我们,应该不仅对这门语言有所了解,而且我们在平常编程时也需要使用众多的库。比如小编知道的,如果要学习Java Web的话,SSH(Spring, Struts和Hibernate)肯定得会吧,或者至少了解基本的原理吧。
2769 0
|
13天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
35 3
|
13天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
93 2
|
21天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
46 6
|
2月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####