目录
一. 线程 进程简介
进程(Process)
在操作系统中,进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它是资源分配的基本单位。
进程具有以下特点:
- 独立性:每个进程都有自己独立的地址空间,包括代码、数据和运行环境。
- 资源拥有:进程拥有自己的系统资源,如内存、文件描述符等。
例如,打开一个浏览器是一个进程,运行一个文本编辑器也是一个进程。
线程(Thread)
线程是进程中的一个执行单元,是进程内的一条执行路径。
线程的特点包括:
- 共享资源:多个线程在同一个进程中运行,它们共享该进程的资源,如内存、文件等。
- 轻量级:创建和切换线程的开销比创建和切换进程小得多。
在 Java 中,通过多线程编程可以实现并发执行。
例如,一个 Web 服务器可以为每个客户端连接创建一个线程来处理请求,从而能够同时服务多个客户端。
线程和进程的主要区别:
- 资源分配:进程拥有独立的资源,而线程共享所属进程的资源。
- 上下文切换开销:进程切换的开销较大,线程切换的开销较小。
- 通信方式:进程间通信较为复杂,通常需要通过进程间通信机制,如管道、消息队列等;线程间通信相对简单,可以直接共享变量。
假设我们有一个复杂的计算任务,将其拆分为多个子任务并通过多个线程来并行处理,这比创建多个进程来处理要高效和便捷。但如果不同的任务需要完全独立的资源和环境,可能更适合创建多个进程。
总的来说,进程和线程在操作系统中都有各自的适用场景,理解它们的特点和区别对于编写高效、可靠的程序非常重要。
二. 多线程简介
在 Java 中,多线程是指在一个程序中同时运行多个执行线程的能力。每个线程都可以独立地执行一段代码,并且它们可以并发地执行,从而提高程序的性能和响应性。
多线程的主要优势包括:
- 提高资源利用率:可以同时执行多个任务,充分利用 CPU 的多核处理能力,避免 CPU 资源的闲置。
- 增强程序的响应性:例如,在一个图形用户界面程序中,可以使用一个线程处理用户交互,另一个线程在后台执行耗时的计算任务,使界面不会出现卡顿。
- 提高程序的效率:对于一些可以并行处理的任务,如数据处理、文件读写等,多线程可以显著提高执行速度。
Java 中实现多线程主要有两种方式:
- 继承
Thread
类:
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
System.out.println("Hello from thread!");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
- 实现
Runnable
接口:
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码
System.out.println("Hello from runnable!");
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
多线程编程需要注意线程安全问题,例如多个线程同时访问和修改共享数据时可能导致的数据不一致性。可以使用同步机制,如 synchronized
关键字、锁等来解决这些问题。
例如,在一个银行账户的操作中,如果多个线程同时对账户余额进行修改,就可能出现错误。通过同步相关的操作代码,可以保证数据的正确性。
三. 线程状态
在 Java 中,线程具有以下几种状态:
- 新建(New):当创建一个新的线程对象,但还未调用
start()
方法启动线程时,线程处于新建状态。
Thread thread = new Thread(() -> {});
此时线程尚未开始执行。
-
就绪(Runnable):线程已经调用了
start()
方法,正在等待被操作系统调度执行,处于就绪状态。一旦获取到 CPU 时间片,就可以开始执行。 -
运行(Running):线程获得 CPU 资源正在执行其
run()
方法中的代码。 -
阻塞(Blocked):线程因为某些原因被阻塞,暂时停止执行。例如,等待获取锁、等待 I/O 操作完成等。
synchronized (lock) {
// 等待获取锁时可能进入阻塞状态
}
-
等待(Waiting):线程调用了
Object.wait()
等方法,主动进入等待状态,需要其他线程调用Object.notify()
或Object.notifyAll()
来唤醒。 -
超时等待(Timed_Waiting):线程调用了带有超时时间的方法,如
Thread.sleep(long)
、Object.wait(long)
等,在指定时间内未被唤醒则自动恢复。 -
终止(Terminated):线程执行完毕或者因异常而终止。
例如,一个线程在等待另一个线程完成某些操作时可能进入阻塞状态,当条件满足后被唤醒继续执行。
总之,线程在其生命周期中会在不同的状态之间切换,了解线程状态对于理解多线程编程中的并发行为和调试问题非常重要。
四. 线程管理
在 Java 中,线程管理是多线程编程的重要部分,包括线程的创建、启动、暂停、恢复和终止等操作。
线程的创建和启动:
可以通过继承 Thread
类或实现 Runnable
接口来创建线程。然后调用 start()
方法启动线程。
// 继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
}
}
// 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码
}
}
public class ThreadManagementExample {
public static void main(String[] args) {
// 创建并启动继承 Thread 类的线程
MyThread thread1 = new MyThread();
thread1.start();
// 创建并启动实现 Runnable 接口的线程
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
}
}
线程的暂停和恢复:
在 Java 中,不建议直接使用暂停和恢复线程的方法,因为这可能导致死锁或其他不可预测的问题。但可以通过一些条件控制来实现类似的效果,例如使用 volatile
变量或线程间的通信机制。
线程的终止:
线程可以自然执行完毕而终止,也可以通过设置标志位等方式让线程主动退出。
class TerminableThread extends Thread {
private volatile boolean shouldTerminate = false;
@Override
public void run() {
while (!shouldTerminate) {
// 线程执行的代码
}
}
public void terminate() {
shouldTerminate = true;
}
}
public class TerminationExample {
public static void main(String[] args) {
TerminableThread thread = new TerminableThread();
thread.start();
// 一段时间后终止线程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.terminate();
}
}
线程优先级:
可以设置线程的优先级,不过这并不能保证线程的执行顺序,只是给操作系统一个调度的提示。
Thread thread = new Thread(() -> {});
thread.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
thread.start();
线程同步:
当多个线程访问共享资源时,需要使用同步机制来保证线程安全,如 synchronized
关键字、锁等。
class SharedResource {
private int value;
public synchronized void increment() {
value++;
}
}
总之,合理的线程管理可以提高程序的性能和稳定性,避免出现线程安全问题和死锁等情况。
五. 线程安全
在多线程环境中,当多个线程同时访问和修改共享数据时,如果不采取适当的措施,可能会导致数据不一致、程序错误或不可预测的结果。线程安全就是指在多线程环境下,程序的运行结果是正确和可预测的。
导致线程不安全的情况:
竞争条件:多个线程同时访问和修改共享资源,导致结果取决于线程执行的顺序。
- 数据不一致:不同线程对共享数据的修改相互干扰,使得数据处于不一致的状态。
实现线程安全的方法:
synchronized
关键字:可以用于方法或代码块,保证同一时刻只有一个线程能够访问被修饰的部分。
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
Lock
接口:提供了更灵活的锁控制。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class CounterWithLock {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
- 线程局部变量(
ThreadLocal
):每个线程都有自己独立的变量副本,不会相互干扰。
public class ThreadLocalExample {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
threadLocal.set(10);
System.out.println("Thread 1: " + threadLocal.get());
}).start();
new Thread(() -> {
threadLocal.set(20);
System.out.println("Thread 2: " + threadLocal.get());
}).start();
}
}
- 不可变对象:如果对象的状态在创建后不能被修改,那么它在多线程环境中是天然安全的。
例如,在一个在线购物系统中,如果多个线程同时处理订单并发修改库存数量,就需要确保库存数据的线程安全,以避免出现库存超卖或错误的库存记录。
六. 线程间通信
在 Java 中,线程间通信是指多个线程之间协调和交换信息的过程。
使用共享对象和 synchronized
关键字:
多个线程可以访问同一个共享对象,通过在对共享对象的操作方法上使用 synchronized
关键字来保证线程安全和实现通信。
class SharedObject {
private boolean flag = false;
public synchronized void setFlag(boolean flag) {
this.flag = flag;
notifyAll(); // 唤醒等待的线程
}
public synchronized boolean getFlag() {
while (!flag) {
try {
wait(); // 线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return flag;
}
}
class Thread1 extends Thread {
private SharedObject sharedObject;
public Thread1(SharedObject sharedObject) {
this.sharedObject = sharedObject;
}
@Override
public void run() {
sharedObject.setFlag(true);
}
}
class Thread2 extends Thread {
private SharedObject sharedObject;
public Thread2(SharedObject sharedObject) {
this.sharedObject = sharedObject;
}
@Override
public void run() {
boolean flag = sharedObject.getFlag();
System.out.println("Flag: " + flag);
}
}
public class ThreadCommunicationExample {
public static void main(String[] args) {
SharedObject sharedObject = new SharedObject();
Thread1 thread1 = new Thread1(sharedObject);
Thread2 thread2 = new Thread2(sharedObject);
thread1.start();
thread2.start();
}
}
使用 volatile
关键字:volatile
关键字可以保证线程对变量的可见性,但不能保证原子性操作。
volatile boolean flag = false;
使用 Thread.join()
方法:
一个线程可以等待另一个线程完成。
Thread thread = new Thread(() -> {});
thread.start();
thread.join(); // 等待线程完成
使用线程阻塞队列:
如 BlockingQueue
,可以实现线程间的数据传递和同步。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
class Producer implements Runnable {
private BlockingQueue<String> queue;
public Producer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
queue.put("Item");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
String item = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
例如,在一个生产者-消费者模型中,生产者线程生产数据并放入共享队列,消费者线程从队列中取出数据进行处理,通过这些线程间通信机制实现了高效的协作。