Java线程&线程池

Thread

Java中的Thread是用于实现多线程编程的类。通过创建Thread对象并调用其start()方法,可以启动一个新的线程并执行指定的代码。

Thread使用

以下是一个简单的示例,展示了如何使用Thread类创建和启动一个线程:

public class MyThread extends Thread {
    public void run() {
        // 线程要执行的代码
        System.out.println("Hello from a thread!");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
    }
}

在上面的例子中,我们创建了一个继承自Thread类的自定义线程类MyThread。在run()方法中定义了线程要执行的代码,这里只是简单地打印一条消息。在main()方法中,我们创建了一个MyThread对象,并调用其start()方法来启动线程。

启动线程后,它会在后台执行run()方法中的代码。注意,不要直接调用run()方法,而是使用start()方法来启动线程,这样才能实现多线程并发执行的效果。

需要注意的是,Java中还有其他实现多线程的方式,比如实现Runnable接口、使用线程池等。但Thread类是最基本和常用的多线程编程方式之一。

Thread的一些缺点包括:

  1. 多线程编程复杂性:使用多线程编程需要考虑线程同步、死锁、竞态条件等问题,这增加了程序设计和调试的复杂性。

  2. 内存消耗:每个线程都需要一定的内存来维护线程栈、程序计数器等信息,当线程数量过多时,会增加内存消耗。

  3. 上下文切换开销:线程之间的切换需要保存和恢复上下文信息,这会带来一定的开销。

  4. 线程安全问题:多线程环境下,共享资源的访问需要进行同步,否则可能会出现数据竞争和不一致的问题。

  5. 难以调试:多线程程序的调试相对复杂,由于线程的并发执行,问题的复现和定位可能会更加困难。

  6. 可能导致死锁:如果线程之间存在循环等待资源的情况,就可能导致死锁,使得程序无法继续执行。

总之,虽然Java Thread提供了多线程编程的便利性,但也存在一些缺点,需要开发人员在设计和实现时注意避免潜在的问题。

线程池(ExecutorService、ThreadPool)

newCachedThreadPool

newCachedThreadPool是Java中的一个线程池创建方法,它返回一个ThreadPoolExecutor对象,该对象可以用于执行多个任务。它是一个可缓存的线程池,可以根据需要创建新的线程,如果线程池中的线程空闲时间超过指定的时间(默认为60秒),则会被终止并移除。当任务到达时,如果线程池中有空闲线程,则会立即执行任务;如果没有空闲线程,则会创建新的线程来执行任务。适用于执行大量的短期异步任务的场景,不适用于长期运行的任务。

使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        // 提交任务给线程池执行
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Task " + taskId + " is being executed.");
                }
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

在上面的示例中,我们首先使用Executors.newCachedThreadPool()方法创建一个可缓存的线程池。然后,我们使用execute()方法向线程池提交了10个任务,每个任务都是一个Runnable对象,用于打印任务的编号。最后,我们调用shutdown()方法关闭线程池。

newFixedThreadPool

newFixedThreadPool 是Java中的一个线程池创建方法。它创建一个固定大小的线程池,其中线程的数量是固定的,不会根据任务的数量进行动态调整。

使用 newFixedThreadPool 方法创建的线程池可以同时执行指定数量的任务,当有新的任务提交时,如果线程池中有空闲的线程,则会立即执行;如果线程池中没有空闲的线程,则新的任务会被放入任务队列中等待执行,直到有线程空闲为止。

这种线程池适用于需要控制并发线程数量的场景,可以有效地控制系统资源的使用,避免因为线程过多而导致系统负载过高的问题。

使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        // 提交任务给线程池
        for (int i = 0; i < 10; i++) {
            final int taskNum = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("正在执行任务 " + taskNum);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务 " + taskNum + " 执行完毕");
                }
            });
        }

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

上述示例中,我们首先通过Executors.newFixedThreadPool(5)创建了一个固定大小为5的线程池。然后,我们通过executor.execute()方法提交了10个任务给线程池进行执行。每个任务都会打印出自己的任务编号,并在执行完毕后打印出执行完毕的消息。最后,我们通过executor.shutdown()方法关闭线程池。

使用线程池可以更好地管理和控制线程的执行,提高程序的性能和效率。newFixedThreadPool方法创建的线程池具有固定大小,适用于需要控制并发线程数量的场景。

newScheduledThreadPool

newScheduledThreadPool是Java中的一个方法,用于创建一个可调度的线程池。它返回一个ScheduledExecutorService对象,可以用来执行定时任务和周期性任务。

使用示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
        
        // 执行定时任务
        executor.schedule(() -> {
            System.out.println("定时任务执行");
        }, 1, TimeUnit.SECONDS);
        
        // 执行周期性任务
        executor.scheduleAtFixedRate(() -> {
            System.out.println("周期性任务执行");
        }, 0, 2, TimeUnit.SECONDS);
        
        // 关闭线程池
        executor.shutdown();
    }
}

在上述示例中,我们通过Executors.newScheduledThreadPool(5)创建了一个可调度的线程池,其中参数5表示线程池的大小为5。然后,我们可以使用返回的ScheduledExecutorService对象执行定时任务和周期性任务。最后,通过调用executor.shutdown()方法来关闭线程池。

