单线程与多线程的理解与学习(入门到深入)

在这里插入图片描述

一、在Java中,有多种方式可以创建线程。以下是几种常用的方法:

  1. 继承Thread类:创建一个继承自Thread类的子类,并重写run()方法,在run()方法中定义线程的执行逻辑。然后通过创建子类对象,调用start()方法来启动线程。示例代码如下:
class MyThread extends Thread {
    
    
    public void run() {
    
    
        // 线程的执行逻辑
    }
}
// 创建并启动线程
MyThread thread = new MyThread();
thread.start();
  1. 实现Runnable接口:创建一个实现了Runnable接口的类,并实现其run()方法,在run()方法中定义线程的执行逻辑。然后通过创建Runnable实现类的对象,再创建Thread对象,并将Runnable实现类的对象作为参数传递给Thread的构造方法。最后调用Thread对象的start()方法来启动线程。示例代码如下:
class MyRunnable implements Runnable {
    
    
    public void run() {
    
    
        // 线程的执行逻辑
    }
}
// 创建并启动线程
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
  1. 使用匿名内部类:通过创建匿名内部类来实现Runnable接口,并实现其run()方法。然后创建Thread对象,并将匿名内部类对象作为参数传递给Thread的构造方法。最后调用Thread对象的start()方法来启动线程。示例代码如下:
Runnable runnable = new Runnable() {
    
    
    public void run() {
    
    
        // 线程的执行逻辑
    }
};
// 创建并启动线程
Thread thread = new Thread(runnable);
thread.start();
  1. 使用线程池:通过Java提供的Executor框架可以方便地创建线程池和管理线程。使用线程池可以更好地控制线程的数量和资源消耗。示例代码如下:
ExecutorService executor = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池,最多同时执行5个线程
Runnable runnable = new Runnable() {
    
    
    public void run() {
    
    
        // 线程的执行逻辑
    }
};
executor.execute(runnable); // 提交任务给线程池执行
executor.shutdown(); // 关闭线程池

以上是几种常见的创建线程的方式,根据具体的需求选择合适的方式来创建线程。注意,在使用多线程时,要注意线程安全和资源共享的问题。

二、线程的调度

假如我们的电脑只有一个CPU,那么CPU在某一时刻只能执行一个指令,其他程序只有分到CPU的时间片(也就是使用权),才能够执行指令。

线程的调度分为两种调度模型

分时调度模型

所有线程轮流使用CPU的使用时间,平均分配每个线程占用CPU的时间片。

抢占式调度模型

优先让优先级的高的线程使用CPU,如果优先级相同,随机选择一个线程,优先级高的线程所获取的CPU时间片会相对多一些。

线程调度是操作系统或者编程语言的任务调度器决定在多个线程之间分配处理器执行时间的过程。线程调度的目标是合理利用CPU资源,提高系统的吞吐量和响应速度。

在Java中,线程调度是由Java虚拟机(JVM)的线程调度器负责的。JVM的线程调度器按照一定的策略来决定哪个线程获得执行时间,具体的调度策略可能因不同的JVM实现而有所不同。

Java线程调度器使用抢占式调度方式。抢占式调度意味着较高优先级的线程可以抢占正在执行的线程的CPU执行时间,以确保高优先级任务的及时执行。线程的优先级用整数表示,范围从1到10,默认为5。可以使用Thread类的setPriority()方法设置线程的优先级。

Java线程调度器基于时间片轮转的调度算法,每个线程被分配一个时间片段,在时间片段结束后,线程调度器会切换到下一个线程继续执行。时间片的长度可以通过操作系统或者JVM的设置进行调整。

除了线程优先级和时间片轮转算法,Java还提供了一些其他的线程调度方法,例如:

  1. yield()方法:调用yield()方法可以让当前线程主动让出CPU执行时间,使得其他具有相同优先级的线程有机会执行。
  2. sleep()方法:调用sleep()方法可以使线程暂停执行一段时间,让其他线程有机会执行。
  3. join()方法:调用join()方法可以让一个线程等待另一个线程执行完毕后再继续执行。
    线程调度是一个复杂的主题,涉及到多个因素,包括线程优先级、时间片分配、同步机制等。了解线程调度的原理和方法,可以帮助开发人员编写高效且可靠的多线程程序。

