线程介绍及创建方式


一、线程是什么,相关概念?

讲解线程之前我们先了解一下其它相关的概念:
程序:是为了完成特定任务,用某种语言编写的一组指令的集合,简单来说就是,我们写的代码
示例:

    //判断号码的格式是否正确
    public boolean isValidMobileNumber(String str){
    
    
        if (str.length() !=11 ){
    
    
            return false;
        }
        for (int i=0; i<str.length();i++ ){
    
    
            char a = str.charAt(i);
            if (a<'0' || a>'9'){
    
    
                return false;
            }
        }
        return true;
    }

在此之上:
进程:是程序的一次执行过程,或是正在运行的一个程序,是动态过程,有它自身的产生,存在和消亡的过程 。你打开一个程序就是打开了一个进程
在这里插入图片描述
线程:线程是由进程产生的,是进程的一个实体,一个进程可以产生多个线程。示例:打开百度网盘下载多个资源。

单线程:同一时刻,只允许执行一个线程执行
多线程:同一时刻,只允许执行多个线程执行

并发:同一时刻,多个任务交替执行,单核CPU实现的多任务
并行:多任务同时执行,多核CPU实现
示例:当一个人一边开车一边打电话相当于是一个并发,而当两个人一个开车,另一个打电话相当于并行。

这里提到一个法可以获取直接电脑的处理核心数:

public class CpuNum {
    
    
    public static void main(String[] args) {
    
    
        Runtime runtime = Runtime.getRuntime();
        //获取当地电脑的核心数量
        int cpuNum = runtime.availableProcessors();
        System.out.println(cpuNum);

    }
}

   Runtime类是Java中与运行时环境相关的类,主要用于获取当前JVM的运行状态、内存使用情况、执行系统命令等操作。其中,availableProcessors()方法可以获取当前计算机的CPU核心数量,该方法返回一个int类型的值。 这段代码使用Java中的Runtime类获取当前运行时环境,然后调用其getRuntime()方法获取Runtime实例。接着通过调用availableProcessors()方法获取当前电脑的CPU核心数量,并将其赋值给变量cpuNum。最后通过调用System.out.println()方法将cpuNum输出到控制台。

二、创建线程的四种方式

1.继承Thread类

继承Thread类:通过继承Thread类,重写其run()方法,然后创建线程对象并调用start()方法来启动线程。

class MyThread extends Thread {
    
    
    public void run() {
    
    
        // 线程执行的代码
    }
}

// 创建线程对象并启动线程
MyThread thread = new MyThread();
thread.start();

优点::编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this即可获取当前线程

缺点:因为线程类已经继承了Thread类,Java语言是单继承的,所以就不能再继承其他父类了。

2.实现Runnable接口

代码如下(示例):

class MyRunnable implements Runnable {
    
    
    public void run() {
    
    
        // 线程执行的代码
    }
}

// 创建Runnable对象
Runnable myRunnable = new MyRunnable();

// 创建Thread对象,并将Runnable对象传递给Thread的构造函数
Thread thread = new Thread(myRunnable);

// 启动线程
thread.start();

3.实现Callable接口(JDK>=1.5)

代码如下(示例):

class MyCallable implements Callable<Integer> {
    
    
    public Integer call() throws Exception {
    
    
        // 线程执行的代码
        return result;
    }
}

// 创建Callable对象
Callable<Integer> myCallable = new MyCallable();

// 创建ExecutorService对象
ExecutorService executor = Executors.newSingleThreadExecutor();

// 提交Callable任务并获取Future对象
Future<Integer> future = executor.submit(myCallable);

// 获取线程返回结果
Integer result = future.get();

// 关闭ExecutorService
executor.shutdown();

两个实现接口的方法:
优点:线程类只是实现了Runnable或者Callable接口,还可以继承其他类。这种方式下,多个线程 可以共享一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。

缺点:编程稍微复杂一些,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法

4.线程池方式创建

示例代码:

// 创建线程池对象
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交Runnable任务给线程池执行
executor.execute(new Runnable() {
    
    
    public void run() {
    
    
        // 线程执行的代码
    }
});

// 关闭线程池
executor.shutdown();

线程池创建线程的优点包括:

  1. 提高系统性能:线程池可以复用线程,减少线程的创建和销毁开销,提高系统的性能。
  2. 资源管理:线程池可以控制线程的数量,避免线程数量过多导致系统资源耗尽。
  3. 提高代码可读性:使用线程池可以更好地实现任务和线程的分离,提高代码的可读性和可维护性。

线程池创建线程的缺点包括:

  1. 需要额外学习:使用线程池需要理解线程池的工作原理和使用方式,需要一定的学习成本。
  2. 可能会浪费资源:如果线程池的大小设置不合理,可能会浪费系统资源或者导致任务等待时间变长。
  3. 可能会引起死锁:如果线程池中的任务依赖于其他任务的结果,可能会发生死锁现象。

三、Thread local的用法

ThreadLocal是Java中的一个线程局部变量,它可以让每个线程在访问同一个变量时,都有自己独立的副本。ThreadLocal通常用于解决多线程并发访问共享变量的线程安全问题。

ThreadLocal的基本用法是,首先创建一个ThreadLocal对象,然后通过调用它的set()方法来设置当前线程的局部变量值,通过调用get()方法来获取当前线程的局部变量值。

public class UserContext {
    
    
    private static final ThreadLocal<String> userThreadLocal = new ThreadLocal<>();

    public static void setUser(String username) {
    
    
        userThreadLocal.set(username);
    }

    public static String getUser() {
    
    
        return userThreadLocal.get();
    }
}

在上述代码中,我们首先创建了一个ThreadLocal对象userThreadLocal,它的泛型参数为String类型。然后我们定义了两个静态方法,分别用于设置和获取当前线程的用户名。在setUser()方法中,我们调用ThreadLocal的set()方法来设置当前线程的用户名;在getUser()方法中,我们调用ThreadLocal的get()方法来获取当前线程的用户名。

使用ThreadLocal的好处在于,每个线程都有自己独立的副本,它们之间互不干扰,不需要加锁或者使用同步机制,从而避免了线程安全问题。例如,在上述代码中,如果多个线程同时访问UserContext.setUser()方法,每个线程都会有自己独立的副本,不会相互干扰,从而保证了线程安全。

需要注意的是,ThreadLocal虽然可以解决线程安全问题,但过多的使用ThreadLocal也会导致内存泄漏和性能问题,因为每个ThreadLocal都会占用一定的内存,而且ThreadLocal的使用也会增加线程上下文切换的开销。因此,在使用ThreadLocal时需要注意控制数量和生命周期,避免出现内存泄漏和性能问题。

总结

本章内容主要讲了线程的几个基本概念,以及创建线程的几种方式,这几种方式的优缺点,最后讲了ThreadLocal的用法。

猜你喜欢

转载自blog.csdn.net/a545__27/article/details/131693191