多线程学习,一篇文档就够!!!

多线程教学文档

一、引言

欢迎来到本教学文档,本文将详细介绍多线程的概念、实现方式以及常用方法。作为一名从事自媒体行业多年的Java开发工程师,我将以CSDN博主的文章结构为蓝本,以幽默的风格为您呈现多线程编程的精华知识。

二、目录

  1. 引言

  2. 目录

  3. 什么是多线程?

  4. 多线程的实现方式

    1. 继承Thread类

    2. 实现Runnable接口

    3. 实现Callable接口

  5. 多线程方法

    1. 守护线程

    2. 线程强制插入JOIN()

    3. 线程优先级

    4. 线程休眠

    5. 线程的五种状态

    6. 线程停止

    7. 线程礼让Yield()

    8. 线程同步(synchronized和Lock)

    9. 线程不安全集合

    10. 死锁

    11. 线程通信

    12. 线程池

  6. 总结

三、什么是多线程?

在软件或硬件上实现多个线程并发执行的技术被称为多线程。这种技术能够在同一时间执行多于一个线程,从而提升整体处理性能。了解多线程前,我们需要明确两个术语:

  • 进程: 执行程序中的一次执行过程,是系统资源分配的单位。

  • 线程: 在系统中,一个程序有一个进程,一个进程中至少有一个线程。线程是CPU调度和执行的单位。

四、多线程的实现方式

4.1 继承Thread类

javaCopy codepublic class TestThread extends Thread {
    @Override
    public void run() {
        // 线程执行的方法体
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在刷抖音!" + i);
        }
    }
​
    public static void main(String[] args) {
        TestThread thread = new TestThread();
        thread.start();
​
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在上厕所!" + i);
        }
    }
}

4.2 实现Runnable接口

javaCopy codepublic class TestRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的方法体
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在刷抖音!" + i);
        }
    }
​
    public static void main(String[] args) {
        Thread thread = new Thread(new TestRunnable());
        thread.start();
​
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在上厕所!" + i);
        }
    }
}

4.3 实现Callable接口

javaCopy codeimport 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 TestCallable implements Callable<Boolean> {
​
    String name = null;
​
    public TestCallable(String name) {
        this.name = name;
    }
​
    @Override
    public Boolean call() throws Exception {
        // 线程执行的方法体
        for (int i = 1; i <= 20; i++) {
            System.out.println(name + i);
        }
​
        return true;
    }
​
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建线程
        TestCallable t1 = new TestCallable("睡觉!");
        TestCallable t2 = new TestCallable("水饺");
​
        // 创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(2);
​
        // 提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
​
        // 获取结果
        Boolean res1 = r1.get();
        Boolean res2 = r2.get();
​
        System.out.println(res1);
        System.out.println(res2);
​
        // 关闭服务
        ser.shutdown();
    }
}

案例:龟兔赛跑

javaCopy codepublic class Race implements Runnable {
​
    public static String winner;
​
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            String name = Thread.currentThread().getName();
            // 兔子线程睡眠,保证每次赢得都是乌龟
            if (name.equals("兔子") && i % 10 == 0) {
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            Boolean over = this.isOver(i);
            if (over) {
                break;
            }
​
            System.out.println(name + "跑了第" + i + "步");
        }
    }
​
    /**
     * 判断游戏是否结束
     *
     * @param steps
     * @return
     */
    public Boolean isOver(int steps) {
        if (winner != null) {
            return true;
        } else {
            if (steps >= 100) {
                winner = Thread.currentThread().getName();
                System.out.println("winner is " + winner);
                return true;
            }
        }
​
        return false;
    }
​
    public static void main(String[] args) {
        Race race = new Race();
​
        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }
}

五、多线程方法

5.1 守护线程

守护线程在Java虚拟机中执行,为其他线程提供便利服务。守护线程的经典例子是垃圾回收器(GC)。

案例:妈妈监视我写作业

javaCopy codepublic class TestDaemon {
    public static void main(String[] args) {
        Mom mom = new Mom();
        You you = new You();
​
        Thread thread1 = new Thread(mom);
        thread1.setDaemon(true); // 将mom设置为守护线程
        thread1.start();
​
        new Thread(you).start();
    }
}
​
class Mom implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("妈妈在监视着你写作业!");
        }
    }
}
​
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 365 * 100; i++) {
            System.out.println("好好写作业,离上大学不远了!");
        }
    }
}

5.2 线程强制插入JOIN()

案例:模拟售票窗口

javaCopy codepublic class TestJoin implements Runnable {
    private int ticketNums = 10;
​
    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
​
            // 模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "票");
        }
    }
​
    public static void main(String[] args) {
        TestJoin ticket = new TestJoin();
​
        new Thread(ticket, "小明").start();
        new Thread(ticket, "老师").start();
        new Thread(ticket, "黄牛").start();
    }
}

5.3 线程优先级

