一口气看完系列 java多线程(全)

就从 Java 多线程的概念和实现方案,以及 Java 线程池这三个方面来给你介绍一下
一、Java 多线程的概念
在 Java 中,多线程是指在一个进程中同时执行多个线程,每个线程都可以独立地执行特定的任务。线程是轻量级的进程,它们共享同一个进程的资源,包括内存空间、文件句柄等。通过多线程,可以提高程序的并发性和性能,因为多个线程可以同时执行,从而更好地利用 CPU 资源。
二、Java 多线程的实现方案
Java 中有四种常见的实现多线程的方式,下面是对它们的简要介绍:
1. 继承  Thread  类并重写  run()  方法:
public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码逻辑
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
在这个例子中,创建了一个名为  MyThread  的类,它继承自  Thread  类。在  run()  方法中定义了线程的执行逻辑。然后,在  main()  方法中创建了一个  MyThread  对象,并调用  start()  方法启动线程。

2. 实现  Runnable  接口并实现  run()  方法:
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码逻辑
    }

    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
在这个例子中,创建了一个名为  MyRunnable  的类,它实现了  Runnable  接口。在  run()  方法中定义了线程的执行逻辑。然后,在  main()  方法中创建了一个  MyRunnable  对象,并将其传递给  Thread  类的构造函数,创建一个线程对象。最后,调用  start()  方法启动线程。

3. 使用  Callable  接口和  Future  对象:
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 线程执行的代码逻辑,返回结果
        return "Hello, World!";
    }

    public static void main(String[] args) throws Exception {
        MyCallable callable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();

        String result = futureTask.get();
        System.out.println(result);
    }
}
在这个例子中,创建了一个名为  MyCallable  的类,它实现了  Callable  接口。在  call()  方法中定义了线程的执行逻辑,并返回一个结果。然后,在  main()  方法中创建了一个  MyCallable  对象,并将其传递给  FutureTask  类的构造函数,创建一个  FutureTask  对象。接着,将  FutureTask  对象传递给  Thread  类的构造函数,创建一个线程对象。最后,调用  start()  方法启动线程,并使用  get()  方法获取线程的执行结果。