newSingleThreadExecutor

newSingleThreadExecutor 是 Java 中的一个线程池创建方法,用于创建一个只有一个线程的线程池。它的定义如下:

ExecutorService executor = Executors.newSingleThreadExecutor();

这个线程池只会创建一个线程来执行任务,当这个线程执行完一个任务后,会接着执行下一个任务。如果有多个任务提交给这个线程池,它们会按照提交的顺序依次执行。

使用 newSingleThreadExecutor 可以保证任务按照顺序执行,适用于需要顺序执行的场景,比如需要按照任务的提交顺序来处理数据的情况。

示例代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        // 提交任务给线程池执行
        executor.submit(() -> {
            System.out.println("Task 1 is running");
        });

        executor.submit(() -> {
            System.out.println("Task 2 is running");
        });

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

在上面的示例中,我们首先使用Executors.newSingleThreadExecutor()方法创建了一个单线程的线程池。然后,我们使用executor.submit()方法提交了两个任务给线程池执行。最后,我们调用executor.shutdown()方法关闭线程池。

由于newSingleThreadExecutor创建的是一个单线程的线程池,所以任务会按照提交的顺序依次执行。这样可以保证任务之间的顺序性,适用于需要按照顺序执行的场景。

Java提供的四种线程池的优点

Java提供了四种线程池,分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool和SingleThreadPool。它们各自有不同的优点:

  1. FixedThreadPool:固定大小的线程池。它适用于需要控制并发线程数的场景,可以限制线程的数量,避免资源耗尽。适用于执行长期的任务,效率高。

  2. CachedThreadPool:可缓存的线程池。它适用于执行大量的短期任务,可以根据需要自动创建新的线程,线程池的大小可以根据任务的多少自动调整。适用于执行大量的耗时较短的任务,灵活性高。

  3. ScheduledThreadPool:定时任务线程池。它适用于需要定时执行任务的场景,可以按照指定的时间间隔周期性地执行任务。适用于需要定时执行任务的场景,如定时备份、定时统计等。

  4. SingleThreadPool:单线程池。它适用于需要保证任务按照顺序执行的场景,所有任务都在同一个线程中按照指定的顺序执行。适用于需要保证任务按照顺序执行的场景,如消息队列等。

这四种线程池都是通过线程池Executor框架提供的,可以有效地管理和复用线程,提高程序的性能和效率。

自定义线程池

自定义线程池步骤:

  1. 创建一个线程池类,可以命名为CustomThreadPool
  2. 在该类中,需要定义以下属性:
    • ThreadPoolExecutor对象:用于管理线程池的执行和调度。
    • int类型的corePoolSize:指定线程池的核心线程数,即线程池中保持活动状态的线程数。
    • int类型的maximumPoolSize:指定线程池的最大线程数,即线程池中允许的最大线程数。
    • long类型的keepAliveTime:指定线程池中非核心线程的闲置超时时间。
    • BlockingQueue<Runnable>类型的workQueue:用于存放待执行的任务的阻塞队列。
  3. CustomThreadPool类的构造方法中,初始化线程池对象,并设置相关属性。
  4. 提供方法来提交任务到线程池中执行,可以命名为submitTask方法。该方法接受一个Runnable类型的任务作为参数,并将任务提交到线程池中执行。
  5. 可以根据需要,提供其他方法来管理线程池,如获取当前活动线程数、获取线程池中的任务数量等。

示例代码:

import java.util.concurrent.*;

public class CustomThreadPool {
    private ThreadPoolExecutor threadPool;

    public CustomThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, BlockingQueue<Runnable> workQueue) {
        threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, workQueue);
    }

    public void submitTask(Runnable task) {
        threadPool.submit(task);
    }

    public int getActiveThreadCount() {
        return threadPool.getActiveCount();
    }

    public int getTaskCount() {
        return threadPool.getQueue().size();
    }
}

使用自定义线程池的示例代码:

public class Main {
    public static void main(String[] args) {
        // 创建一个阻塞队列作为任务队列
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);

        // 创建自定义线程池对象
        CustomThreadPool threadPool = new CustomThreadPool(5, 10, 5000, workQueue);

        // 提交任务到线程池中执行
        for (int i = 0; i < 20; i++) {
            final int taskNum = i;
            threadPool.submitTask(() -> {
                System.out.println("Task " + taskNum + " is running.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskNum + " is finished.");
            });
        }

        // 输出线程池中的活动线程数和任务数量
        System.out.println("Active Thread Count: " + threadPool.getActiveThreadCount());
        System.out.println("Task Count: " + threadPool.getTaskCount());

        // 关闭线程池
        threadPool.shutdown();
    }
}

以上代码示例中,创建了一个自定义线程池对象threadPool,并提交了20个任务到线程池中执行。然后通过getActiveThreadCount方法和getTaskCount方法获取线程池中的活动线程数和任务数量。最后调用shutdown方法关闭线程池。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,252评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,886评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,814评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,869评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,888评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,475评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,010评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,924评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,469评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,552评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,680评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,362评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,037评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,519评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,621评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,099评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,691评论 2 361

推荐阅读更多精彩内容