三、线程传值

在线程之间传递值可以通过以下几种方式实现:

  1. 构造函数或方法参数传递:可以在创建线程的时候,将需要传递的值通过构造函数或者方法参数传递给线程。线程在执行时就可以使用这些传递的值。示例代码如下:
class MyThread extends Thread {
    
    
    private int value;
    public MyThread(int value) {
    
    
        this.value = value;
    }
    public void run() {
    
    
        // 使用传递的值
        System.out.println("Value: " + value);
    }
}
// 创建并启动线程,传递值
int value = 10;
MyThread thread = new MyThread(value);
thread.start();
  1. 实例变量传递:可以在创建线程后,通过设置线程对象的实例变量来传递值。示例代码如下:
class MyThread extends Thread {
    
    
    private int value;
    public void setValue(int value) {
    
    
        this.value = value;
    }
    public void run() {
    
    
        // 使用传递的值
        System.out.println("Value: " + value);
    }
}
// 创建并启动线程,设置实例变量的值
MyThread thread = new MyThread();
int value = 10;
thread.setValue(value);
thread.start();
  1. 使用ThreadLocal:ThreadLocal类可以在每个线程中维护一个变量的副本,每个线程可以独立地访问自己的副本,实现了线程间的数据隔离。示例代码如下:
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
Runnable runnable = new Runnable() {
    
    
    public void run() {
    
    
        // 设置线程的变量值
        threadLocal.set(10);
        // 获取线程的变量值
        System.out.println("Value: " + threadLocal.get());
        // 清除线程的变量值
        threadLocal.remove();
    }
};
// 创建并启动线程
Thread thread = new Thread(runnable);
thread.start();

通过上述方法,可以在不同的线程间传递值,并在线程内部获取和使用这些传递的值。注意在多线程场景下,要注意线程安全和数据一致性的问题。

四、什么是线程同步

线程同步是一种机制,用于协调多个线程之间的执行顺序和共享资源的访问,以避免出现数据不一致或不确定的结果。
在多线程环境下,如果多个线程同时访问和修改共享资源,可能会导致以下问题:

  1. 竞态条件(Race Condition):多个线程同时访问和修改同一个共享变量,导致最终结果的不确定性。
  2. 数据不一致(Data Inconsistency):多个线程对共享数据进行操作,其中一个线程的操作可能会影响到其他线程的操作,导致数据的不一致性。
    线程同步的目的是通过限制多个线程的执行顺序,并提供互斥访问共享资源的机制,确保线程之间的协调和数据的一致性。
    常见的线程同步机制包括:
  3. 锁机制:使用锁(如synchronized关键字和ReentrantLock类)来保护共享资源的访问,确保同一时刻只有一个线程可以访问共享资源,从而避免竞态条件和数据不一致。
  4. 条件变量:使用条件变量(如wait()、notify()、notifyAll())来实现线程之间的等待和唤醒机制,以实现有序访问和协作。
  5. 原子操作:使用原子类(如AtomicInteger、AtomicReference等)来实现对共享变量的原子操作,保证操作的原子性,避免竞态条件和数据不一致。
  6. volatile关键字:使用volatile关键字来保证共享变量的可见性,确保多个线程对变量进行操作时能够看到最新的值。
    线程同步的实现方式取决于具体的需求和场景。需要根据情况选择合适的同步机制,并进行必要的测试和验证,以确保线程的安全和数据的一致性。同时,也需要注意避免死锁、活锁等问题,以保证程序的正常执行。

五、线程安全

