多线程是指在一个进程中同时运行多个线程,每个线程执行不同的任务。线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。

线程与进程的区别在于,进程是资源分配的最小单位,线程是CPU调度的最小单位。同一个进程中的多个线程共享进程的资源,包括内存、文件描述符等。

线程的创建方式

在Java中,创建线程有两种主要方式:继承Thread类和实现Runnable接口。继承Thread类需要重写run方法,实现Runnable接口需要实现run方法。

// 继承Thread类
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running");
    }
}

// 实现Runnable接口
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable running");
    }
}

线程的生命周期

线程的生命周期包括新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五个状态。新建状态是线程被创建但未启动;就绪状态是线程已启动但未获得CPU时间片;运行状态是线程正在执行;阻塞状态是线程因等待资源或I/O操作而暂停执行;死亡状态是线程执行完毕或被中断。

线程同步与锁

多线程环境下,共享资源的访问可能导致数据不一致问题。Java提供了synchronized关键字和Lock接口来实现线程同步。synchronized可以修饰方法或代码块,确保同一时间只有一个线程执行该代码。

// synchronized方法
public synchronized void method() {
    // 同步代码
}

// synchronized代码块
public void method() {
    synchronized(this) {
        // 同步代码
    }
}

线程间通信

线程间通信可以通过wait、notify和notifyAll方法实现。这些方法必须在同步代码块或同步方法中调用。wait方法使当前线程释放锁并进入等待状态,notify方法唤醒一个等待线程,notifyAll方法唤醒所有等待线程。

// 生产者消费者示例
class Buffer {
    private int data;
    private boolean available = false;

    public synchronized void produce(int value) {
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        data = value;
        available = true;
        notifyAll();
    }

    public synchronized int consume() {
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        available = false;
        notifyAll();
        return data;
    }
}

线程池的使用

线程池是一种管理线程的机制,可以减少线程创建和销毁的开销。Java提供了Executor框架来创建和管理线程池。常见的线程池类型包括FixedThreadPool、CachedThreadPool和ScheduledThreadPool。

// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交任务
executor.submit(() -> {
    System.out.println("Task running");
});

// 关闭线程池
executor.shutdown();

死锁问题

死锁是指多个线程互相等待对方释放资源,导致所有线程都无法继续执行。死锁的四个必要条件是互斥条件、占有并等待、不可抢占和循环等待。避免死锁的方法包括破坏其中一个必要条件,例如按顺序获取锁或使用超时机制。

// 死锁示例
class Deadlock {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public void method1() {
        synchronized (lock1) {
            synchronized (lock2) {
                System.out.println("Method1");
            }
        }
    }

    public void method2() {
        synchronized (lock2) {
            synchronized (lock1) {
                System.out.println("Method2");
            }
        }
    }
}

并发工具类

Java提供了多种并发工具类,如CountDownLatch、CyclicBarrier和Semaphore。CountDownLatch用于等待多个线程完成,CyclicBarrier用于多个线程相互等待,Semaphore用于控制资源的访问数量。

// CountDownLatch示例
CountDownLatch latch = new CountDownLatch(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println("Thread finished");
        latch.countDown();
    }).start();
}

latch.await();
System.out.println("All threads finished");

线程安全集合

Java提供了多种线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList和BlockingQueue。这些集合类通过内部同步机制保证多线程环境下的安全性。

// ConcurrentHashMap示例
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
System.out.println(map.get("key"));

volatile关键字

volatile关键字用于确保变量的可见性和禁止指令重排序。volatile变量每次读取都直接从主内存中获取,每次写入都立即刷新到主内存。volatile适用于状态标志等简单场景,但不能保证复合操作的原子性。

// volatile示例
class VolatileExample {
    private volatile boolean flag = false;

    public void toggle() {
        flag = !flag;
    }

    public boolean isFlag() {
        return flag;
    }
}

原子类

Java提供了多种原子类,如AtomicInteger、AtomicLong和AtomicReference。这些类通过CAS(Compare-And-Swap)操作保证原子性,适用于计数器等场景。

// AtomicInteger示例
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
System.out.println(counter.get());

线程局部变量

ThreadLocal类用于创建线程局部变量,每个线程都有自己独立的变量副本。ThreadLocal适用于需要避免共享的变量,如数据库连接或用户会话信息。

// ThreadLocal示例
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
threadLocal.set(1);
System.out.println(threadLocal.get());

异步编程

Java提供了Future和CompletableFuture支持异步编程。Future表示异步计算的结果,CompletableFuture提供了更丰富的异步操作,如组合多个异步任务。

// CompletableFuture示例
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(System.out::println);

线程中断

线程中断是一种协作机制,通过调用interrupt方法设置中断标志,被中断的线程需要检查中断标志并决定是否终止。isInterrupted方法检查中断标志,interrupted方法检查并清除中断标志。

// 线程中断示例
Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        System.out.println("Running");
    }
});
thread.start();
thread.interrupt();

线程优先级

线程优先级用于提示调度器优先调度高优先级线程,但实际执行顺序取决于操作系统。Java中线程优先级范围是1(MIN_PRIORITY)到10(MAX_PRIORITY),默认是5(NORM_PRIORITY)。

// 设置线程优先级
Thread thread = new Thread(() -> System.out.println("Running"));
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();

守护线程

守护线程是为其他线程提供服务的线程,当所有非守护线程结束时,守护线程会自动终止。通过setDaemon方法设置线程为守护线程。

// 守护线程示例
Thread daemon = new Thread(() -> {
    while (true) {
        System.out.println("Daemon running");
    }
});
daemon.setDaemon(true);
daemon.start();

线程组

线程组用于管理一组线程,可以对组内所有线程进行统一操作,如设置优先级或中断。线程组可以嵌套形成树形结构。

// 线程组示例
ThreadGroup group = new ThreadGroup("MyGroup");
Thread thread = new Thread(group, () -> System.out.println("Group thread"));
thread.start();

线程异常处理

线程的未捕获异常可以通过设置UncaughtExceptionHandler进行处理。默认情况下,未捕获异常会导致线程终止,但不会影响其他线程。

// 异常处理示例
Thread thread = new Thread(() -> {
    throw new RuntimeException("Error");
});
thread.setUncaughtExceptionHandler((t, e) -> {
    System.out.println("Exception in thread " + t.getName() + ": " + e);
});
thread.start();

线程性能优化

多线程性能优化包括减少锁竞争、使用无锁数据结构、合理设置线程池大小等。避免过度同步,缩小同步范围,使用读写锁(ReentrantReadWriteLock)替代独占锁。

// 读写锁示例
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
try {
    // 读操作
} finally {
    lock.readLock().unlock();
}

lock.writeLock().lock();
try {
    // 写操作
} finally {
    lock.writeLock().unlock();
}

想获取更多学习资料,请关注 GZH 【咖啡 java 研习班

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