Java多线程是Java编程中的一个核心概念,它允许程序同时执行多个任务,从而提高程序的执行效率和响应速度。下面我将从线程的创建、线程的状态管理、线程的协作、线程池的使用、同步机制的实现以及并发控制的方法等几个方面来详细介绍Java多线程。
一、线程的创建
在Java中,创建线程主要有三种方式:
继承Thread类:
通过自定义一个类继承Thread类,并重写其run方法来实现线程的创建。在run方法中编写线程需要执行的代码。然后创建该类的实例,并调用其start方法来启动线程。
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
实现Runnable接口:
通过实现Runnable接口,并重写其run方法来实现线程的创建。这种方式的好处是避免了Java单继承的局限性。然后创建Runnable接口的实现类实例,将其作为参数传递给Thread类的构造函数来创建线程对象,并调用start方法启动线程。
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
通过Callable和FutureTask创建线程:这种方式创建的线程可以返回值。首先需要创建Callable接口的实现类,并实现其call方法。然后创建Callable实现类的实例,并使用FutureTask类进行包装。最后,将FutureTask对象作为Thread对象的参数来启动新线程,并调用FutureTask对象的get方法获取子线程执行结束后的返回值。
二、线程的状态管理
线程在其生命周期中会经历不同的状态,包括新建(NEW)、可运行(RUNNABLE)、运行(RUNNING)、阻塞(BLOCKED)、等待(WAITING)、计时等待(TIMED_WAITING)和终止(TERMINATED)。
- 新建状态:新创建了一个线程对象,但还没有调用start方法。
- 可运行状态:线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权。
- 运行状态:可运行状态的线程获得了CPU时间片,执行程序代码。
- 阻塞状态:线程因为某种原因(如等待某个条件成立)而暂停执行,此时线程不会释放锁。
- 等待状态:线程进入等待状态,等待其他线程的通知(如调用Object类的wait方法)。
- 计时等待状态:线程在等待的同时设置了一个超时时间(如调用Thread类的sleep方法)。
- 终止状态:线程执行完毕或异常退出。
三、线程池的使用
线程池是一种用于管理线程的机制,它可以方便地管理线程,减少内存的消耗,并复用线程。Java中的线程池主要由java.util.concurrent包提供。
- 创建线程池:可以使用Executors类提供的静态方法来创建线程池,如newFixedThreadPool、newCachedThreadPool等。
- 提交任务:使用线程池的submit方法提交任务,任务可以是Runnable接口的实现类或Callable接口的实现类。
- 关闭线程池:使用线程池的shutdown方法关闭线程池,不再接受新任务,但会继续执行已提交的任务。shutdownNow方法则会尝试立即关闭线程池,并尝试停止正在执行的任务。
四、线程的协同
Java线程协同是指多个线程在执行过程中相互协作,共同完成某个任务。这通常涉及到线程之间的通信和同步。
1、基本概念
-
线程间的通信:
- 线程间通过共享内存、阻塞队列、信号量等方式进行通信。
- 线程可以调用某个对象的wait()方法,使自己进入等待状态,同时释放该对象上的锁。另一个线程可以调用相同对象的notify()或notifyAll()方法,唤醒一个或多个处于等待状态的线程。
-
线程间的同步
synchronized关键字:可以用于方法或代码块,实现线程之间的互斥访问。
volatile关键字:用于修饰变量,确保变量的可见性,但不能保证原子性。
Lock接口:提供了更灵活的锁机制,如可重入锁(ReentrantLock)、读写锁(ReadWriteLock)等。
2、并发控制的方法
在Java多线程编程中,并发控制是非常重要的。Java提供了多种并发控制的方法,如信号量(Semaphore)、屏障(CyclicBarrier)、交换器(Exchanger)和条件变量(Condition)等。
- 信号量:用于控制对共享资源的访问数量。
- 屏障:用于让一组线程互相等待,直到所有线程都到达一个共同屏障点时再继续执行。
- 交换器:用于在两个线程之间交换数据。
- 条件变量:与Lock接口配合使用,用于实现线程之间的等待/通知机制。
3、线程协同的实现
-
共享内存:
- 多个线程可以访问共享变量或数据结构,通过加锁和解锁来确保数据的一致性。
-
等待/通知机制:
- 使用Object类的wait()和notify()/notifyAll()方法实现线程间的协作。
- 示例代码:
public class WaitNotifyExample { private static final Object lock = new Object(); private static boolean flag = true; static class WaitThread implements Runnable { @Override public void run() { synchronized (lock) { while (flag) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("WaitThread is running"); } } } static class NotifyThread implements Runnable { @Override public void run() { synchronized (lock) { flag = false; lock.notify(); } } } public static void main(String[] args) { Thread waitThread = new Thread(new WaitThread()); Thread notifyThread = new Thread(new NotifyThread()); waitThread.start(); notifyThread.start(); } }
-
阻塞队列:
- 使用BlockingQueue接口实现线程安全的队列,支持在队尾插入元素和从队首移除元素。
- 常用于生产者-消费者模式,实现线程间的协作。
-
信号量(Semaphore):
- 用于控制对共享资源的访问,通过acquire()和release()方法实现线程间的同步和协作。
-
倒计时门闩(CountDownLatch):
- 允许一个或多个线程等待其他线程完成操作,当计数器的值为0时,所有等待的线程将被释放。
- 示例代码:
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(2); new Thread(() -> { try { System.out.println("Thread A is running"); Thread.sleep(1000); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { System.out.println("Thread B is running"); Thread.sleep(1000); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); latch.await(); System.out.println("All tasks are done"); } }
-
循环栅栏(CyclicBarrier):
- 允许一组线程互相等待,直到所有线程都到达某个屏障点。
- 可以重复使用,支持线程间的协作。