Java必学必会之线程(1)
一、线程的基本概念
1.1 线程定义
线程是进程中的执行单元,是轻量级的进程,一个进程可以有多个线程。线程拥有自己的执行栈和局部变量,但同时也可以访问共享变量。
1.2 线程状态
线程在其生命周期中可以处于以下几种状态:
- NEW:新创建的线程,尚未开始执行。
- RUNNABLE:正在 Java 虚拟机中执行的线程。
- BLOCKED:等待获取一个排它锁以便进入同步块/方法的线程。
- WAITING:等待另一个线程通知调度器一个条件的线程。
- TIMED_WAITING:有限时间等待另一个线程通知调度器一个条件的线程。
- TERMINATED:已经执行完毕的线程。
1.3 创建线程的三种方式
Java 中创建线程的方法有以下三种:
1.3.1 实现 Runnable 接口
创建一个实现 Runnable 接口的类,重写 run() 方法,并将该类的实例作为 Thread 构造函数的参数传入。
public class MyRunnable implements Runnable {
public void run() {
// 执行代码
}
}
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
1.3.2 继承 Thread 类
创建一个继承 Thread 类的子类,重写 run() 方法,并通过该子类的实例启动线程。
public class MyThread extends Thread {
public void run() {
// 执行代码
}
}
MyThread myThread = new MyThread();
myThread.start();
1.3.3 使用匿名内部类
通过创建匿名内部类,重写 run() 方法并启动线程。
Thread thread = new Thread(new Runnable() {
public void run() {
// 执行代码
}
});
thread.start();
二、线程的基本操作
2.1 线程的启动
通过调用 start() 方法启动线程。不能通过调用 run() 方法启动线程,这样不会创建新的线程,而是在当前线程中直接执行 run() 方法。
class MyThread extends Thread {
@Override
public void run() {
// 执行代码
}
}
MyThread myThread = new MyThread();
myThread.start();
2.2 线程的休眠
通过调用 Thread.sleep(millis) 方法,让当前线程暂停执行指定的时间,单位是毫秒。该方法可能会抛出 InterruptedException 异常。
try {
Thread.sleep(1000); // 线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
2.3 线程的中断
可以通过调用 Thread.interrupt() 方法向目标线程发送中断信号,但目标线程并不一定会立即响应,可能仍会继续执行。
class MyThread extends Thread {
@Override
public void run() {
while (!Thread.interrupted()) {
// 执行代码
}
}
}
MyThread myThread = new MyThread();
myThread.start();
myThread.interrupt(); // 发送中断信号
2.4 线程的等待和通知
Java 中通过 Object 类的 wait()、notify() 和 notifyAll() 方法实现线程等待和通知。
2.4.1 wait() 方法
wait() 方法会使当前线程进入等待状态,并且会释放当前线程持有的对象锁,直到其他线程调用该对象的 notify() 方法或 notifyAll() 方法来唤醒该线程。调用 wait() 方法的前提是必须先获得对象锁,否则会抛出 IllegalMonitorStateException 异常。
synchronized (object) {
while (condition) {
try {
object.wait(); // 线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.4.2 notify() 方法
notify() 方法会随机唤醒一个正在等待该对象锁的线程,使其继续执行。并且当前线程并不会立即释放锁,而是在退出 synchronized 代码块后才释放。
synchronized (object) {
condition = true;
object.notify(); // 唤醒一个正在等待该对象锁的线程
}
2.4.3 notifyAll() 方法
notifyAll() 方法会唤醒正在等待该对象锁的所有线程,使其继续执行。并且当前线程并不会立即释放锁,而是在退出 synchronized 代码块后才释放。
synchronized (object) {
condition = true;
object.notifyAll(); // 唤醒所有正在等待该对象锁的线程
}
三、示例
3.1 生产者-消费者模型
生产者-消费者模型是一种常见的并发模型,通过一个或多个线程生产数据,另一个或多个线程消费数据,通过队列或者缓存区来解耦生产者和消费者,从而实现并发处理数据。
下面的示例展示了简单的生产者-消费者模型,一个生产者线程不断生产数据,保存到缓存区,多个消费者线程从缓存区读取数据进行消费。其中,Cache 类是缓存区类,包括 put() 和 take() 两个方法,分别是生产和消费数据的方法。
import java.util.LinkedList;
class Cache<T> {
private final LinkedList<T> queue = new LinkedList<>();
private final int limit;
public Cache(int limit) {
this.limit = limit;
}
public synchronized void put(T t) throws InterruptedException {
while (queue.size() == limit) {
wait();
}
queue.add(t);
notifyAll();
}
public synchronized T take() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
T t = queue.removeFirst();
notifyAll();
return t;
}
}
class Producer extends Thread {
private final Cache<Integer> cache;
private final int value;
public Producer(Cache<Integer> cache, int value) {
this.cache = cache;
this.value = value;
}
@Override
public void run() {
while (true) {
try {
cache.put(value);
System.out.println("Producer: " + value);
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer extends Thread {
private final Cache<Integer> cache;
public Consumer(Cache<Integer> cache) {
this.cache = cache;
}
@Override
public void run() {
while (true) {
try {
int value = cache.take();
System.out.println("Consumer: " + value);
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Cache<Integer> cache = new Cache<>(10);
Producer producer = new Producer(cache, 1);
Consumer consumer1 = new Consumer(cache);
Consumer consumer2 = new Consumer(cache);
producer.start();
consumer1.start();
consumer2.start();
}
}
3.2 多线程修改同一对象
多个线程修改同一个对象的属性或者成员变量时,可能会出现线程安全问题,如数据不一致、数据污染等问题。下面的示例模拟了一个共享变量 num 被多个线程读写的情况,其中线程 A 不断将 num 加一,线程 B 不断将 num 减一,最终结果应该是 num 的值不变。但是由于线程不安全,最终 num 的值可能不是预期的值。
class MyThread extends Thread {
private static int num = 0;
private final char operator;
public MyThread(char operator) {
this.operator = operator;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (operator == '+') {
num++;
} else if (operator == '-') {
num--;
}
}
System.out.println("operator: " + operator + ", num: " + num);
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new MyThread('+');
Thread thread2 = new MyThread('-');
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("final num: " + MyThread.num);
}
}
为了解决这个问题,我们可以使用 synchronized 关键字来保证任意时刻只有一个线程执行修改操作。
class MyThread extends Thread {
private static int num = 0;
private final char operator;
public MyThread(char operator) {
this.operator = operator;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (MyThread.class) {
if (operator == '+') {
num++;
} else if (operator == '-') {
num--;
}
}
}
System.out.println("operator: " + operator + ", num: " + num);
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new MyThread('+');
Thread thread2 = new MyThread('-');
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("final num: " + MyThread.num);
}
}
以上就是线程的基本概念、操作和两个示例的详细解释。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java必学必会之线程(1) - Python技术站