javaCopy codepublic class TestPriority {
    public static void main(String[] args) {
        // 主线程默认优先级
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
​
        MyPriority myPriority = new MyPriority();
​
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
​
        // 先设置优先级再启动
        t1.start();
​
        t2.setPriority(1);
        t2.start();
​
        t3.setPriority(4);
        t3.start();
​
        t4.setPriority(Thread.MAX_PRIORITY); // MAX_PRIORITY = 10
        t4.start();
​
        t5.setPriority(Thread.MIN_PRIORITY); // MIN_PRIORITY = 1
        t5.start();
    }
}
​
class MyPriority implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

5.4 线程休眠

javaCopy codepublic class TestSleep implements Runnable {
    private int ticketNums = 10;
​
    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
​
            // 模拟延时
            try {
                Thread.sleep(200); // 暂停代码执行,让出cpu给其他线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "票");
        }
    }
​
    public static void main(String[] args) {
        TestSleep ticket = new TestSleep();
​
        new Thread(ticket, "小明").start();
        new Thread(ticket, "老师").start();
        new Thread(ticket, "黄牛").start();
    }
}

5.5 线程的五种状态

  1. 新建(New): 线程对象被创建后,即进入了新建状态。例如,Thread thread = new Thread()。

  2. 就绪(Runnable): 线程对象被创建后,其它线程调用了该对象的start()方法。线程此时进入就绪状态,但并不意味着立即进入运行状态,它需要等待CPU的调度。

  3. 运行(Running): 如果处于就绪状态的线程获取了CPU,此时线程就进入了运行状态。具体执行哪个线程由操作系统的调度算法来决定。

  4. 阻塞(Blocked):

    处于运行状态的线程由于某些原因,暂时放弃对CPU的使用权,暂时停止运行。直到线程进入了就绪状态,才有机会再次获得CPU使用权。阻塞的情况分三种:

    • 等待阻塞: 运行状态中的线程执行wait()方法,使线程进入等待阻塞状态。

    • 同步阻塞: 线程在获取对象的同步锁时,若该同步锁已被其他线程占用,则该线程进入同步阻塞状态。

    • 其他阻塞: 通过调用线程的sleep()join()发出了I/O请求时,线程会进入到阻塞状态。

  5. 死亡(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

5.6 线程停止

在多线程编程中,线程的停止是一个涉及到安全性和可控性的重要问题。在Java中,通常推荐线程自己停止,而不是强制终止线程。以下是一种推荐的线程停止的方式:

javaCopy code/**
 * @Author WangYan
 * @Date 2022/11/9 15:05
 * @Version 1.0
 * 线程停止
 * 1、推荐线程自己停止下来
 * 2、建议使用一个标志符来进行终止变量。当flag = false ,则终止线程运行
 */
public class TestStop implements Runnable{
​
    private Boolean flag = true;
​
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println(Thread.currentThread().getName() + "run......." + i++);
        }
    }
​
    /**
     * 线程更改标志符
     */
    public void stop(){
        this.flag = false;
    }
​
    public static void main(String[] args) {
        TestStop stop = new TestStop();
        new Thread(stop).start();
​
        for (int i = 0; i <= 1000000000; i++) {
            if (i == 999999){
                // 更改线程标志符,使线程停止
                stop.stop();
                System.out.println("线程停止了");
            }
        }
    }
}

在这个例子中,我们创建了一个线程 TestStop,其中包含一个循环,只有在 flagtrue 的情况下才会继续执行。通过在主线程中改变 flag 的值,我们能够优雅地停止线程的执行。这种方式避免了强制终止线程,使得线程在执行完当前任务后能够自己优雅地停止。

7. 线程礼让 (Yield())

线程礼让是通过 Thread.yield() 方法实现的,它暗示调度器当前线程愿意让出其使用的处理器资源,以便其他线程有机会运行。调用 yield() 并不会释放锁或者资源,仅是一种提示。

7.1 示例代码
javaCopy code/**
 * @Author WangYan
 * @Date 2022/11/13 17:30
 * @Version 1.0
 * 线程礼让示例
 */
public class ThreadYieldExample {

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Producer: Producing item " + i);
                Thread.yield(); // 线程礼让
            }
        });

        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Consumer: Consuming item " + i);
                Thread.yield(); // 线程礼让
            }
        });

        producer.start();
        consumer.start();
    }
}

在上述代码中,ProducerConsumer 线程在运行过程中调用 Thread.yield(),表示它们愿意让出一些处理器资源,以便其他线程运行。实际效果可能因操作系统和具体调度器的不同而有所差异。

8. 线程同步 (synchronizedLock)

线程同步是多线程编程中的关键问题之一,主要涉及到多个线程访问共享资源时的数据一致性问题。Java 提供了 synchronized 关键字和 Lock 接口来实现线程同步。

8.1 使用 synchronized 实现线程同步
javaCopy code/**
 * @Author WangYan
 * @Date 2022/11/13 17:45
 * @Version 1.0
 * 使用 synchronized 实现线程同步示例
 */
public class SynchronizedExample {

    private static int counter = 0;

