Java多线程与线程池技术分享

Java多线程与线程池技术分享

1. 多线程

什么是线程?

线程是一个程序执行流的最小单元,一个程序至少有一个执行流,即主线程。主线程在JVM启动时就存在了。

创建线程的方式

继承Thread类

重写Thread类的run()方法。

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程体
    }
}

实现Runnable接口

实现Runnable接口中的run()方法。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程体
    }
}

使用匿名内部类

new Thread(new Runnable() {
    @Override
    public void run() {
        // 线程体
    }
}).start();

线程状态

  • new:线程创建后尚未启动。
  • runnable:可运行状态,可运行但可能暂停执行。
  • blocked:阻塞状态,表示线程因为某些原因暂停执行。
  • waiting:等待状态,线程进入该状态表示在等待某个条件发生,按需等待或按时间等待。
  • time waiting:超时等待状态,等待某个条件的发生,超时后自动执行。
  • terminated:终止状态。

线程同步

synchronized关键字

synchronized是Java提供的一种锁机制,保证同一时刻只有一个线程可以访问同步代码块。

synchronized (锁对象) {
    // 同步代码块
}

Lock接口

Lock接口提供比synchronized更细粒度的同步机制。在编程中使用ReentrantLock。

Lock lock = new ReentrantLock();
lock.lock();
try {
    // 同步代码块
} finally {
    lock.unlock(); // 必须释放锁,否则会导致死锁
}

线程间通信

wait、notify、notifyAll方法

wait方法释放对象锁并挂起当前线程,notify方法唤醒等待该对象锁的线程。

synchronized (锁对象) {
    while (条件不满足) {
        锁对象.wait();
    }
    // 条件满足执行操作
    // 唤醒其他线程
    锁对象.notify();
    // 或者唤醒所有等待线程
    锁对象.notifyAll();
}

Condition接口

Condition接口提供的await、signal、signalAll方法与wait、notify、notifyAll方法类似。在编程中使用ReentrantLock配合Condition。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
    while (条件不满足) {
        condition.await();
    }
    // 条件满足执行操作
    condition.signal(); // 唤醒一个等待线程
    condition.signalAll(); // 唤醒所有等待线程
} finally {
    lock.unlock();
}

示例

多线程下载图片

public class DownloadImage {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://example.com/image.jpg");
        URLConnection connection = url.openConnection();
        int size = connection.getContentLength();
        if (size <= 0) {
            throw new Exception("文件大小为0或小于0");
        }
        int threadCount = 5;
        int[][] ranges = new int[threadCount][2];
        int blockSize = size / threadCount;
        for (int i = 0; i < threadCount; i++) {
            int startPos = i * blockSize;
            int endPos = (i + 1) * blockSize - 1;
            if (i == threadCount - 1) {
                endPos = size - 1;
            }
            ranges[i][0] = startPos;
            ranges[i][1] = endPos;
            System.out.printf("线程%d下载范围:%d-%d %n", i, startPos, endPos);
        }
        RandomAccessFile raf = new RandomAccessFile("image.jpg", "rw");
        raf.setLength(size);
        raf.close();
        ExecutorService service = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < threadCount; i++) {
            service.execute(new DownloadThread(url, ranges[i][0], ranges[i][1]));
        }
        service.shutdown();
    }
}

class DownloadThread implements Runnable {
    private URL url;
    private int startPos;
    private int endPos;

    public DownloadThread(URL url, int startPos, int endPos) {
        this.url = url;
        this.startPos = startPos;
        this.endPos = endPos;
    }

    @Override
    public void run() {
        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
            InputStream in = conn.getInputStream();
            RandomAccessFile raf = new RandomAccessFile("image.jpg", "rw");
            raf.seek(startPos);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
            in.close();
            raf.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

该示例中,使用多线程的方式下载大文件,将文件分为若干个范围,每个线程负责下载对应范围内的文件。同时,也要注意对同步代码块、文件写入等要素的安全管理。

2. 线程池

线程池的概念

线程池是一种特殊的线程组,包含若干个工作线程,它们可重复利用,执行一些预设好的任务,并且提供了管理、调度等机制。

线程池的好处

  • 创建、销毁线程的开销比较大,线程池可以重复利用线程,节省开销。
  • 线程数量受到限制,避免因过多的线程而导致CPU、内存的浪费或调度的问题。

Java线程池

Java提供了ThreadPoolExecutor类,可以通过该类创建线程池。

ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                keepAliveTime, unit, workQueue, threadFactory, rejectedExecutionHandler);

ThreadPoolExecutor的构造函数有7个参数,具体含义如下:

