并发编程 · 创建高效的多线程世界

社区
引言

上一节确实介绍了线程的基本知识。这些内容为理解多线程的实现提供了基础。接下来,我们将深入探讨两种常见的线程实现方式:Thread类,Runnable接口。

在Java中,Thread类和Runnable接口都是用于实现多线程的重要工具。下面我们来分别查看它们的源码,以便更好地理解它们的实现原理。

picture.image

Thread 源码

Thread类是Java中用于实现多线程的核心类。它实现了Runnable接口,并提供了许多与线程相关的方法。以下是Thread类的部分源码:

public class Thread implements Runnable {
    // 线程名称
    private volatile String name;

    // 线程优先级
    private int priority;

    // 线程的堆栈大小
    private long stackSize;

    // 线程的状态
    private volatile int threadStatus = 0;

    // 线程是否为守护线程
    private boolean daemon = false;

    // 线程是否已经启动
    private volatile boolean started = false;

    // 线程的目标对象
    private Runnable target;

    // 线程的运行结果
    private volatile Object result;

    // 线程的锁
    private final Object lock = new Object();

    // 构造方法
    public Thread() {
        this(null, null, "Thread-" + nextThreadNum(), 0);
    }

    public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }

    public Thread(Runnable target, String name) {
        this(null, target, name, 0);
    }

    public Thread(String name) {
        this(null, null, name, 0);
    }

    public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
        // ... 初始化线程的相关属性
    }

    // 重写Runnable接口的run方法
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

    // 启动线程
    public synchronized void start() {
        // ... 启动线程的相关操作
    }

    // 其他线程相关的方法,如stop(), join(), sleep()等
    // ...
}
Runnable 源码

Runnable接口是Java中用于定义线程任务的接口。它只包含一个run()方法,用于执行线程的任务。以下是Runnable接口的源码:

public interface Runnable {
    public abstract void run();
}

通过查看Thread类和Runnable接口的源码,我们可以更好地理解Java多线程的实现原理。

Thread类负责管理线程的生命周期、状态和其他相关属性,而Runnable接口则定义了线程的任务内容。在实际开发中,我们通常会实现Runnable接口来定义线程任务,然后将其作为参数传递给Thread类的构造函数,从而创建和启动线程。

通过仔细观察就能发现,Thread类实现了Runnable接口。实际上,Thread类和Runnable接口在实现多线程上的区别主要体现在以下几个方面:

继承与实现

当我们需要创建一个新的线程时,可以选择继承Thread类或者实现Runnable接口。继承Thread类意味着我们需要创建一个新的类,该类继承自Thread类,并重写其run()方法。而实现Runnable接口则意味着我们需要创建一个新的类,该类实现了Runnable接口,并重写其run()方法。

// 继承Thread类
class MyThread extends Thread {
    @Override
    public void run() {
        // 线程逻辑
    }
}

// 实现Runnable接口
class MyTask implements Runnable {
    @Override
    public void run() {
        // 线程逻辑
    }
}

灵活性

实现Runnable接口相较于继承Thread类具有更高的灵活性。因为Java不支持多重继承,所以如果一个类已经继承了其他类,就不能再继承Thread类。而实现Runnable接口则没有这个限制,一个类可以实现多个接口。

class MyTask extends SomeClass implements Runnable {
    @Override
    public void run() {
        // 线程逻辑
    }
}

资源共享

当我们实现Runnable接口时,可以将Runnable对象作为参数传递给Thread类的构造函数,从而实现线程的创建和启动。这种方式下,多个线程可以共享同一个Runnable对象的状态,这有助于实现线程间的资源共享。

Runnable task = new MyTask();
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);

而当我们继承Thread类时,每个线程都会创建一个新的Thread对象,这使得线程间的资源共享变得困难。

设计原则

从面向对象设计的角度来看,实现Runnable接口更符合“开闭原则”和“单一职责原则”。开闭原则指的是对扩展开放,对修改关闭,实现Runnable接口允许我们在不修改现有代码的情况下,通过添加新的Runnable实现类来扩展线程的功能。单一职责原则指的是一个类应该只负责一个职责,实现Runnable接口可以让我们将线程的创建和管理与具体的业务逻辑分离,使得代码更加清晰。

Thread 和 Runnable 解析

Thread 类

Thread类是Java中用于实现多线程的核心类。它实现了Runnable接口,并提供了许多与线程相关的方法。Thread类的主要职责是管理线程的生命周期、状态和其他相关属性。

示例

通过继承Thread类创建一个新的线程。

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("线程[" + Thread.currentThread().getName() + "] 执行:" + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

Runnable 接口

Runnable接口是Java中用于定义线程任务的接口。它只包含一个run()方法,用于执行线程的任务。Runnable接口的主要职责是定义线程要执行的操作。

示例

通过实现Runnable接口创建一个新的线程。

class MyTask implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("线程[" + Thread.currentThread().getName() + "] 执行:" + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyTask());
        thread.start();
    }
}
Thread 与 Runnable的区别

picture.image

示例:通过继承Thread类和实现Runnable接口分别创建两个线程,并比较它们的输出结果。

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread类线程[" + Thread.currentThread().getName() + "] 执行:" + i);
        }
    }
}

class MyTask implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Runnable接口线程[" + Thread.currentThread().getName() + "] 执行:" + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        thread1.start();

        Thread thread2 = new Thread(new MyTask());
        thread2.start();
    }
}