线程安全是指在多线程环境下,对共享资源的访问操作能够正确、可靠地执行,不会出现数据不一致或不确定的结果。
在多线程编程中,线程安全是一个重要的概念,需要特别注意以下几个方面:

  1. 原子性(Atomicity): 原子操作是指一个操作是不可中断的,要么全部执行成功,要么全部不执行。例如,对于一个整型变量的自增操作,如果不是线程安全的,可能会出现多个线程同时读取并修改值,导致结果不正确。可以使用原子类(Atomic classes)或加锁(synchronized、Lock)来保证原子操作。
  2. 可见性(Visibility): 可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到该变化。使用volatile关键字可以保证变量的可见性。此外,使用synchronized或Lock机制也可以确保线程间的可见性。
  3. 有序性(Ordering): 有序性是指程序按照一定的顺序来执行。在多线程环境下,由于线程的调度和指令重排等因素,可能会导致程序执行顺序的不确定性。可以使用volatile关键字或同步机制来保证指令的有序性。
  4. 线程安全的数据结构和类:Java提供了一些线程安全的数据结构和类,如ConcurrentHashMap、CopyOnWriteArrayList等,它们在多线程环境下能够安全地进行操作,并且不需要额外的同步。
    要保证线程安全,可以采取以下几种方式:
  • 使用线程安全的数据结构和类。
  • 使用锁(synchronized、Lock)来保护共享资源的访问。
  • 使用volatile关键字来保证变量的可见性。
  • 避免使用可变的共享对象,尽量使用不可变对象。
  • 合理设计线程安全的算法和逻辑。
    需要根据具体的场景和需求选择合适的线程安全解决方案,并进行必要的测试和验证,以确保程序在多线程环境下的正确性和稳定性。

六、线程的同步机制

线程的同步机制是一种用于控制多个线程之间访问共享资源的机制,确保线程之间的正确交互和数据一致性。常见的线程同步机制包括:

  1. synchronized关键字:synchronized关键字可以用于方法和代码块。当一个线程进入synchronized修饰的方法或代码块时,它会获得对象的锁,其他试图获取锁的线程将被阻塞,直到该线程释放锁。synchronized关键字确保了同一时刻只有一个线程可以访问被锁定的代码,从而保证了线程的安全性。
    示例代码:
public synchronized void synchronizedMethod() {
    
    
    // 同步方法
}
public void method() {
    
    
    synchronized (this) {
    
    
        // 同步代码块
    }
}
  1. ReentrantLock类:ReentrantLock是Java提供的一个可重入锁,它提供了更多的灵活性和功能。与synchronized不同,ReentrantLock需要手动地进行加锁和释放锁的操作,可以更灵活地控制锁的获取和释放。它还提供了可定时的、可中断的锁等功能,使得线程同步更加灵活。
    示例代码:
ReentrantLock lock = new ReentrantLock();
public void method() {
    
    
    lock.lock();
    try {
    
    
        // 同步代码
    } finally {
    
    
        lock.unlock();
    }
}
  1. volatile关键字:volatile关键字用于修饰共享变量,保证了变量的可见性。当一个线程修改了volatile变量的值,其他线程可以立即看到该变化。然而,volatile关键字无法保证复合操作的原子性,也无法解决竞态条件问题。
    示例代码:
private volatile int sharedVariable;
public void method() {
    
    
    // 修改共享变量
    sharedVariable = 10;
    // 读取共享变量
    int value = sharedVariable;
}
  1. wait()和notify()/notifyAll()方法:wait()方法用于线程间的等待,notify()和notifyAll()方法用于线程间的唤醒。这些方法通常与synchronized关键字一起使用,用于实现线程间的协作和同步。通过wait()方法,线程可以主动释放对象的锁,并进入等待状态,直到其他线程调用notify()或notifyAll()方法来唤醒它。
    示例代码:
Object lock = new Object();
public void producer() throws InterruptedException {
    
    
    synchronized (lock) {
    
    
        // 生产数据
        lock.notify(); // 唤醒消费者线程
    }
}
public void consumer() throws InterruptedException {
    
    
    synchronized (lock) {
    
    
        lock.wait(); // 等待生产者线程唤醒
        // 消费数据
    }
}

这些线程同步机制可以根据具体的需求和场景进行选择和使用,确保线程之间的正确交互和共享资源的安全访问。同时,也需要注意避免死锁、竞态条件等问题,以确保线程的正常执行。

七、线程控制

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_48053866/article/details/131955484
今日推荐