多线程(二)

发布于:2024-05-20 ⋅ 阅读:(174) ⋅ 点赞:(0)

1.Thread提供的常用方法

 以上是Thread类提供的线程对象的构造方法以及常用方法

1.构造方法:简单的说,它可以指定封装某一个任务为一个线程,同时也可以为他起名字。

2.其中需要注意:一、run方法是runable接口的方法,实际上是线程类实现了接口,以此来描述线程的任务。二、其中的“获取当前对象”和“线程的休眠”是静态方法,其他的是实例方法。

2.线程安全

   1.什么是线程安全   

        线程安全(Thread Safety)是指在多线程环境中,一个类或对象的行为符合预期,并且不会由于多线程的并发操作导致数据不一致或状态错误。简单来说,如果一个类或对象在多线程环境中使用时,不需要额外的同步措施就能保证其操作的正确性,那么它就是线程安全的。

        线程安全问题通常发生在多个线程访问共享资源时,如果没有适当的同步机制,可能会导致数据竞争(Race Condition),从而产生不可预测的结果。

   2.怎么解决线程安全

1.线程同步

加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来(一把锁就是一个对象)

1)方式一:同步代码块(锁的范围较小)

注意:对于任何唯一的量都可以作为锁,只不过这样的锁太具有一般性,对所有的对象都是一份,比如说取钱问题,对于一家人同时取钱的安全可以保证,但是对于很多家人来说,他们用的不是一个共享账户,所以就会降低工作效率,因此,一般锁对象由共享资源承担,而共享资源在实例方法中可以用this得到当前共享资源,静态方法建议使用字节码(类名.class)对象作为锁对象

2)方式二:同步方法(锁的范围较大)

把访问共享资源的核心方法上锁

3)方式三:lock锁
  1. Lock锁时JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便你、更强大。
  2. Lock是接口,他不能直接实例化(创建对象),但是可以通过实现类ReentrantLock进行实例化
  3. 怎么使用呢?可以把锁作为每个共享资源的一个属性(用final修饰更好),若有多个进程访问同一个共享资源(核心代码前)时调用加锁(lock()),访问完调用解锁(unlock())。
 2.并发工具(了解)

并发工具是指用于帮助程序员在多线程环境中编写高效且线程安全的代码的一组工具和库。这些工具通常提供了一种更高级的抽象,使得开发者能够更容易地处理并发编程中的复杂性,如线程的创建、管理、同步和通信等问题。

在Java中,java.util.concurrent(JUC)包是提供并发工具的主要来源。以下是一些常见的并发工具:

  1. Executor框架:

    • 用于创建和管理线程池,提供了一种异步执行任务的方法。它允许你定义任务(通过实现Runnable接口或创建实现了Callable接口的对象),然后将这些任务提交给线程池执行。
  2. 同步器(Synchronizers):

    • 用于协调多个线程对共享资源的访问。例如,CountDownLatchCyclicBarrierSemaphoreExchanger等。
  3. 原子变量(Atomic Variables):

    • 提供了一种无锁的线程安全编程方式。AtomicIntegerAtomicLongAtomicReference等类使用CAS(Compare-And-Swap)操作来保证操作的原子性。
  4. 并发集合(Concurrent Collections):

    • 线程安全的集合类,如ConcurrentHashMapConcurrentLinkedQueueCopyOnWriteArrayList等,它们提供了比传统的同步集合更好的性能。
  5. 锁框架(Lock Framework):

    • 提供了比synchronized关键字更灵活的锁机制。ReentrantLockReadWriteLock等允许更细粒度的控制和更复杂的同步需求。
  6. 条件对象(Condition Objects):

    • 与锁框架一起使用,允许线程等待特定的条件成立。Condition接口提供了一种更灵活的线程间通信方式。
  7. 阻塞队列(Blocking Queues):

    • 一种特殊的队列,当队列为空时,从队列中取元素的线程会阻塞,直到队列中有元素可用。反之,当队列满时,向队列中添加元素的线程会阻塞。
  8. Future接口:

    • 表示一个可能还没有完成的异步计算的结果。通过Future对象,可以查询计算是否完成,等待计算完成,甚至取消正在执行的任务。
  9. Phaser:

    • 是一个多方面的同步辅助工具,用于协调多个线程的阶段。它比CountDownLatch更灵活,允许线程注册和注销,以及在每个阶段开始和结束时执行特定的动作。
  10. Fork/Join框架:

    • 用于并行计算的框架,它通过分而治之的方式将任务分解成更小的任务,然后在多个处理器上并行执行这些任务。

