Java多线程编程高级应用

43 阅读2分钟

Java多线程编程高级应用

Java多线程编程是Java中的核心技术之一,它可以帮助我们在程序中同时执行多个任务,提高程序的执行效率。然而,理解和正确使用Java的多线程编程并不是一件简单的事情,需要对其底层的工作原理有深入的了解。

下面,我们将介绍Java多线程编程的高级应用,包括线程池、同步、锁定、并发集合、线程的调度策略等内容,并且附上相关的代码示例。

1. 线程池

线程池是一种基于池化思想管理线程的工具,可以控制系统中运行的线程数量,根据系统的性能动态地调整线程数量。

1.1 创建线程池

Java的java.util.concurrent.Executors类提供了创建不同类型线程池的工厂方法。以下是创建一个固定大小的线程池的例子:

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

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("" + i);
            executorService.execute(worker);
        }

        executorService.shutdown();
        while (!executorService.isTerminated()) {
        }

        System.out.println("Finished all threads");
    }
}

class WorkerThread implements Runnable {
    private String command;

    public WorkerThread(String s) {
        this.command = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Start. Command = " + command);
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End.");
    }

    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们创建了一个固定大小(5个线程)的线程池,并向里面提交了10个任务。每个任务就是一个实现了Runnable接口的WorkerThread对象。任务一旦提交,就会由线程池中的一个空闲线程来执行。如果线程池中没有空闲的线程,新提交的任务就会被放在等待队列中,等待有线程空闲时再执行。

1.2 线程池的关闭

当我们不再需要线程池时,应该关闭它,以释放资源。可以通过调用ExecutorServiceshutdown()方法来关闭线程池。这个方法会等待所有已提交的任务都完成后,再关闭线程池。

2. 同步

Java的synchronized关键字可以用来确保多个线程在访问共享资源时,不会同时进行,从而避免数据不一致的问题。

下面的例子展示了如何使用synchronized关键字来保证数据的一致性:

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized void decrement() {
        count--;
    }

    public synchronized int value() {
        return count;
    }
}

在这段代码中,我们定义了一个Counter类,它有一个int类型的成员变量count,以及三个方法:increment()、decrement()和value()。这三个方法都被声明为synchronized,这意味着在同一时间,只有一个线程能够执行这些方法。

3. 锁定

Java的java.util.concurrent.locks包提供了一些更灵活的锁定机制,比如ReentrantLock

下面的例子展示了如何使用ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    // Locking using Lock and ReentrantLock
    publicint getCount() {
        lock.lock();
        try {
            System.out.println("Lock acquired by " + Thread.currentThread().getName());
            return count++;
        } finally {
            lock.unlock();
            System.out.println("Lock released by " + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        final ReentrantLockExample counter = new ReentrantLockExample();
        Runnable r = () -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + " gets count: " + counter.getCount());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(r, "Thread 1").start();
        new Thread(r, "Thread 2").start();
    }
}

在这段代码中,我们定义了一个ReentrantLockExample类,它有一个ReentrantLock类型的成员变量lock和一个int类型的成员变量count。getCount()方法首先获得锁,然后打印一条消息,增加count的值,再解锁并打印一条消息。

4. 并发集合

Java的java.util.concurrent包中还提供了许多并发集合,比如ConcurrentHashMapCopyOnWriteArrayList等。这些集合在支持高并发的同时,还能保证数据的一致性。

下面的例子展示了如何使用ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");

        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.remove("key1");
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.remove("key2");
        }).start();

        for (Object key : map.keySet()) {
            System.out.println(key + " : " + map.get(key));
        }
    }
}

在这段代码中,我们首先创建了一个ConcurrentHashMap对象,并向其中添加了三个元素。然后我们启动了两个线程,每个线程都在1秒后删除一个元素。最后,我们遍历并打印出map中的所有元素。

5. 线程的调度策略

Java的线程调度策略基于优先级。可以通过Thread类的setPriority()方法来设置线程的优先级。优先级高的线程有更大的几率被操作系统选中执行。

下面的例子展示了如何设置线程的优先级:

public class PriorityExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println("Thread 1");
            }
        });
        t1.setPriority(1);

        Thread t2 = new Thread(() -> {
            while (true) {
                System.out.println("Thread 2");
            }
        });
        t2.setPriority(10);

        t1.start();
        t2.start();
    }
}

在这段代码中,我们创建了两个线程,一个的优先级设置为1(最低),另一个的优先级设置为10(最高)。然后同时启动这两个线程。由于t2的优先级更高,因此它有更大的几率被操作系统选中执行。

在Java多线程编程中,理解线程的生命周期、线程同步、线程通信以及线程的调度策略等是非常重要的。通过这些高级应用,我们可以更好地控制程序的并发行为,提高程序运行的效率,同时也可以避免程序的错误和异常。