JAVA-线程和并发

1. 线程的基本概念

**线程(Thread)**是操作系统能够进行运算调度的最小单位,它是程序执行的基本单元。Java 是一种支持多线程的语言,允许多个线程同时运行,从而提高应用程序的响应能力和性能。

2. 创建线程的方式

Java 提供两种主要方式来创建线程:

2.1 继承 Thread

步骤

  1. 创建一个类继承 Thread
  2. 重写 run() 方法。
  3. 创建线程对象并调用 start() 方法。

示例

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - Count: " + i);
            try {
                Thread.sleep(500); // 模拟工作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start(); // 启动线程
        thread2.start(); // 启动另一个线程
    }
}

解析:在这个例子中,两个线程同时运行,输出各自的计数。

2.2 实现 Runnable 接口

步骤

  1. 创建一个类实现 Runnable 接口。
  2. 重写 run() 方法。
  3. 创建 Thread 对象并传入 Runnable 实现。

示例

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - Count: " + i);
            try {
                Thread.sleep(500); // 模拟工作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());
        thread1.start(); // 启动线程
        thread2.start(); // 启动另一个线程
    }
}

解析:使用 Runnable 接口可以更灵活地使用线程,支持多个线程共享同一个 Runnable 实例。

3. 线程的生命周期

Java 线程的生命周期主要包括以下几个状态:

状态描述
新建线程被创建,但尚未启动。
就绪线程准备好执行,但可能因资源争用而被暂停。
运行线程正在执行任务。
阻塞线程等待某个资源(如 I/O 操作)。
死亡线程执行完毕或异常退出。

状态转换示意

  • 从新建到就绪:调用 start() 方法。
  • 从就绪到运行:操作系统调度线程运行。
  • 从运行到阻塞:调用 wait()sleep()join() 等方法。
  • 从运行到死亡:run() 方法执行完毕或抛出异常。

4. 线程的同步

多线程编程中,多个线程可能会同时访问共享资源,这时需要进行同步,以避免数据不一致或冲突。

4.1 使用 synchronized 关键字

方法同步

class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

解析increment() 方法被 synchronized 修饰,确保同一时间只有一个线程可以访问该方法,避免数据竞争。

对象锁

class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

解析:在这种方式下,使用 synchronized 块来控制访问范围,可以提高效率。

4.2 使用 Lock 接口

使用 java.util.concurrent.locks 包中的 Lock 接口提供更灵活的锁机制。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class LockCounter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++;
        } finally {
            lock.unlock(); // 确保释放锁
        }
    }

    public int getCount() {
        return count;
    }
}

解析:使用 ReentrantLock 提供显式锁,确保线程安全,并且在 finally 块中释放锁,避免死锁。

5. Java 并发包 java.util.concurrent

Java 提供的 java.util.concurrent 包包含许多用于构建并发程序的高级工具和类。

5.1 常用的并发工具
  • Executor 框架:用于管理线程池,简化线程管理。

示例

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

public class ExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            executor.submit(() -> {
                System.out.println("Task is running in " + Thread.currentThread().getName());
            });
        }
        executor.shutdown(); // 关闭线程池
    }
}

解析:使用 ExecutorService 创建一个固定大小的线程池,提交任务后,线程池会管理线程的创建和销毁。

  • Future:用于表示异步计算的结果。

示例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(() -> {
            Thread.sleep(1000);
            return 123; // 返回结果
        });

        System.out.println("Result: " + future.get()); // 阻塞,直到结果准备好
        executor.shutdown();
    }
}

解析:通过 Future 可以获取异步任务的执行结果,get() 方法会阻塞直到结果可用。

  • CountDownLatch:用于实现线程间的等待机制。

示例

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        
        Runnable task = () -> {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " has completed.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown(); // 计数减一
            }
        };

        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }

        latch.await(); // 等待计数器为0
        System.out.println("All tasks completed.");
    }
}

 解析CountDownLatch 允许主线程等待所有子线程完成后再继续执行。

5.2 线程安全的集合类

Java 提供了一些线程安全的集合类,常见的有:

  • ConcurrentHashMap:一个高效的线程安全哈希表。

示例

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
        map.put(1, "One");
        System.out.println(map.get(1));

        map.putIfAbsent(2, "Two"); // 只有在键2不存在时才插入
        System.out.println(map.get(2));
    }
}

 解析ConcurrentHashMap 在多线程环境中安全地处理并发读写操作。

  • CopyOnWriteArrayList:适合读多写少的场景。

示例

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteExample {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        System.out.println(list);
        
        list.remove("A"); // 安全删除
        System.out.println(list);
    }
}

 解析:在读操作频繁且写操作少的情况下,CopyOnWriteArrayList 可以有效避免并发问题。

6. 线程的常用方法

  • start():启动线程,调用 run() 方法。
  • run():线程执行的方法,不能直接调用。
  • join():等待线程结束,常用于主线程等待其他线程完成。

示例

class JoinExample extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }

    public static void main(String[] args) throws InterruptedException {
        JoinExample thread = new JoinExample();
        thread.start();
        thread.join(); // 主线程等待 JoinExample 完成
        System.out.println("Main thread continues");
    }
}

解析:主线程调用 join(),会等待 JoinExample 线程执行完毕后再继续执行。

7. 线程池

使用线程池可以有效地管理线程资源,避免频繁创建和销毁线程的开销。

示例

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

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

        for (int i = 0; i < 10; i++) {
            int taskId = i;
            pool.submit(() -> {
                System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟任务执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        pool.shutdown(); // 关闭线程池
    }
}

 解析:在这个例子中,使用线程池管理多个任务,并发执行,避免了创建线程的开销。

8. 并发编程中的常见问题

8.1 死锁

死锁是指两个或多个线程在执行过程中,因为争夺资源而造成的一种相互等待的现象。

示例

class DeadlockExample {
    static class A {
        synchronized void methodA(B b) {
            System.out.println("Thread 1: Holding lock A...");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock B...");
            b.last();
        }
        synchronized void last() {}
    }

    static class B {
        synchronized void methodB(A a) {
            System.out.println("Thread 2: Holding lock B...");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock A...");
            a.last();
        }
        synchronized void last() {}
    }

    public static void main(String[] args) {
        final A a = new A();
        final B b = new B();

        new Thread(() -> a.methodA(b)).start();
        new Thread(() -> b.methodB(a)).start();
    }
}

解析:在这个例子中,两个线程相互持有对方的锁,导致死锁。

8.2 饥饿

饥饿是指某个线程长时间得不到执行,导致无法运行的现象。

示例

import java.util.concurrent.locks.ReentrantLock;

public class StarvationExample {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Runnable task = () -> {
            while (true) {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " is running");
                } finally {
                    lock.unlock();
                }
            }
        };

        new Thread(task).start();
        new Thread(task).start();
    }
}

 解析:由于锁的竞争,某些线程可能一直无法获得锁,导致饥饿现象。

9. 总结

Java 提供了强大的线程和并发支持,通过核心类(ThreadRunnableLock 等)和 java.util.concurrent 包,可以方便地实现多线程编程。理解线程的生命周期、同步机制、并发工具,以及常见问题和解决方案,能够帮助开发者编写高效、安全的并发程序。

10. 进一步学习

建议深入了解 Java 并发的相关书籍,如《Java并发编程实战》,以获取更深入的知识和最佳实践。同时,通过实践项目来提高自己的并发编程能力。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值