java必学必会之线程(1)

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技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • Win11 22563如何还原右键单击Windows图标?

    如何还原右键单击Windows图标? 在Win11 22563中,右键单击Windows图标时,弹出的菜单选项与以前版本的Win10不同。如果你想要还原右键单击Windows图标的默认行为,可以按照以下步骤执行: 打开注册表编辑器 按下“Win + R”键,输入“regedit”并按下“Enter”键。此时会弹出注册表编辑器的窗口。 定位到相关目录 在注册表…

    other 2023年6月27日
    00
  • JavaScript中OnLoad几种使用方法

    当一个网页被加载时,浏览器会自动触发其onload事件。在JavaScript中,我们可以使用onload事件来执行一些操作,例如加载页面时显示一些动画效果、触发一些脚本代码等等。下面讲解几种使用onload事件的方法。 方法一:为window对象添加onload事件处理程序 window.onload = function() { // 在这里编写需要执行…

    other 2023年6月25日
    00
  • MyBatis一对多嵌套查询的完整实例

    MyBatis一对多嵌套查询的完整实例攻略 简介 MyBatis是一个流行的Java持久化框架,它提供了一种简单而强大的方式来与数据库进行交互。在一些场景中,我们需要进行一对多的嵌套查询,即查询一个实体对象及其关联的多个子对象。本攻略将详细介绍如何在MyBatis中实现一对多嵌套查询,并提供两个示例说明。 步骤 步骤1:创建数据库表和实体类 首先,我们需要创…

    other 2023年7月28日
    00
  • [下载]安卓6.0/Android M第三个开发者预览版固件下载地址

    下载安卓6.0/Android M第三个开发者预览版固件下载地址 安卓6.0/Android M第三个开发者预览版现已推出,下载地址如下: 步骤1:为您的设备下载正确的固件 请确保您下载的固件与您的设备和型号相匹配,以避免因下载错误的固件导致问题。 示例说明1:如果您的设备是Nexus 6,那么您应该下载“shamu”的固件。 示例说明2:如果您的设备是Ne…

    other 2023年6月26日
    00
  • SpringBoot使用@Autowired为多实现的接口注入依赖

    SpringBoot使用@Autowired为多实现的接口注入依赖 在Spring Boot中,使用@Autowired注解将依赖注入到类中是非常常见的操作。当接口有多个实现类时,我们可以使用@Autowired注解根据条件选择合适的实现类进行注入。 下面是使用@Autowired为多实现的接口注入依赖的完整攻略: 步骤一:定义接口和多个实现类 首先,我们需…

    other 2023年6月28日
    00
  • mariadb启动方法

    Mariadb启动方法 一、前言 本篇文章主要介绍Mariadb启动方法,内容适用于所有使用Mariadb的用户,帮助用户正确、快速的启动Mariadb。 二、启动方法 启动Mariadb需要通过终端或命令行执行相关命令,具体步骤如下: 打开终端或命令行。 输入以下命令以启动Mariadb服务: sudo systemctl start mariadb.se…

    其他 2023年3月28日
    00
  • 清洁jenkins工作区

    Jenkins是一个流行的持续集成和持续交付工具,它可以帮助我们自动化构建、测试和部署软件。在使用Jenkins时,我们需要定期清理工作区,以避免占用过多的磁盘空间和混淆旧的构结果。本攻略将介绍如何清洁Jenkins工作区,并提供两个示例。 步骤一:使用Jenkins插件清洁作区 Jenkins提供了许多插件,可以帮助我们清洁工作区。以下是一个示例,展示了如…

    other 2023年5月9日
    00
  • iOS8.1完美越狱插件推荐:ShowCase显示键盘英文大小写

    iOS8.1完美越狱插件推荐:ShowCase显示键盘英文大小写攻略 简介 在iOS 8.1上进行完美越狱后,你可以使用ShowCase插件来显示键盘上的英文大小写状态。这个插件非常实用,特别是当你需要输入密码或者进行英文文本编辑时。下面是详细的攻略,包含了安装和使用ShowCase插件的步骤。 步骤 步骤一:安装ShowCase插件 打开Cydia应用,确…

    other 2023年8月17日
    00
合作推广
合作推广
分享本页
返回顶部