  • corePoolSize:核心线程池大小,当提交一个任务时,如果当前线程池大小小于corePoolSize,则新建线程执行任务。当提交一个任务时,如果当前线程池大小等于corePoolSize,且队列未满,则任务被存储在队列中等待执行。
  • maximumPoolSize:线程池最大的大小,在队列满了之后,当新的任务再次到来时,如果已创建的线程数小于maximumPoolSize,则会新创建线程执行该任务。
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,corePoolSize中的线程不会退出,除非设置allowCoreThreadTimeOut(允许核心线程超时退出)为true。
  • unit:keepAliveTime的时间单位。
  • workQueue:线程池中的任务队列,常用的有以下四种:
    • SynchronousQueue:同步队列,任务直接提交给线程池的线程执行,不存储任务,提交任务时,线程池中没有可用线程则会新建一个线程。
    • LinkedBlockingQueue:无界阻塞队列,适用于源源不断的异步任务。
    • ArrayBlockingQueue:有界阻塞队列,适用于限制线程数量不太大并发度适中的情况。
    • PriorityBlockingQueue:具有优先级的任务队列,优先级由Comparable规定。
  • threadFactory:线程工厂,用来创建线程类,默认的即可。
  • rejectedExecutionHandler:饱和策略,当任务队列满了且当前线程数等于maximumPoolSize时,处理该如何了。常用的饱和策略有以下几种:
    • ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务,并抛出RejectedExecutionException异常。
    • ThreadPoolExecutor.CallerRunsPolicy:回退策略,由提交任务的线程来直接执行改任务。
    • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面(最早提交的)任务,并重新提交当前任务。

线程池的状态

  • running:线程池处于运行状态,可以接受新任务并处理已加入队列的任务。
  • shutdown:线程池处于关闭状态,不再接受新任务,但会处理已加入队列的任务。
  • stop:线程池处于永久终止状态,不会处理已加入队列的任务。
  • tidying:任务队列为空,线程池中活跃线程数等于0,正在执行终止任务。
  • terminated:执行完终止任务后变为该状态。

示例

线程池下载图片

public class DownloadImageWithThreadPool {

    public static void main(String[] args) throws Exception {
        URL url = new URL("https://www.example.com/image.jpg");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        int size = connection.getContentLength();
        if (size <= 0) {
            throw new Exception("文件大小为0或小于0");
        }
        int threadCount = 5;
        int[][] ranges = new int[threadCount][2];
        int blockSize = size / threadCount;
        for (int i = 0; i < threadCount; i++) {
            int startPos = i * blockSize;
            int endPos = (i + 1) * blockSize - 1;
            if (i == threadCount - 1) {
                endPos = size - 1;
            }
            ranges[i][0] = startPos;
            ranges[i][1] = endPos;
            System.out.printf("线程%d下载范围:%d-%d %n", i, startPos, endPos);
        }
        RandomAccessFile raf = new RandomAccessFile("image2.jpg", "rw");
        raf.setLength(size);
        raf.close();
        ExecutorService service = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            service.execute(new DownloadTask(url, ranges[i][0], ranges[i][1], latch));
        }
        latch.await();
        service.shutdown();
    }
}

class DownloadTask implements Runnable {
    private URL url;
    private int startPos;
    private int endPos;
    private CountDownLatch latch;

    public DownloadTask(URL url, int startPos, int endPos, CountDownLatch latch) {
        this.url = url;
        this.startPos = startPos;
        this.endPos = endPos;
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
            InputStream in = conn.getInputStream();
            RandomAccessFile raf = new RandomAccessFile("image2.jpg", "rw");
            raf.seek(startPos);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
            in.close();
            raf.close();
            System.out.printf("Thread %d downloaded %d bytes from %d to %d %n", Thread.currentThread().getId(), endPos - startPos + 1, startPos, endPos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            latch.countDown();
        }
    }
}

