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多线程(3)

    当我们需要处理一些比较消耗时间的操作时,多线程可以提高程序的执行效率,因此实现多线程在Java编程中也显得尤为重要。本文将带你从多方面快速搞定Java多线程,实现多任务并发执行。 1. 创建线程的三种方式 在Java中,创建线程的方式有三种:继承Thread类、实现Runnable接口以及使用线程池。 1.1 继承Thread类 继承Thread类是最简单的…

    多线程 2023年5月17日
    00
  • Go语言中并发的工作原理

    Go语言是一门支持并发的编程语言,通过goroutine和channel两种核心机制实现并发编程。下面分以下步骤详细讲解Go语言中并发的工作原理。 1. goroutine goroutine是Go语言实现并发的基本单位。它类似于线程,但相较于线程开销更小,能够更好地利用多核心CPU的优势。与线程不同的是,Goroutine通过Go语言的运行时系统进行调度,…

    多线程 2023年5月16日
    00
  • Entity Framework管理并发

    对于Entity Framework的并发管理,可以通过以下步骤进行设置和处理。 1. 设计数据库表 在设计数据库表时,需要考虑到并发操作可能会导致数据冲突的情况。可以为需要进行并发管理的数据表添加一个行版本号列。每次更新数据时自动将该列的值增加1。 以下为创建一个包含行版本号的记录的示例: CREATE TABLE Employee ( Id INT PR…

    多线程 2023年5月16日
    00
  • PHP+Redis事务解决高并发下商品超卖问题(推荐)

    PHP+Redis事务解决高并发下商品超卖问题(推荐) 问题背景 在高并发下,如果不做任何处理,会出现商品超卖的问题。例如,用户同时购买同一个商品,但是只有一件商品的库存,如果没有控制,就会导致超卖现象。 解决方案 为了解决这个问题,我们可以利用Redis事务来实现。Redis事务提供了原子性,即事务中的操作要么全部成功,要么全部失败。因此,我们可以通过Re…

    多线程 2023年5月17日
    00
  • Java synchornized与ReentrantLock处理并发出现的错误

    Java中的多线程编程牵涉到了并发访问,同时访问共享资源可能会造成数据竞争导致程序出现异常。为了解决这个问题,Java提供了两个主要的同步控制手段,即synchronized和ReentrantLock。然而,在使用这两种手段进行并发控制时也可能出现错误,下面就具体说明其出现的原因及如何解决。 Java synchronized的错误处理 问题引出 在Jav…

    多线程 2023年5月16日
    00
  • 深入理解JS中的Promise.race控制并发量

    标题:深入理解JS中的Promise.race控制并发量 简介 JavaScript中的Promise是一种处理异步操作的方式,而Promise.race方法则是Promise对象上的一种方法,它与其他方法不同的是,只要其中的一个Promise状态被改变,Promise.race的状态就会被改变。这个方法通常用来控制异步操作的并发数,即同时进行的异步操作数量…

    多线程 2023年5月16日
    00
  • golang 限制同一时间的并发量操作

    下面是详细讲解“golang 限制同一时间的并发量操作”的完整攻略: 前置知识 在了解如何限制同一时间的并发量操作之前,我们需要先了解一些并发编程基础知识,包括 goroutine、channel、sync.WaitGroup 和 sync.Mutex。 goroutine:Go 语言的轻量级线程,可以在多个 goroutine 之间并发执行。 channe…

    多线程 2023年5月16日
    00
  • 详细分析Java并发集合ArrayBlockingQueue的用法

    下面是详细的攻略: Java并发集合ArrayBlockingQueue的用法分析 1. 简介 ArrayBlockingQueue是Java中的一个并发集合,是线程安全的,可以在生产者和消费者之间传递数据。它是一个有界队列,具有固定的大小,即在构造时指定队列的容量。 2. 常用方法 ArrayBlockingQueue有许多常用的方法,下面是其中的一些: …

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