4. 使用  Executors  类和线程池:
public class Main {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池,大小为 3
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 提交任务给线程池执行
        for (int i = 1; i <= 5; i++) {
            final int taskNumber = i;
            executor.execute(() -> {
                System.out.println("任务 " + taskNumber + " 正在执行,由线程 " + Thread.currentThread().getName() + " 执行");
                try {
                    // 模拟任务执行时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务 " + taskNumber + " 执行完毕");
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}
在这个例子中,使用  Executors.newFixedThreadPool(3)  创建了一个固定大小为 3 的线程池。然后,使用  executor.execute()  方法提交任务给线程池执行。每个任务都是一个匿名内部类,实现了  Runnable  接口,定义了任务的执行逻辑。最后,使用  executor.shutdown()  方法关闭线程池。
以上就是 Java 中实现多线程的四种常见方式。你可以根据具体的需求选择适合的方式来创建和管理线程。
三、Java 线程池
线程池是一种线程管理机制,它可以在程序启动时创建一组线程,并将它们放置在等待任务的队列中。当任务到达时,线程池会从队列中取出任务并分配给空闲的线程执行。这样可以避免频繁地创建和销毁线程,提高程序的性能和效率。
Java 中的线程池是通过  java.util.concurrent  包中的  Executors  类来实现的。 Executors  类提供了一些静态方法,用于创建不同类型的线程池。
线程池有很多好处,比如:
1. 降低资源消耗:通过重复利用已创建的线程,线程池可以减少创建和销毁线程的开销,从而降低系统资源的消耗。

2. 提高响应性:线程池可以根据需要动态调整线程数量,从而提高系统的响应性。当任务负载增加时,线程池可以自动创建更多的线程来处理任务;当任务负载减少时,线程池可以自动销毁多余的线程,以避免资源浪费。

3. 更好的线程管理:线程池可以提供更好的线程管理功能,例如设置线程优先级、设置最大线程数量等。这些功能可以帮助开发人员更好地控制线程的行为,从而提高系统的稳定性和可靠性。
总之,线程池是一种非常有用的线程管理工具,它可以提高程序的性能和效率,降低资源消耗,提高响应性,以及更好地管理线程。在 Java 中,可以使用  Executors  类来创建不同类型的线程池,以满足不同的需求。

附:线程安全性问题是多线程编程中非常重要的一个问题。在多线程环境下,多个线程可能会同时访问同一个共享资源,例如全局变量、静态变量、文件、数据库等。如果没有正确地处理线程安全性问题,就可能会导致竞态条件、数据不一致、死锁等问题。为了避免线程安全性问题,可以使用线程同步机制,例如锁、信号量、条件变量等。线程同步机制可以确保在任何给定的时刻,只有一个线程能够访问共享资源,从而避免竞态条件和数据不一致的问题。

线程安全性问题主要包括竞态条件、数据不一致和死锁。
竞态条件是指在多线程环境下,多个线程同时访问同一个共享资源,并且其中至少一个线程在访问资源时会修改资源的状态,从而导致其他线程访问到的资源状态不一致的情况。
数据不一致是指在多线程环境下,多个线程同时访问同一个共享变量,并且其中至少一个线程在访问变量时会修改变量的值,从而导致其他线程访问到的变量值不一致的情况。
死锁是指在多线程环境下,多个线程相互等待对方释放资源,从而导致程序无法继续执行的情况。
为了避免线程安全性问题,可以使用线程同步机制,例如锁、信号量、条件变量等。线程同步机制可以确保在任何给定的时刻,只有一个线程能够访问共享资源,从而避免竞态条件和数据不一致的问题。

除了上面提到的线程安全性问题和线程同步机制,还有一些其他的线程相关的概念和原理。比如说,线程池是一种线程管理机制,它可以在程序启动时创建一组线程,并将它们放在线程池中,当任务到达时,线程池会从池中选择一个线程来执行任务,执行完任务后,线程会返回线程池,等待下一个任务的到来。线程池可以提高程序的性能和效率,因为它可以减少线程的创建和销毁开销。还有呢,线程的优先级是指线程在调度器中的执行优先级,优先级越高的线程越有可能先被执行。在 Java 中,可以通过设置线程的 priority 属性来设置线程的优先级。最后,线程的状态是指线程在执行过程中的不同阶段,包括新建、就绪、运行、阻塞、等待和死亡等状态。线程的状态转换是由操作系统的调度器来控制的。

附:线程可以分为用户线程和守护线程。用户线程是指执行用户代码的线程,它们与程序的主线程并发执行。守护线程是指在后台执行一些系统级任务的线程,它们与程序的主线程并发执行,但是当主线程结束时,守护线程也会自动结束。线程还可以分为前台线程和后台线程。前台线程是指与用户交互相关的线程,它们与程序的主线程并发执行。后台线程是指在后台执行一些与用户交互无关的任务的线程,它们与程序的主线程并发执行,但是当主线程结束时,后台线程也会自动结束。

守护线程是一种特殊的线程,它与用户线程并发执行,但是当用户线程结束时,守护线程也会自动结束。守护线程通常用于执行一些系统级的任务,比如垃圾回收、资源清理等。
在 Java 中,可以通过设置线程的 daemon 属性来创建守护线程。 daemon 属性的值为 true 表示是守护线程,值为 false 表示是用户线程。默认情况下,线程的 daemon 属性为 false ,即用户线程。
下面是一个示例代码,演示了如何创建守护线程:
 
public class DaemonThreadExample {
    public static void main(String[] args) {
        // 创建一个用户线程
        Thread userThread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("User Thread: " + i);
                try {
                    // 让用户线程休眠 1 秒钟
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 设置用户线程的 daemon 属性为 false
        userThread.setDaemon(false);

        // 启动用户线程
        userThread.start();

        // 创建一个守护线程
        Thread daemonThread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("Daemon Thread: " + i);
                try {
                    // 让守护线程休眠 1 秒钟
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 设置守护线程的 daemon 属性为 true
        daemonThread.setDaemon(true);

        // 启动守护线程
        daemonThread.start();

        // 等待用户线程执行完毕
        try {
            userThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出守护线程是否还在运行
        System.out.println("Daemon Thread is still running: " + daemonThread.isAlive());
    }
}
在上面的示例代码中,首先创建了一个用户线程,并设置了它的 daemon 属性为 false 。然后,创建了一个守护线程,并设置了它的 daemon 属性为 true 。最后,启动了用户线程和守护线程,并等待用户线程执行完毕。在用户线程执行完毕后,输出了守护线程是否还在运行的信息。
需要注意的是,守护线程的生命周期取决于它所守护的用户线程。当用户线程结束时,守护线程也会自动结束。因此,在使用守护线程时,应该确保它所守护的用户线程能够正常结束,否则守护线程可能会一直运行,导致资源泄漏。

-------------------

线程池有好多种,我来给你仔细讲讲吧。最常用的线程池是 ThreadPoolExecutor ,它是 Java 的 Executors 类中的一个静态内部类。 ThreadPoolExecutor 有 7 个参数,分别是:

- corePoolSize:核心线程数,线程池在创建线程时会初始化这个数量的线程,如果没有任务执行,这些线程也不会被销毁,它们会一直等待任务的到来。

- maximumPoolSize:最大线程数,线程池在执行任务时最多可以创建的线程数量,如果任务数量超过了核心线程数,并且线程池中的线程都在执行任务,那么线程池会创建新的线程来执行任务,直到达到最大线程数。

- keepAliveTime:空闲线程的存活时间,如果线程池中的线程数量超过了核心线程数,并且没有任务执行,那么这些空闲线程会在这个时间后被销毁。

- unit:空闲线程的存活时间的单位,可以是秒、分钟、小时等。

- workQueue:任务队列,当任务数量超过了核心线程数,并且线程池中的线程都在执行任务,那么新的任务会被添加到这个队列中,等待线程池中的线程来执行。

- threadFactory:线程工厂,用于创建新的线程。

- handler:拒绝策略,当任务数量超过了核心线程数和最大线程数,并且任务队列已满,那么线程池会根据这个策略来处理新的任务。

拒绝策略有 4 种,分别是:

- AbortPolicy:直接抛出 RejectedExecutionException 异常,这是默认的拒绝策略。

- CallerRunsPolicy:在调用者的线程中执行任务。

-DiscardOldestPolicy:丢弃任务队列中最旧的任务,然后尝试执行新的任务。

-DiscardPolicy:直接丢弃新的任务。

线程池可以解决一些问题,比如:

- 减少线程创建和销毁的开销。

- 控制线程的最大数量,避免资源耗尽。

- 提高程序的性能和效率。

下面是一个使用 Java 创建固定大小线程池的示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小为 3 的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 提交任务到线程池
        for (int i = 1; i <= 5; i++) {
            final int taskNumber = i;
            executor.execute(() -> {
                System.out.println("任务 " + taskNumber + " 正在执行,由线程 " + Thread.currentThread().getName() + " 执行");
                try {
                    // 模拟任务执行时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务 " + taskNumber + " 执行完毕");
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}
在上述示例代码中,我们使用 Executors.newFixedThreadPool(3) 方法创建了一个固定大小为 3 的线程池。然后,我们使用 executor.execute() 方法提交了 5 个任务到线程池。由于线程池的大小为 3,因此最多同时执行 3 个任务,其他任务会等待线程池中的线程空闲后再执行。最后,我们使用 executor.shutdown() 方法关闭线程池,释放资源。
请注意,由于线程池的大小是固定的,因此如果提交的任务数量超过了线程池的大小,那么任务会被阻塞,直到线程池中的线程空闲为止。在实际应用中,需要根据任务的数量和执行时间来合理设置线程池的大小,以避免任务阻塞和性能下降。

除了固定大小线程池,Java 中还有其他几种常见的线程池,包括单个线程的线程池、缓存线程池和定时线程池。下面是它们的示例代码和解释:
1. 单个线程的线程池(SingleThreadExecutor):
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        // 创建一个单个线程的线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 提交任务到线程池
        for (int i = 1; i <= 5; i++) {
            final int taskNumber = i;
            executor.execute(() -> {
                System.out.println("任务 " + taskNumber + " 正在执行,由线程 " + Thread.currentThread().getName() + " 执行");
                try {
                    // 模拟任务执行时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务 " + taskNumber + " 执行完毕");
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}
在上述示例代码中,我们使用 Executors.newSingleThreadExecutor() 方法创建了一个单个线程的线程池。然后,我们使用 executor.execute() 方法提交了 5 个任务到线程池。由于是单个线程的线程池,因此所有任务都会按照提交的顺序依次执行。最后,我们使用 executor.shutdown() 方法关闭线程池,释放资源。
2. 缓存线程池(CachedThreadPool):
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个缓存线程池
        ExecutorService executor = Executors.newCachedThreadPool();

        // 提交任务到线程池
        for (int i = 1; i <= 5; i++) {
            final int taskNumber = i;
            executor.execute(() -> {
                System.out.println("任务 " + taskNumber + " 正在执行,由线程 " + Thread.currentThread().getName() + " 执行");
                try {
                    // 模拟任务执行时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务 " + taskNumber + " 执行完毕");
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}
在上述示例代码中,我们使用 Executors.newCachedThreadPool() 方法创建了一个缓存线程池。然后,我们使用 executor.execute() 方法提交了 5 个任务到线程池。由于是缓存线程池,它会根据需要自动创建和销毁线程,因此可以同时执行多个任务。最后,我们使用 executor.shutdown() 方法关闭线程池,释放资源。
3. 定时线程池(ScheduledThreadPoolExecutor):
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个定时线程池
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        // 提交一个定时任务,每隔 1 秒执行一次
        executor.scheduleAtFixedRate(() -> {
            System.out.println("定时任务执行,当前时间:" + System.currentTimeMillis());
        }, 0, 1, TimeUnit.SECONDS);

        // 提交一个延迟任务,延迟 2 秒后执行
        executor.schedule(() -> {
            System.out.println("延迟任务执行,当前时间:" + System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);

        // 关闭定时线程池
        executor.shutdown();
    }
}
在上述示例代码中,我们使用 Executors.newScheduledThreadPool(2) 方法创建了一个包含 2 个线程的定时线程池。然后,我们使用 executor.scheduleAtFixedRate() 方法提交了一个定时任务,每隔 1 秒执行一次。使用 executor.schedule() 方法提交了一个延迟任务,延迟 2 秒后执行。最后,我们使用 executor.shutdown() 方法关闭定时线程池,释放资源
总之,线程池是一种非常有用的线程管理工具,它可以提高程序的性能和效率,降低资源消耗,提高响应性,以及更好地管理线程。在 Java 中,可以使用  Executors  类来创建不同类型的线程池,以满足不同的需求。

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值