当我们需要处理一些比较消耗时间的操作时,多线程可以提高程序的执行效率,因此实现多线程在Java编程中也显得尤为重要。本文将带你从多方面快速搞定Java多线程,实现多任务并发执行。
1. 创建线程的三种方式
在Java中,创建线程的方式有三种:继承Thread类、实现Runnable接口以及使用线程池。
1.1 继承Thread类
继承Thread类是最简单的创建一个线程的方式,只需要重载Thread的run()方法即可。
class MyThread extends Thread {
public void run() {
// 线程所需要执行的代码
}
}
1.2 实现Runnable接口
实现Runnable接口也是很常用的一种创建线程的方式。这种方式可以让一个类实现多个接口,同时也可以继承其他类。创建线程的时候,将实现Runnable接口的类传入Thread类的构造函数中即可。
class MyRunnable implements Runnable {
public void run() {
// 线程所需要执行的代码
}
}
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
1.3 使用线程池
使用线程池是一种比较高级的创建线程的方式,它能够控制线程的数量,以及复用那些已经执行完毕的线程,从而提高程序的效率。Java中使用线程池需要借助java.util.concurrent
包中的Executor和ExecutorService接口。
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
MyRunnable r = new MyRunnable(); // MyRunnable是实现了Runnable接口的类
pool.execute(r); // 将任务提交给线程池中的线程
}
2. 线程同步
当多个线程同时访问一个共享资源时,就可能会出现数据不一致的问题。为了避免这样的情况发生,就需要使用线程同步技术。Java中提供了synchronized
关键字来保证同步。
2.1 synchronized关键字
synchronized
关键字可以修饰一个方法或者一个代码块,只有拥有锁的线程才能够执行该方法或者代码块。其他线程只能在锁被释放后再去执行。
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
}
2.2 Lock接口
除了synchronized
关键字,Java中还提供了Lock接口来实现线程同步。Lock接口提供了更细粒度的锁控制。
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
try {
lock.lock(); // 获取锁
count++;
} finally {
lock.unlock(); // 释放锁
}
}
public void decrement() {
try {
lock.lock(); // 获取锁
count--;
} finally {
lock.unlock(); // 释放锁
}
}
}
示例说明
示例1
有一个系统需要对一组数进行排序,并将排序结果输出到控制台。这个过程比较耗时,需要使用多线程来提高效率。以下是实现该功能的代码:
class SortThread extends Thread {
private int[] arr;
public SortThread(int[] arr) {
this.arr = arr;
}
public void run() {
Arrays.sort(arr);
}
public int[] getArr() {
return arr;
}
}
public class SortTest {
public static void main(String[] args) throws InterruptedException {
int[] arr = {1, 5, 3, 7, 6, 2, 4, 9, 8, 10};
SortThread t1 = new SortThread(Arrays.copyOfRange(arr, 0, 5));
SortThread t2 = new SortThread(Arrays.copyOfRange(arr, 5, 10));
t1.start();
t2.start();
t1.join();
t2.join();
arr = mergeArray(t1.getArr(), t2.getArr());
System.out.println(Arrays.toString(arr));
}
private static int[] mergeArray(int[] arr1, int[] arr2) {
int[] arr = new int[arr1.length + arr2.length];
int i = 0, j = 0, k = 0;
while (i < arr1.length && j < arr2.length) {
if (arr1[i] < arr2[j]) {
arr[k++] = arr1[i++];
} else {
arr[k++] = arr2[j++];
}
}
while (i < arr1.length) {
arr[k++] = arr1[i++];
}
while (j < arr2.length) {
arr[k++] = arr2[j++];
}
return arr;
}
}
在上述代码中,我们创建了两个SortThread线程对象,分别对数组arr的前五个元素和后五个元素进行排序。这两个线程是同时启动的,所以整个过程可以得到充分的利用。当两个线程都执行完毕后,我们再将它们排序的结果进行合并。
示例2
一个生产者消费者模型,大致流程是:生产者不断生产产品,放入缓冲区;消费者不断从缓冲区取出产品进行消费。一旦缓冲区满了,生产者会暂停生产,等待消费者消费后再继续生产;一旦缓冲区为空,消费者会暂停消费,等待生产者生产产品后再继续消费。以下是实现该模型的代码:
class Clerk {
private int productCount = 0;
private final int MAX_PRODUCT = 20;
public synchronized void addProduct() {
if (productCount >= MAX_PRODUCT) {
try {
wait(); // 缓冲区已满,线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
productCount++;
System.out.println(Thread.currentThread().getName() + " 生产了一件产品,总共有 " + productCount + " 件产品");
notifyAll(); // 唤醒所有等待的线程
}
public synchronized void removeProduct() {
if (productCount <= 0) {
try {
wait(); // 缓冲区为空,线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
productCount--;
System.out.println(Thread.currentThread().getName() + " 消费了一件产品,总共有 " + productCount + " 件产品");
notifyAll(); // 唤醒所有等待的线程
}
}
class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000); // 模拟生产过程耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000); // 模拟消费过程耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.removeProduct();
}
}
}
public class ProducerConsumerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(producer, "生产者1").start();
new Thread(consumer, "消费者1").start();
new Thread(producer, "生产者2").start();
new Thread(consumer, "消费者2").start();
}
}
在上述代码中,我们创建了一个Clerk类来模拟产品的生产和消费。这个类中包含了一个产品计数器和两个同步方法addProduct()和removeProduct(),分别用来添加和移除产品。在每个方法中,都用synchronized
关键字修饰了方法,用来保证线程同步。
创建了两个线程生产者和消费者,分别在run()方法中对应调用Clerk类中的addProduct()和removeProduct()方法来生产和消费产品。这个过程中,使用了wait()和notifyAll()方法来控制线程的等待和唤醒。当缓冲区满或为空的时候,线程会进入等待状态并且释放锁,等待notifyAll()方法的唤醒。当notifyAll()方法被调用的时候,它会唤醒所有等待的线程,但是只有一个线程可以获得锁,继续执行下去。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:带你快速搞定java多线程(3) - Python技术站