该示例使用线程池的方式下载大文件,这样不仅可以避免由于线程创建销毁带来的开销,还可以方便管理已在队列中排队等待处理的任务。使用CountDownLatch,等待全部线程执行结束之后,关闭线程池。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java多线程与线程池技术分享 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • java虚拟机中多线程总结

    Java虚拟机中多线程总结 Java是一种支持多线程的编程语言,可以在同一个程序中同时运行多个线程。Java虚拟机(JVM)是Java程序的核心组件之一,多线程是JVM提供的一项非常重要的功能。在JVM中,多线程的实现方式主要有两种:基于进程的多线程和基于原生线程的多线程。 基于进程的多线程 基于进程的多线程是指在JVM内部使用单独的进程来实现多线程。这种多…

    多线程 2023年5月17日
    00
  • Java中多线程的ABA场景问题分析

    Java中多线程的ABA场景问题分析 ABA场景问题简介 多线程中,如果一个线程在读取一个共享变量时,另一个线程把它修改为另外一个值,再修改回原来的值,这时第一个线程可能会检查到期望的值,但是并没有发现这个值已经被修改过,这种情况就叫做ABA场景问题。 ABA场景问题如何解决 Java中提供了一个原子变量类AtomicStampedReference来解决A…

    多线程 2023年5月16日
    00
  • 深入SQLite多线程的使用总结详解

    下面为您详细讲解“深入SQLite多线程的使用总结详解”的完整攻略。 概述 在高并发场景下,为了提升数据访问效率,多线程访问数据库已经成为了必要的需求。而SQLite作为轻量级的嵌入式数据库,因其灵活的使用方式和可靠的性能表现,成为了许多应用的首选。本文将深入探讨SQLite多线程的使用方法和技巧,同时提供实战性的示例代码供读者参考。 SQLite多线程的使…

    多线程 2023年5月16日
    00
  • 在IntelliJ IDEA中多线程并发代码的调试方法详解

    当我们在编写多线程并发代码时,调试代码通常比调试单线程代码更为困难。但是,在使用 IntelliJ IDEA 这样的 IDE 中,我们可以利用 IDE 的一些工具来帮助我们更有效地调试多线程并发代码。本文将具体介绍在 IntelliJ IDEA 中如何调试多线程并发代码的步骤和方法。 调试多线程并发代码的步骤 针对我们要调试的类,打开 IntelliJ ID…

    多线程 2023年5月16日
    00
  • 浅谈Redis如何应对并发访问

    浅谈Redis如何应对并发访问 Redis是一种高性能的键值对存储数据库,并且由于其内存型的特性,使得它可以应对并发访问。本文将从以下几个方面详细讲解如何使用Redis应对并发访问。 数据库设计 在设计Redis数据库的时候,需要考虑以下几点来应对并发访问: 使用合适的数据结构:Redis支持多种数据结构,如字符串、哈希、列表、集合和有序集合等,我们需要根据…

    多线程 2023年5月16日
    00
  • java多线程编程之使用Synchronized块同步变量

    下面就是关于Java多线程编程中使用Synchronized块同步变量的完整攻略。 一、Synchronized块的作用 在Java多线程编程中,当多个线程同时访问某个对象的某个数据时,就会出现竞争状态,进而导致数据的不稳定性。Synchronized(同步)关键字可以用来给对象和方法上锁,以保证只有一个线程可以访问该对象或方法。 Synchronized只…

    多线程 2023年5月17日
    00
  • python支持多线程的爬虫实例

    下面是详细讲解“Python支持多线程的爬虫实例”的攻略: 准备工作 安装Python。可从官网(https://www.python.org/downloads/)下载适用于您的操作系统的Python版本。 安装必要的包:requests, beautifulsoup4, lxml,它们可通过pip命令进行安装。 bash pip install requ…

    多线程 2023年5月16日
    00
  • springboot tomcat最大线程数与最大连接数解析

    下面是“Spring Boot Tomcat最大线程数与最大连接数解析”的攻略。 一、Tomcat的最大连接数和最大线程数是什么? Tomcat是一个Web服务器,默认情况下,它的连接请求都是使用HTTP/1.1协议的。Tomcat的最大连接数指的是能同时建立的最大连接数,而Tomcat的最大线程数指的是Tomcat处理请求的最大线程数量。这两个参数可以决定…

    多线程 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部