使用场景

示例:在实际开发中,我们通常会根据具体的需求来选择使用Thread类还是Runnable接口。如果一个类已经继承了其他类,那么只能选择实现Runnable接口来实现多线程。此外,实现Runnable接口也更符合面向对象设计原则,更具有灵活性和可扩展性。

class MyTask implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("线程[" + Thread.currentThread().getName() + "] 执行:" + i);
        }
    }
}

class MyClass extends SomeClass {
    public void startTask() {
        Thread thread = new Thread(new MyTask());
        thread.start();
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.startTask();
    }
}
Thread常见方法

我们通常会使用Thread类或实现Runnable接口来创建和管理线程。在使用这些方法时,需要注意线程安全和同步问题,以避免出现意外的行为和性能问题。

run()

如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法。否则,该方法不执行任何操作并返回。

class MyTask implements Runnable {
    @Override
    public void run() {
        // 线程逻辑
    }
}

Thread thread = new Thread(new MyTask());
thread.run(); // 调用MyTask中的run方法

sleep(long millis)

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。此操作受到系统计时器和调度程序精度和准确性的影响。

try {
    Thread.sleep(1000); // 当前线程休眠1秒
} catch (InterruptedException e) {
    e.printStackTrace();
}

yield()

暂停当前正在执行的线程对象,并执行其他线程。线程调度器将尽可能快地重新调度当前线程,但它并不保证一定会立即执行。

Thread.yield(); // 当前线程让出CPU资源,尽快重新调度

start()

使该线程开始运行,Java虚拟机再调用该线程的run方法。

Thread thread = new Thread(new MyTask());
thread.start(); // 启动线程,调用MyTask中的run方法

join()

等待该线程结束。调用某个线程的join()方法会使当前线程暂停执行,直到被调用的线程执行完毕。

Thread thread = new Thread(new MyTask());
thread.start();
thread.join(); // 当前线程等待thread线程执行完毕
run 和 start

run()start()都是Thread类的方法,它们在多线程编程中起着不同的作用。

run() 方法表示线程要执行的任务,需要在子类中重写;

start()方法用于启动线程,会自动调用run()方法来执行任务。

在实际开发中我们通常会重写run()方法来定义线程的逻辑,然后调用start()方法来启动线程。

run()

run()方法是Thread类中的一个空方法,它表示线程要执行的任务。在Thread类中,run()方法是一个空方法,需要在子类中重写。在实现Runnable接口时,run()方法是接口中唯一的方法,用于定义线程要执行的操作。 当你创建一个Thread对象或实现Runnable接口的对象时,需要重写run()方法来定义线程的任务。当线程启动时,它将自动调用run()方法来执行任务。

class MyThread extends Thread {
    @Override
    public void run() {
        // 线程逻辑
    }
}

class MyTask implements Runnable {
    @Override
    public void run() {
        // 线程逻辑
    }
}

start()

start()方法用于启动线程。当你调用一个Thread对象的start()方法时,Java虚拟机会创建一个新的线程,并调用该线程的run()方法来执行任务。

需要注意的是,不要直接调用run()方法来启动线程。这样做实际上是在主线程上下文中执行了run()方法,而不是作为新线程执行。正确的做法是调用start()方法来启动线程。

Thread thread = new Thread(new MyTask());
thread.start(); // 启动线程,调用MyTask中的run方法
写在最后

多线程编程是现代软件开发中不可或缺的一部分,尤其对于处理并发任务、提高程序性能和响应能力等方面显得尤为重要。作为一名经验丰富的多线程使用者,我对多线程使用的理解可以从以下几个方面进行叙述:

首先,多线程不仅仅是让程序在同一时间执行多个任务那么简单。它的核心在于有效地管理和协调不同线程之间的执行顺序和数据访问,以确保程序的正确性和性能。这就要求我们在设计多线程程序时,必须仔细考虑线程间的同步和互斥问题,防止数据竞争和资源争用。

其次,选择合适的线程实现方式是关键。Java中提供了继承Thread类、实现Runnable接口以及使用线程池等多种方式来实现多线程。每种方式都有其优缺点,我们需要根据具体的应用场景和需求来做出合理的选择。例如,如果我们的类已经继承了其他类,那么实现Runnable接口可能是一个更好的选择,因为它允许类具有多重身份。

此外,线程安全是使用多线程时必须关注的问题。在多线程环境下,数据的可见性和状态的一致性都可能受到影响。为了确保线程安全,我们需要使用各种同步机制,如锁、信号量、条件变量等,来控制对共享资源的访问。同时,避免长时间持有锁、减少锁的竞争以及合理使用读写锁等策略也有助于提高程序的并发性能。

线程间的通信和协作也是多线程编程中不可忽视的一环。在某些情况下,线程之间需要相互等待或通知对方完成任务,这时就需要用到线程间的通信机制,如wait()、notify()和notifyAll()等。合理使用这些方法可以有效地协调线程间的执行顺序,提高程序的运行效率。

最后,我认为多线程编程是一种艺术,需要我们不断地学习和实践。只有深入了解多线程的工作原理和底层机制,才能在实际开发中灵活运用各种技术和策略,编写出高效、稳定且可扩展的多线程程序。在这个过程中,耐心、细心和不断尝试是必不可少的品质。

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论