使用这些并发工具,开发者可以更高效地编写多线程程序,同时减少出错的可能性。这些工具的设计考虑了多线程环境下的性能和安全性,使得并发编程变得更加容易和可靠。

3.线程通信(了解)

线程通信是指在多线程环境中,线程之间通过某种机制来交换信息或协调工作。线程通信在并发编程中非常重要,它可以帮助线程同步它们的工作,确保数据的一致性,以及避免竞态条件等问题。

以下是一些线程通信的常见用法:

1.共享变量

线程可以通过共享变量来通信。但是,为了线程安全,通常需要使用同步机制(如`synchronized`关键字或`ReentrantLock`)来控制对共享变量的访问。

class SharedCounter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public synchronized int getCount() {
        return count;
    }
}
2.wait() 和 notify() 方法

Java中的`wait()`方法允许线程等待某个条件成立,而`notify()`和`notifyAll()`方法则用于唤醒等待的线程。这些方法必须在同步块或同步方法中使用。

public class BoundedBuffer {
    private final int capacity;
    private int count = 0;
    private final List<Integer> buffer;

    public BoundedBuffer(int capacity) {
        this.capacity = capacity;
        this.buffer = new ArrayList<>(capacity);
    }

    public synchronized void put(Integer x) throws InterruptedException {
        while (count == capacity) {
            wait(); // 缓冲区满,生产者线程等待
        }
        buffer.add(x);
        count++;
        notifyAll(); // 唤醒其他线程
    }

    public synchronized Integer take() throws InterruptedException {
        while (count == 0) {
            wait(); // 缓冲区空,消费者线程等待
        }
        int x = buffer.remove(0);
        count--;
        notifyAll(); // 唤醒其他线程
        return x;
    }
}
3.BlockingQueue

BlockingQueue是一个线程安全的队列,它提供了内置的线程通信机制。当队列为空时,从队列中取元素的线程将被阻塞,直到队列中有元素可用;当队列满时,向队列中添加元素的线程将被阻塞。

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