    public static synchronized void increment() {
        counter++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Counter: " + counter);
    }
}

在上述代码中,increment() 方法被声明为 synchronized,保证了对 counter 的访问是原子的,从而避免了竞态条件。

8.2 使用 Lock 实现线程同步
javaCopy codeimport java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author WangYan
 * @Date 2022/11/13 18:00
 * @Version 1.0
 * 使用 Lock 实现线程同步示例
 */
public class LockExample {

    private static int counter = 0;
    private static final Lock lock = new ReentrantLock();

    public static void increment() {
        lock.lock();
        try {
            counter++;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                increment();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Counter: " + counter);
    }
}

在上述代码中,通过 ReentrantLock 创建了一个锁,使用 lock()unlock() 方法实现了对 counter 的线程安全访问。

9. 线程不安全集合

在多线程环境中,使用不安全的集合类可能导致数据不一致性和线程安全问题。Java 提供了一些线程安全的集合类,如 VectorHashtableCollections.synchronizedXXX 等,用于在多线程环境中安全地操作集合。

9.1 示例代码
javaCopy codeimport java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @Author WangYan
 * @Date 2022/11/13 18:15
 * @Version 1.0
 * 使用线程安全的集合示例
 */
public class ThreadSafeCollectionExample {

    public static void main(String[] args) throws InterruptedException {
        // 使用 Collections.synchronizedList() 创建线程安全的 List
        List<String> threadSafeList = Collections.synchronizedList(new ArrayList<>());

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                threadSafeList.add("Item " + i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                threadSafeList.add("Another Item " + i);
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Thread-safe List size: " + threadSafeList.size());
    }
}

在上述代码中,通过 Collections.synchronizedList() 创建了一个线程安全的 List,确保在多线程环境中对集合的操作是线程安全的。

10. 死锁

死锁是多线程编程中常见的问题之一,它发生在两个或多个线程互相等待对方释放锁资源的情况下。这样的情况将导致程序无法继续执行下去。

10.1 死锁的示例
javaCopy code/**
 * @Author WangYan
 * @Date 2022/11/13 18:30
 * @Version 1.0
 * 死锁示例
 */
public class DeadlockExample {

    private static final Object LOCK1 = new Object();
    private static final Object LOCK2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (LOCK1) {
                System.out.println("Thread 1: Holding LOCK1...");

                try {
                    Thread.sleep(10); // 为了让另一个线程有机会持有 LOCK2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("Thread 1: Waiting for LOCK2...");
                synchronized (LOCK2) {
                    System.out.println("Thread 1: Holding LOCK1 and LOCK2...");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (LOCK2) {
                System.out.println("Thread 2: Holding LOCK2...");

                try {
                    Thread.sleep(10); // 为了让另一个线程有机会持有 LOCK1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("Thread 2: Waiting for LOCK1...");
                synchronized (LOCK1) {
                    System.out.println("Thread 2: Holding LOCK2 and LOCK1...");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

上述代码中,Thread 1 持有 LOCK1 并等待 LOCK2,而 Thread 2 持有 LOCK2 并等待 LOCK1,两个线程都在等待对方释放锁,从而形成死锁。

11. 线程通信

线程通信是多线程编程中一种重要的机制,允许线程之间进行协调和合作。常见的线程通信方式包括使用 wait()notify()notifyAll() 方法。

11.1 示例代码
javaCopy code/**
 * @Author WangYan
 * @Date 2022/11/13 18:45
 * @Version 1.0
 * 线程通信示例
 */
public class ThreadCommunicationExample {

    private static final Object LOCK = new Object();
    private static boolean isDataReady = false;

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            // 生产者线程
            synchronized (LOCK) {
                System.out.println("Producer: Producing data...");
                isDataReady = true;
                LOCK.notify(); // 通知等待中的消费者线程
            }
        });

        Thread consumer = new Thread(() -> {
            // 消费者线程
            synchronized (LOCK) {
                while (!isDataReady) {
                    try {
                        LOCK.wait(); // 等待生产者线程通知
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("Consumer: Consuming data...");
            }
        });

        consumer.start();
        producer.start();
    }
}

在上述代码中,Producer 线程负责生产数据,而 Consumer 线程负责消费数据。通过 wait()notify(),实现了线程之间的协同工作。

12. 线程池

线程池是一种管理线程的机制,可以有效地复用线程、控制并发线程数,提高程序性能。Java 中通过 Executor 框架提供了线程池的支持。

12.1 示例代码
javaCopy codeimport java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
/**
 * @Author WangYan
 * @Date 2022/11/13 19:00
 * @Version 1.0
 * 线程池示例
 */
public class ThreadPoolExample {
​
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
​
        // 提交任务给线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
            });
        }
​
        // 关闭线程池
        executorService.shutdown();
    }
}

在上述代码中,通过 Executors.newFixedThreadPool(3) 创建了一个固定大小为 3 的线程池,然后提交了5个任务给线程池执行。线程池会自动管理线程的生命周期,提高了线程的复用率和性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值