// 生产者线程
new Thread(() -> {
    try {
        queue.put(1); // 将元素放入队列
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

// 消费者线程
new Thread(() -> {
    try {
        Integer element = queue.take(); // 从队列中取出元素
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();
4.CountDownLatch

`CountDownLatch`允许一个或多个线程等待一组操作在其他线程中完成。通过`countDown()`方法递减计数器,当计数器达到0时,所有等待的线程将继续执行。

CountDownLatch latch = new CountDownLatch(1);

new Thread(() -> {
    // 执行一些操作
    latch.countDown(); // 操作完成,减少计数器
}).start();

latch.await(); // 主线程等待,直到计数器达到0
5.CyclicBarrier

`CyclicBarrier`允许一组线程相互等待,直到所有线程都到达一个公共屏障点。之后,这些线程可以继续执行。

CyclicBarrier barrier = new CyclicBarrier(2);

new Thread(() -> {
    // 执行一些操作
    try {
        barrier.await(); // 等待其他线程到达屏障点
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    // 所有线程都到达屏障点后执行的操作
}).start();

new Thread(() -> {
    // 执行一些操作
    try {
        barrier.await(); // 等待其他线程到达屏障点
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    // 所有线程都到达屏障点后执行的操作
}).start();
6.Exchanger

`Exchanger`用于两个线程之间的数据交换。每个线程调用`exchange()`方法,传入一个对象,然后等待另一个线程到达并交换对象。

Exchanger<String> exchanger = new Exchanger<>();

new Thread(() -> {
    try {
        String exchanged = exchanger.exchange("Hello"); // 交换数据
        System.out.println("Received: " + exchanged);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

new Thread(() -> {
    try {
        String exchanged = exchanger.exchange("World"); // 交换数据
        System.out.println("Received: " + exchanged);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

通过这些线程通信机制,开发者可以构建复杂的多线程应用程序,同时确保线程间的协调和数据的一致性。 

4.线程池

  1.先来了解什么是线程池

线程池是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要目的是减少在创建和销毁线程时所产生的性能开销。通过重用已经创建的线程来执行新的任务,线程池提高了程序的响应速度,并且提供了更好的系统资源管理。

线程池的主要优点1.提高性能:避免频繁地创建和销毁进程  2.控制资源:控制最大并发数,避免因大量线程竞争资源二导致的系统资源不足  3.提高稳定性:可以避免过多线程因争用资源而导致的系统崩溃

  2.线程池的工作原理

 以下是线程池的工作原理解释:

线程池的工作原理可以概括为以下几个步骤:

  1. 任务提交: 当一个任务被提交给线程池时,线程池会根据当前的线程数量和任务队列的状态来决定是直接执行该任务,还是将其放入任务队列中等待。

  2. 任务队列: 如果线程池中的线程数小于核心线程数,且没有空闲线程,则线程池会创建一个新线程来执行任务。否则,任务会被放入任务队列中等待。

  3. 线程复用: 线程执行完一个任务后,不会销毁,而是继续从任务队列中获取下一个任务执行。

  4. 线程创建: 如果任务队列已满,且线程数小于最大线程数,则线程池会创建新线程来执行任务。

  5. 拒绝策略: 如果任务太多,任务队列满了,且线程池中的线程数已经达到最大值,线程池将根据预设的拒绝策略来处理新提交的任务。常见的拒绝策略包括丢弃任务、运行任务、抛出异常等。

  6. 线程终止: 当线程池中的线程都空闲了一定时间(即保持空闲状态的时间超过了线程池的keep-alive时间)后,线程池会逐步销毁多余的空闲线程。

  7. 关闭线程池: 当调用线程池的shutdown()shutdownNow()方法时,线程池会停止接受新任务,并且会等待已提交的任务完成。shutdownNow()还会尝试立即停止所有正在执行的任务,并返回等待执行的任务列表。

  3.关于线程的名词解释(基本)

  1. 线程(Thread):

    • 线程是程序执行的最小单元,是操作系统能够进行运算调度的最小单位。
  2. 进程(Process):

    • 进程是计算机中的程序关于数据集合上的一次运行活动,线程是进程中的一个实体,是CPU调度和分派的基本单位。
  3. 主线程(Main Thread):

    • 在程序启动时创建的第一个线程,通常用于执行程序的入口点(main方法)。
  4. 核心线程(Core Thread):

    • 线程池中始终存活的线程数量,即使它们处于空闲状态,也不会被终止。
  5. 最大线程(Maximum Thread):

    • 线程池中允许的最大线程数量。当任务队列满了之后,线程池会尝试创建新的线程,直到达到这个数量。
  6. 空闲线程(Idle Thread):

    • 当没有任务执行时,处于等待状态的线程。
  7. 并发(Concurrency):

    • 多个线程同时进行(同时启动),但不一定同时执行
  8. 并行(Parallelism):

    • 多个线程同时执行,通常需要多核处理器支持。
  9. 同步(Synchronization):

    • 控制多个线程对共享资源的访问,以保证数据一致性和线程安全。
  10. 互斥(Mutex):

    • 一种同步机制,确保一次只有一个线程可以访问特定的代码段。
  11. 死锁(Deadlock):

    • 多个线程在相互等待对方释放资源,导致程序无法继续执行的状态。
  12. 竞态条件(Race Condition):

    • 当多个线程访问共享资源时,程序的输出依赖于线程的执行顺序,这可能导致不可预测的结果。
  13. 原子操作(Atomic Operation):

    • 不可中断的操作,要么完全执行,要么完全不执行,中间不会有其他线程的介入。
  14. 线程安全(Thread Safety):

    • 指在多线程环境中,代码不会因为线程的执行顺序或交叉执行而产生错误。
  15. 线程局部变量(Thread Local Variable):

    • 每个线程都有自己的独立副本的变量,互不影响。
  16. 线程中断(Thread Interrupt):

    • 一个线程可以通过调用另一个线程的interrupt()方法来请求另一个线程终止当前正在执行的任务。
  17. 任务队列(Task Queue):

    • 线程池中用于存放待执行任务的队列。

网站公告

今日签到

点亮在社区的每一天
去签到