当前位置: 首页 > news >正文

建网站的公司浩森宇特桂林北京网站建设

建网站的公司浩森宇特,桂林北京网站建设,上海网页设计公司山东济南兴田德润简介,深圳搜索优化排名公司JavaEE#xff1a;多线程初阶 一、线程的原理和进程与线程之间的关系1. 线程的原理线程的基本概念线程的生命周期线程的调度线程的并发与并行 2. 进程与线程的关系进程#xff08;Process#xff09;线程与进程的关系进程和线程的对比线程的优势线程的缺点 3. 总结 二、多线… JavaEE多线程初阶 一、线程的原理和进程与线程之间的关系1. 线程的原理线程的基本概念线程的生命周期线程的调度线程的并发与并行 2. 进程与线程的关系进程Process线程与进程的关系进程和线程的对比线程的优势线程的缺点 3. 总结 二、多线程的使用和 Thread 类的用法1. 使用 Thread 类来创建线程1.1 通过继承 Thread 类1.2 Thread 类常见的方法1.3 Thread 类其他方法 2. 使用 Runnable 接口实现线程2.1 Runnable 接口的使用 3. 线程的控制3.1 线程的休眠sleep()3.2 线程的通知和等待wait() notify() notifyAll()3.3 线程的中断interrupt() 4. 总结 三、线程安全问题及解决方式1. 线程安全问题的产生原因1.1 竞态条件Race Condition1.2 可见性问题1.3 死锁Deadlock 2. 如何解决线程安全问题2.1 使用同步synchronized2.2 使用 volatile 关键字2.3 使用 java.util.concurrent 包2.4. 避免死锁 3. 总结 四、如何理解多线程等待通知机制1. 等待通知机制的基本概念2. 如何理解等待通知机制3. 等待通知机制的使用示例3.1 生产者-消费者问题3.2 wait() 和 notify() 方法的工作机制3.3 注意事项 4. wait()、notify() 和 notifyAll() 的区别5. 总结 五、多线程的代码案例单例模式1. 单例模式的应用场景2. 单例模式的实现方法3. 单例模式的优缺点4. 什么时候使用单例模式5. 总结 六、多线程的代码案例阻塞队列1. 阻塞队列的特点2. 常用的阻塞队列实现3. 代码示例使用 ArrayBlockingQueue 实现生产者-消费者模型 七、多线程的代码案例线程池1. 线程池的优势2. Java 中的线程池实现3. 参数说明4. 代码示例手动模拟实现线程池执行任务5. 代码分析 八、多线程的代码案例定时器1. 使用 Timer 和 TimerTask 实现定时任务2. 使用 ScheduledExecutorService 实现定时任务3. 手动模拟实现一个定时器4. 总结 一、线程的原理和进程与线程之间的关系 在Java中线程是执行程序中的最小单元线程的管理和调度是由操作系统的内核和Java虚拟机JVM共同负责的。理解线程的原理和进程与线程的关系对于开发高效、线程安全的程序至关重要。 1. 线程的原理 线程的基本概念 线程Thread是计算机中的基本执行单位。 每个进程都有多个线程每个线程都共享同一个进程中的资源如内存文件句柄等。每个线程都有自己的程序计时器Program Counter栈Stack和局部变量但它们之间都共享堆Heap等资源。 线程的生命周期 一个线程在其生命周期中经历以下几个状态 新建状态New当线程对象被创建时线程处于新建状态。就绪状态Runnable当线程被启动并等待操作系统的调度时它处于就绪状态。请注意Java中的“就绪状态”是指线程已经准备好执行但可能因为操作系统调度的原因尚未获得 CPU 时间。运行状态Running当线程获得了 CPU 时间并开始执行时线程进入运行状态。阻塞状态Blocked如果线程因为等待某些资源如 I/O 操作或锁而无法继续执行它将进入阻塞状态。等待状态Waiting线程进入等待状态时它正在等待其他线程的某些操作或事件如某个线程调用 Thread.join() 或通过 Object.wait() 等方式。超时等待状态Timed Waiting线程进入超时等待状态是指它会在指定的时间内等待常见的方法包括 Thread.sleep()、Object.wait(long timeout) 等。死亡状态Terminated线程在执行完毕或者因异常等原因终止时进入死亡状态。 线程的调度 Java的线程调度都是基于线程抢占式调度的。 这意味着当前执行的线程可以被操作系统抢占并暂停然后由操作系统调度其他线程执行。Java的线程调度器依赖于操作系统的算法调度通常是基于优先级的调度。在大多数操作系统中。 线程的并发与并行 并发指的是多个线程在同一个时段共享CPU资。即在一个时段内多个线程同时执行。操作系统通过快速切换线程来实现并发。并行指的是多个线程在同一时刻由多个处理器或核心同时执行。对于拥有多核 CPU 的机器操作系统可以将多个线程分配给不同的核心来实现并行。 2. 进程与线程的关系 进程Process 进程是程序的一次执行实例。每个进程都有自己的地址空间、数据栈和其他用于执行的辅助数据。进程是资源分配和调度的基本单位。每个进程可以包含一个或多个线程。 线程与进程的关系 独立性线程是进程中的执行单元线程的执行不依赖于其他线程的执行。一个进程中的多个线程共享进程的资源如内存空间、文件描述符等。资源分配操作系统为每个进程分配资源如内存、CPU 时间等。而线程是共享这些资源的多个线程可以访问相同的内存区域。创建与销毁创建一个进程比创建一个线程更加昂贵。因为创建进程需要独立的内存空间和系统资源。而创建线程分配少量的控制资源栈和程序计数器。执行速度线程的创建、销毁和上下文切换的开销比进程小得多因此在需要大量并发任务时线程相较于进程更为高效。 进程和线程的对比 特性进程线程内存空间独立的内存空间共享进程的内存空间资源分配每个进程有自己的资源多个线程共享进程的资源创建与销毁开销创建和销毁开销较大创建和销毁开销较小调度与切换开销进程上下文切换开销较大线程上下文切换开销较小并发性每个进程并行执行线程并行执行依赖于多核 CPU异常隔离一个进程内的异常不会影响其他进程线程间的异常会影响其他线程 线程的优势 线程比进程更轻量创建和销毁的速度更快。线程之间可以共享进程的资源如内存从而降低了资源的消耗和管理复杂性。在多核 CPU 上线程可以并行执行从而充分利用硬件性能。 线程的缺点 由于线程共享进程的内存空间线程间的通信和同步比较复杂容易引发线程安全问题。多线程程序容易出现死锁、竞态条件等并发问题需要使用锁、条件变量等技术来保证线程安全。 3. 总结 线程是程序的执行单元一个进程可以拥有多个线程多个线程之间共享进程的资源。线程的创建和销毁相对比进程轻量但也带来了一些同步和资源管理的问题。理解进程和线程之间的关系及其各自的特点能帮助开发者更好地利用并发和并行提高程序的性能和响应速度。 二、多线程的使用和 Thread 类的用法 在Java中线程实现的方式有很多用 Thread 类来实现是最直接的方式。Thread 类是 java.lang 包中的一个类它提供了创建和控制线程的基本功能。通过继承 Thread 类或实现 Runnable 接口可以在 Java 程序中创建和使用线程。 1. 使用 Thread 类来创建线程 1.1 通过继承 Thread 类 创建一个自定义的线程类通过继承 Thread 类并重写 run 方法。在 run 方法中定义线程的执行逻辑。然后创建该类的实例并调用 start 方法来启动线程。 示例通过继承 Thread 类创建线程 class MyThread extends Thread {Overridepublic void run() {for (int i 0; i 1000; i) {System.out.println(Thread.currentThread().getName() - i);}} } public class Demo1 {public static void main(String[] args) {// 创建并启动线程Thread t1 new MyThread();Thread t2 new MyThread();t1.start();t2.start();} }解释 MyThread 类继承了 Thread 类并重写了 run 方法定义了线程的任务。调用 t1.start() 和 t2.start() 来启动线程这会使线程进入 就绪 状态等待 操作系统分别 CPU 时间片来执行。 1.2 Thread 类常见的方法 start() 启动线程调用 start() 方法后操作系统会调度执行 run() 方法。每个线程只能调度一次 start() 方法。run() 线程执行的任务方法通常重写来定义线程的具体的执行逻辑。run() 方法不会直接执行需要通过 start() 方法来启动线程。sleep(long millis) 使当前线程暂停的指定的毫秒数。它会使当前线程进入 休眠 状态等待指定时间过去就自动恢复 就绪 状态。currentThread() 返回当前线程的执行对象。getName() 返回线程的名称每个线程都有名称默认名称是 Thread-0 Thread-1 等。isAlive() 判断线程是否已启动并处于活动状态。 1.3 Thread 类其他方法 join() 使当前线程等待调用该方法的线程执行完毕。join() 用于线程间的协调和同步。确保某个线程在执行之前等待其他线程执行完毕。interrupt() 中断线程的执行。线程会抛出 InterruptException 异常。当线程处于休眠或等待的状态调用 Interrupt() 方法抛出该异常并终止执行。 2. 使用 Runnable 接口实现线程 虽然继承 Thread 类创建线程比较简单但在实际开发中通常实现 Runnable 接口来创建线程。因为 Java 是单继承的继承 Thread 类就不能再继承其他类了而实现 Runnable 接口的同时还可以继承其他类。 2.1 Runnable 接口的使用 Runnable 接口只有一个方法 run() 需要在该方法中定义线程的执行逻辑。实现 Runnable 接口时需要创建一个 Thread 对象并将实现了 Runnable 接口的实例传递给 Thread 构造方法。 class MyRunnable implements Runnable {Overridepublic void run() {for (int i 0; i 1000; i) {System.out.println(Thread.currentThread().getName() - i);}} } public class Demo2 {public static void main(String[] args) {MyRunnable myRunnable new MyRunnable();// 创建线程并启动Thread t1 new Thread(myRunnable);Thread t2 new Thread(myRunnable);t1.start();t2.start();} }解释 MyRunnable 类实现了 Runnable 接口并重写了 run() 方法定义了线程的任务。创建了 Runnable 对象 myRunnable并将其传递给 Thread 的构造函数来创建线程。调用 t1.start() 和 t2.start() 启动线程。 3. 线程的控制 3.1 线程的休眠sleep() Thread.sleep(long millis) 可以让当前线程暂停执行的毫秒数。在这段时间内线程会进入休眠状态不会参与 CPU 资源的调度直到指定时间后才被唤醒。 示例线程休眠 class SleepThread extends Thread {Overridepublic void run() {for (int i 0; i 1000; i) {try {// 休眠 1 秒Thread.sleep(1000);System.out.println(Thread.currentThread().getName() - i);} catch (InterruptedException e) {e.printStackTrace();}}} } public class Demo3 {public static void main(String[] args) {SleepThread sleepThread new SleepThread();sleepThread.start();} }解释 每次循环线程都会休眠 1秒1000ms打印当前线程的名称和循环计数。休眠期间线程不会参与 CPU 资源的调度。 3.2 线程的通知和等待wait() notify() notifyAll() 线程之间的协调可以通过 Object 类中的 wait() notify() notifyAll() 方法实现。通过这些方法可以实现线程之间的通信和同步。 wait() 方法使当前线程等待直到被其他线程唤醒。notify() 方法唤醒一个正在等待的线程。notifyAll() 方法唤醒所有正在等待的线程。 这些方法需要在同步代码块中使用与 synchronized 关键字一起使用确保线程安全。 3.3 线程的中断interrupt() 通过 Thread.interrupt() 方法可以请求线程线程中断。通常线程在执行过程中会检查自身的中断状态并决定是否停止执行。中断通常用于需要及时停止长时间运行的任务。 示例线程中断 class InterruptThread extends Thread {Overridepublic void run() {for (int i 0; i 10; i) {if (Thread.interrupted()) {System.out.println(Thread.currentThread().getName() was interrupt);break;}System.out.println(Thread.currentThread().getName() - i);}} } public class Demo4 {public static void main(String[] args) throws InterruptedException {InterruptThread interruptThread new InterruptThread();interruptThread.start();Thread.sleep(1000);// 中断线程interruptThread.interrupt();} }解释 Thread.interrupted() 检查线程是否已经终止。调用 interrupt() 后线程会在执行过程中通过检查 interrupted() 方法决定是否终止。 4. 总结 Thread 类提供了直接创建和控制线程的方法包括 start()、sleep()、join()、interrupt() 等。使用 Runnable 接口可以避免继承 Thread 带来的局限允许一个类实现多个接口并同时控制线程的执行。线程的执行、控制、等待和中断都可以通过相应的方法进行管理以实现高效的并发编程。 三、线程安全问题及解决方式 在Java中线程安全指多个线程在并发执行的情况下能够正确地访问和操作共享资源不会导致数据不一致出现异常或程序奔溃。线程安全问题通常出现在多个线程同时访问或修改共享资源时尤其是当资源的状态依赖于多个操作的组合时。为了保证线程安全问题确保线程间对共享资源的访问是有序的避免竞争条件和死锁等问题。 1. 线程安全问题的产生原因 1.1 竞态条件Race Condition 竞态条件是多线程程序中一个常见的问题。当多个线程并发访问或修改共享资源时如果线程间的执行顺序不确定时可能会导致不正确的结果。例如多个线程同时读取修改共享资源如果没有同步机制来保证数据的一致性就会出现竞态条件。 示例竞态条件 class Counter {private static int count 0;public void increment() {count; // 非原子的操作}public int getCount() {return count;} } public class Demo5 {public static void main(String[] args) throws InterruptedException {Counter counter new Counter();// 创建两个线程分别执行 increment 操作Thread t1 new Thread(() - {for (int i 0; i 50000; i) {counter.increment();}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {counter.increment();}});t1.start();t2.start();t1.join();t2.join();// 理论上是 10w 的System.out.println(count: counter.getCount()); // count: 53131} }解释 在这个例子中 counter.increment() 方法不是一个原子操作它包括读取 count 在内存中对 count 进行 1 操作在寄存器中然后再读回 count 中内存中。如果两个线程同时执行这个操作就可能会发生竞态条件导致预期的值小于 10w 。例如两个线程都读取了相同的 count 值增加后写回。 1.2 可见性问题 当多个线程访问共享资源时其中一个线程的修改可能不会立即对其他线程可见。这是因为线程会缓存变量的值或写入寄存器中导致其他线程没法看到最新的值。 示例可见性问题 class SharedData {private static boolean flag false;// 写操作public void write() {flag true;}// 读操作public void read() {if (flag) {System.out.println(Flag is true); // Flag is true} else {System.out.println(Flag is false);}} } public class Demo6 {public static void main(String[] args) throws InterruptedException {SharedData data new SharedData();Thread t1 new Thread(data::write);Thread t2 new Thread(data::read);t1.start();t2.start();t1.join();t2.join();} }解释 在这个例子中可能 t2 线程在 t1 线程完成 write() 操作时就已经执行 read() 操作就会导致 t2 线程看到 flag 还是久值而并不是新值。这个问题时线程缓存和优化导致不可见问题。 1.3 死锁Deadlock 死锁发生在多个线程因相互等待对方持有的资源而无法继续执行时。死锁会导致线程无法完成任务从而使整个程序陷入僵局。 示例死锁 class A {synchronized void methodB(B b) {System.out.println(Thread 1: Locking A and calling B); // 锁定 A 并呼叫 Bb.last(); // 尝试获取 B 中的锁}synchronized void last() {System.out.println(In As last method);} } class B {synchronized void methodA(A a) {System.out.println(Thread 2: Locking B and calling A); // 锁定 B 并呼叫 Aa.last(); // 尝试获取 A 中的锁}synchronized void last() {System.out.println(In Bs last method);} } public class Demo7 {public static void main(String[] args) {final A a new A();final B b new B();Thread t1 new Thread(() - {a.methodB(b);});Thread t2 new Thread(() - {b.methodA(a);});t1.start();t2.start();} }解释 在这个示例中t1 线程获取 A 对象的锁并尝试获取 B 对象的锁而 t2 线程获取 B 对象的锁并尝试获取 A 线程对象的锁。两个线程都在等待对方释放锁导致死锁程序无法继续执行。 2. 如何解决线程安全问题 2.1 使用同步synchronized Java提供了 synchronized 关键字确保只有一个线程能够访问某个共享资源。通过方法或代码块上使用 synchronized 关键字可以防止多个线程在执行同一段代码时从而避免竞态条件。 示例使用 synchronized 防止竞态条件 class Counter {private int count 0;public synchronized void increment() {count; // 原子操作}public int getCount() {return count;} }解释 在 increment() 方法上使用 synchronized 可以确保在同一时刻只有一个线程可以执行此方法避免了竞态条件。 2.2 使用 volatile 关键字 volatile 关键字用于保证变量在多个线程间的可见性。当一个线程修改了 volatile 变量时其他线程能够立即看到这个修改而不必等到线程退出同步块。 示例使用 volatile 保证可见性 class SharedData {private volatile boolean flag false;public void write() {flag true;}public void read() {if (flag) {System.out.println(Flag is true);} else {System.out.println(Flag is false);}} }解释 使用 volatile 关键字确保 flag 变量的值对于所有线程都是可见的避免了可见性问题。 2.3 使用 java.util.concurrent 包 Java 提供了并发工具类这些类在设计的时候考虑到了线程安全问题并且能够更加简洁的可靠的解决线程安全问题。 ReentrantLock 比 synchronized 关键字提供的锁定机制更加灵活例如可以中断锁或定时锁。Atomic 类AtomicInteger AtomicBoolean 等类提供对基本数据类型的原子操作确保并发访问不会出现竞态条件。CountDownLatch、CyclicBarrier 等同步工具类它们提供了线程间的协调确保一定的执行顺序。 示例使用 AtomicInteger import java.util.concurrent.atomic.AtomicInteger;class Counter {private AtomicInteger count new AtomicInteger(0);public void increment() {// count;count.incrementAndGet(); // 原子操作}public int getCount() {return count.get();} }解释 AtomicInteger 提供了对整数类型的原子操作确保在多线程条件下对 count 的增减操作是线程安全的。 2.4. 避免死锁 避免嵌套锁避免多个线程持有多个锁避免多个线程相互等待。使用锁的顺序多个线程请求锁时始终按照相同的顺序获取锁避免循环等待。使用 tryLock ReentrantLock 中的 tryLock 方法尝试获取锁时可以避免死锁。 3. 总结 线程安全问题包括竞态条件可见性问题和死锁等问题为了解决这些问题Java 提供了一下几种同步机制。 synchronized 关键字为了解决线程同步性问题。volatile 关键字为了解决线程可见性问题。使用 java.util.concurrent 包中的工具类如 Atomic 类和 ReentrantLock 来简化线程安全问题。设计时要尽量避免死锁通过适当的锁管理和资源访问顺序来避免死锁的发生。 四、如何理解多线程等待通知机制 在 Java 中等待通知机制Wait-Notify Mechanism是多线程编程中的一种重要技术用于协调多个多个线程的执行顺序。当多个线程在并发环境下操作共享资源时某些线程只能等待其他线程完成任务时才能继续执行。 通过 Object 类中的 wait notify notifyAll 等方法Java 提供了线程间的协调机制。 1. 等待通知机制的基本概念 等待通知机制本质上就是通过共享对象同步线程的执行状态。某些线程可能需要满足条件才能继续执行等待而其他线程在满足条件后唤醒等待的线程通知。这种机制通常解决生产者-消费者模型等场景。 wait() 让当前线程进入等待状态直到其他线程使用 notify 或 notifyAll 唤醒当前线程。该方法必须在同步代码块中使用synchronized。notify() 唤醒一个正在等待该对象锁的线程。被唤醒的线程再次尝试获取锁继续执行。notifyAll() 唤醒所有正在等待该对象锁的线程。所有被唤醒的线程再次尝试获取锁哪个线程获取到锁对象哪个线程就继续执行。 2. 如何理解等待通知机制 线程和锁线程和对象是紧密相关的线程只有获取到锁对象才能执行同步代码块即 synchronized 块或同步方法。否则阻塞等待直到获取到锁对象才能继续执行。等待和通知的关系当一个线程执行到 wait() 时就会释放锁并进入等待队列直到其他线程调用 notify() 或 notifyAll() 。notify() 或 notifyAll() 唤醒的是等待该锁对象的线程并且这些线程只有获取到锁才能够继续执行。条件变量等待通知机制核心思想是根据某种条件决定是否继续执行。通常线程会在一个条件变量上等待该条件变量表示某种资源已经准备好。例如生产者线程等待缓冲区有空位消费者线程等待缓冲区有商品。单个线程和多个线程使用 notify 唤醒一个正在等待的线程而 notifyAll 则唤醒所有等待的线程,但是不是所有线程都被唤醒只有它们在获取到锁的时候才能继续执行。 3. 等待通知机制的使用示例 3.1 生产者-消费者问题 这是等待通知机制的经典案例。假设有一个缓冲区生产者线程将写入数据到缓冲区而消费者线程则从缓冲区拿数据。生产者线程需要等到缓冲区中有空位才能写入数据而消费者线程需要等到缓冲区中有数据才能消费。 示例生产者-消费者模型 class SharedBuffer {private int[] buffer new int[10]; // 缓冲区private int count 0; // 当前缓冲区的数据量// 生产者线程public synchronized void produce(int value) throws InterruptedException {// 缓冲区数据量满了生产者线程就阻塞等待if (count buffer.length) {wait();}// 向缓冲区添加数据buffer[count] value;System.out.println(produce: value);// 唤醒消费者线程notify();}// 消费者线程public synchronized int consumer() throws InterruptedException {// 缓冲区数据量为 0等待生产者生产if (count 0) {wait();}// 从缓冲区取出数据int value buffer[--count];System.out.println(consumer: value);// 唤醒生产者线程notify();return value;} } public class Demo8 {public static void main(String[] args) throws InterruptedException {SharedBuffer buffer new SharedBuffer();// 生产者-消费者模型需要两个线程来实现// 生产者线程Thread producer new Thread(() - {for (int i 0; i 100; i) {try {buffer.produce(i);Thread.sleep(1000); // 模拟生产时间} catch (InterruptedException e) {e.printStackTrace();}}});// 消费者线程Thread consumer new Thread(() - {for (int i 0; i 150; i) {try {buffer.consumer();Thread.sleep(1500); // 模拟消费时间} catch (InterruptedException e) {e.printStackTrace();}}});producer.start();consumer.start();producer.join();consumer.join();} }解释 SharedBuffer 类中包含一个缓冲区和一个计数器 count用来记录缓冲区中的元素个数。生产者线程在缓冲区满时时调用 wait() 等待消费者线程消费数据而消费者线程在缓冲区空时会调用 wait() 方法等待生产者线程生产数据。每当生产者线程生产数据时会调用 notify() 唤醒消费者消费数据而当消费者线程消费数据时也会调用 notify() 也会唤醒生产者线程生产数据。wait() 或 notify() 必须在 synchronized 方法或代码块中使用因为它们依赖于对象的锁。 3.2 wait() 和 notify() 方法的工作机制 wait() 方法使当前线程进入等待队列释放当前持有的锁直到其它线程唤醒。调用 wait() 方法不会继续执行下去直到被等待队列解除。notify() 方法从等待队列唤醒唤醒一个线程并使其重新获得锁对象。一旦获得锁对象就会继续执行下去。notifyAll() 方法唤醒所有在当前监视器锁等待的线程所有被唤醒的锁同时竞争锁。 3.3 注意事项 wait() 和 notify() 必须在同步方法或同步代码块中使用因为它们依赖于锁的机制假如没有同步代码块的保障它们的行为无法被预测。notify() 唤醒的线程并不意味着立即执行它只是将线程从等待队列中移除线程会再次竞争锁资源。被唤醒的线程只有先获取到锁才能继续执行。线程的顺序不可预测使用 notify() 或 notifyAll() 时被唤醒的线程获得锁的顺序是由 JVM 和操作系统决定的。因此避免依赖于线程的唤醒顺序。 4. wait()、notify() 和 notifyAll() 的区别 wait() 让当前线程进入等待状态直到被其他线程通知唤醒。释放锁并进入等待队列。notify() 唤醒一个在该对象锁等待的线程。notifyAll() 唤醒所有在该对象锁等待的线程。 notify() 和 notifyAll() 的选择 notify() 适用于一个线程状态改变足以唤醒另一个线程。例如生产者-消费者模型一个生产元素一个就消费元素。notifyAll() 适用于多个线程等待同一个条件下尤其是在多线程相互依赖的场景下。尤其是在多线程相互依赖的场景下。 5. 总结 等待通知机制在 Java 中是一种强大的线程同步手段用于解决多线程协调的问题。通过 wait()、notify() 和 notifyAll() 方法可以在线程之间传递控制信号保证线程按照特定顺序执行。它通常用于处理如生产者-消费者问题、线程池的工作队列等场景。 五、多线程的代码案例单例模式 1. 单例模式的应用场景 单例模式是一种设计模式确保一个类只有一个实例并提供一个全局访问点。在多线程环境下单例模式的应用场景包括 配置管理确保配置信息只有一个实例方便管理和访问。日志记录确保日志记录器只有一个实例避免日志混乱。数据库连接池管理数据库连接确保连接池只有一个实例提高效率。缓存确保缓存只有一个实例方便数据共享和一致性维护。 2. 单例模式的实现方法 在Java中单例模式有多种实现方法特别是在多线程环境下需要考虑线程安全的问题。常见的实现方法包括 懒汉式线程不安全 public class Singleton {private static Singleton instance;private Singleton() {}public Singleton getInstance() {if (instance null) {instance new Singleton();}return instance;} }这种实现方式在多线程环境下是不安全的因为多个线程可能同时进入if (instance null)判断导致创建多个实例。 懒汉式线程安全 public class Singleton {private static Singleton instance;private Singleton() {}public synchronized Singleton getInstance() {if (instance null) {instance new Singleton();}return instance;} }通过在getInstance()方法上添加synchronized关键字确保线程安全但会导致性能下降因为每次调用getInstance()都会进行同步。 饿汉式Eager Initialization public class Singleton {private static final Singleton instance new Singleton();//构造方法防止 new 一个对象实例化对象private Singleton() {}//获取到一个Singletonpublic static Singleton getInstance() {return instance;} }这种实现方式在类加载时就创建实例确保线程安全但可能会提前占用资源。 静态内部类Static Inner Class public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton instance new Singleton();}public static Singleton getInstance() {return SingletonHolder.instance;} }这种实现方式利用类加载机制确保线程安全且延迟加载性能较好。 双重检查锁Double-Checked Locking public class Singleton {private static volatile Singleton instance;private Singleton() {}public Singleton getInstance() {if (instance null) {synchronized (Singleton.class) {if (instance null) {instance new Singleton();}}}return instance;} }解释 volatile 关键字实例变量为 private static volatile Singleton instance; volatile 关键字确保该变量的更改对所有线程都是可见的防止与缓存或指令重排序等问题。getInstance() 方法 第一次检查该方法首先在不获取锁的情况下检查 instance 是否为 null 。如果不为 null 就直接返回现有实例避免进入同步代码块。同步同步块如果 instance 为 null 时就进入 Singleton.class 同步代码块。确保只有一个线程可以继续创建实例。第二次检查在同步代码块内再次判断 instance 是否为 null 这是为了防止其他线程创建完实例后再次创建实例。 3. 单例模式的优缺点 优点 全局唯一实例确保一个类只有一个实例避免资源浪费。控制资源访问通过单例实例控制对资源的访问方便管理。简化对象管理不需要关心对象的创建和销毁只需要通过全局访问点获取实例。 缺点 隐藏的通信机制单例模式可能隐藏了对象之间的通信机制增加系统复杂性。测试困难单例模式可能导致测试困难因为实例是全局的难以进行单元测试。潜在的性能瓶颈如果单例类成为性能瓶颈可能需要重新设计系统。 4. 什么时候使用单例模式 当系统中某个类只需要一个实例而且客户端可以随时获取该实例时。当需要全局访问某个对象并且该对象的状态需要保持一致时。当需要控制资源的使用例如数据库连接池、线程池等。当某个类的实例化成本较高需要复用该实例时。 5. 总结 单例模式是一种常用的设计模式确保一个类只有一个实例并提供一个全局访问点。在多线程环境下需要特别注意线程安全问题选择合适的实现方法。常见的实现方法包括懒汉式、饿汉式、双重检查锁和静态内部类等。根据具体需求和场景选择合适的单例模式实现方式可以有效提高系统的性能和可靠性。 六、多线程的代码案例阻塞队列 在 Java 多线程编程中阻塞队列BlockingQueue 是一种线程安全的数据结构。常用于实现生产者-消费者模型。 1. 阻塞队列的特点 线程安全阻塞队列内部实现了线程同步确保了多个线程可以安全地进行入队列和出队列操作。阻塞特征当队列已经满了再试图添加元素到队列中就会发生阻塞直到有空间可用。当队列为空时尝试从队列中取出元素就会发生阻塞直到队列中直到有新的元素添加。 2. 常用的阻塞队列实现 ArrayBlockingQueue 基于数组的有界阻塞队列必须指定容量。LinkedBlockingQueue 基于链表的阻塞队列默认情况下容量为 Integer.MAX_VALUE 也可以指定容量。PriorityBlockingQueue 支持元素优先级的无界阻塞队列。 3. 代码示例使用 ArrayBlockingQueue 实现生产者-消费者模型 代码示例 public class Demo10 {private static final int QUEUE_CAPACITY 5; // 阻塞队列中的容纳的量private static final ArrayBlockingQueueInteger queue new ArrayBlockingQueueInteger(QUEUE_CAPACITY ); // 阻塞队列public static void main(String[] args) {Thread producer new Thread(new producer());Thread consumer new Thread(new consumer());// 启动线程producer.start();consumer.start();}// 生产者线程static class producer implements Runnable {Overridepublic void run() {int value 0; // 数据量try {while (true) {System.out.println(生产者准备生产数据~);queue.put(value);System.out.println(生产者生产数据 value);value;Thread.sleep(1000); // 模拟生产过程中的耗时操作}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}// 消费者线程static class consumer implements Runnable {Overridepublic void run() {try {while (true) {System.out.println(消费者准备消费数据~);Integer value queue.take();System.out.println(消费者消费数据 value);Thread.sleep(1500); // 模拟消费过程中耗时操作}} catch (InterruptedException e) {}}} }代码分析 阻塞队列的初始化ArrayBlockingQueue 被初始化为固定容量的队列用于生产者消费者之间的传递。生产者线程不断的生产整数添加到队列中。使用 put() 方法在队列满时就会产生阻塞直到有空间可用。消费者线程不断地从队列中取出整数并处理。使用 take() 方法时在队列空时也会产生阻塞直到有元素在对队列中出现。线程休眠Thread.sleep 模拟线程生产和消费地耗时操作。使生产和消费的速度不同体现出阻塞队列调节作用。 通过阻塞队列生产者和消费者可以在不同步的情况下进行协作生产者无需等待消费者处理完毕即可继续生产消费者也无需等待生产者提供数据即可继续消费从而提高系统的并发性能和资源利用率。 此外阻塞队列在实现生产者-消费者模型时具有以下优势 解耦合 生产者和消费者通过阻塞队列来进行通信彼此不直接产生依赖降低系统的耦合度。削峰填谷 在高并发的场景下阻塞队列充当缓冲区平衡生产者和消费者的速度避免系统过载。 七、多线程的代码案例线程池 在 Java 多线程编程中线程池是一种用于管理和复用线程的机制。能够有效地控制并发线程数量降低系统消耗提升执行效率。 1. 线程池的优势 降低资源消耗 通过重复利用已创建的线程减少频繁的创建和销毁线程所带来的开销。增强响应速度 当任务达到时无需等待新线程的创建即可执行提高系统的响应性。提高可管理性 线程池统一分配、调优和监控线程避免线程耗尽或过载提高系统稳定性。 2. Java 中的线程池实现 Java 提供了 java.util.concurrent 包来支持线程池功能其中核心类是 ThreadPoolExecutor。 ThreadPoolExecutor 的构造方法 public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 非核心线程的存活时间TimeUnit unit, // 存活时间的单位BlockingQueueRunnable workQueue, // 任务队列ThreadFactory threadFactory, // 线程工厂RejectedExecutionHandler handler // 拒绝策略 ) {// 构造函数实现 }3. 参数说明 corePoolSize 核心线程数线程池在空闲的时间仍保存的线程数量。maximumPoolSize 最大线程数线程池所能够创建的最大线程数量。keepAliveTime 和 unit 当线程数超过核心线程数时多余的空闲线程会在终止前等待新任务的最长时间。workQueue 用于保存等待执行任务的阻塞队列常用的阻塞队列有 ArrayBlockingQueue 基于数组的有界阻塞队列按 FIFO先进先出顺序对元素进行排序。LinkedBlockingQueue 基于链表的阻塞队列吞吐量通常高于 ArrayBlockingQueue。SynchronousQueue 一个不存储元素的阻塞队列执行插入操作之前需要阻塞等待相应地删除操作。 threadFactory 关于创建新线程的工厂可用于为每个创建的线程设置名称和优先级等属性。handler 当线程池和队列都满时执行拒绝策略操作。常见的拒绝策略 AbortPolicy 直接抛出异常默认策略。CallerRunsPolicy 由调用线程执行。DiscardOldestPolicy 丢弃队列中最老的任务尝试执行当前线程。DiscardPolicy 直接丢弃当前线程。 4. 代码示例手动模拟实现线程池执行任务 import java.util.concurrent.ArrayBlockingQueue;// 实现一个固定线程个数的线程池 class MyThreadPool {//阻塞队列private ArrayBlockingQueueRunnable queue null;public MyThreadPool(int n) {// 初始化线程固定数目的线程数// 以 ArrayBlockingQueue 作为任务队列固定容量为 1000queue new ArrayBlockingQueue(1000);// 创建 N 个线程for (int i 0; i n; i) {Thread t new Thread(() - {try {while (true) {Runnable task queue.take();task.run();}} catch (InterruptedException e) {e.printStackTrace();}});t.start();}}//提交任务public void submit(Runnable task) throws InterruptedException {// 把任务丢到队列中queue.put(task);}} public class Demo11 {public static void main(String[] args) throws InterruptedException {MyThreadPool threadPool new MyThreadPool(10);for (int i 0; i 100; i) {int id i;threadPool.submit(() - {System.out.println(Thread.currentThread().getName() id id);});}} }5. 代码分析 首先MyThreadPool类定义了一个私有的ArrayBlockingQueueRunnable类型的成员变量queue初始值为null。 在构造函数public MyThreadPool(int n)中执行了以下操作 初始化线程池固定线程数为n。 使用ArrayBlockingQueue作为任务队列固定容量为1000。 创建n个线程每个线程通过一个Thread对象来表示。 在线程的运行代码中使用try-catch块来处理可能的InterruptedException。 在try块中使用while (true)循环来不断从队列中获取任务并执行。 如果获取任务成功调用task.run()来执行任务。 如果在等待任务时被中断捕获InterruptedException并打印堆栈跟踪信息。 接着MyThreadPool类定义了一个公共方法public void submit(Runnable task) throws InterruptedException用于提交任务到线程池。 这个方法将任务添加到阻塞队列中使用queue.put(task)。 如果队列已满调用put方法将阻塞直到有空间可用。 在主类Demo11中定义了一个main方法用于演示如何使用MyThreadPool。 创建一个线程池指定线程数为10。 使用for循环提交100个任务到线程池。 每个任务是一个Runnable其运行时打印当前线程的名称。 需要注意的是由于线程池的线程数是10而任务数是100所以任务会被依次添加到阻塞队列中然后由这10个线程来处理。 这个自定义的线程池实现相对简单没有涉及线程的回收、线程工厂等更复杂的功能。在实际应用中通常会使用Java提供的ExecutorService和ThreadPoolExecutor等类来实现更 robust 的线程池管理。 另外代码中使用了lambda表达式来定义Runnable任务这在Java 8及以后的版本中是可用的。如果使用更早的Java版本可能需要使用匿名内部类来实现Runnable接口。 八、多线程的代码案例定时器 在 Java 中定时任务的实现主要依赖于 Timer 类和 ScheduledExecutorService 接口。 1. 使用 Timer 和 TimerTask 实现定时任务 Timer 时Java提供的定时器类主要用于在指定时间安排任务的执行。 代码示例 import java.util.Timer; import java.util.TimerTask;public class Demo12 {public static void main(String[] args) {Timer timer new Timer();TimerTask task new TimerTask() {Overridepublic void run() {System.out.println(任务执行时间 System.currentTimeMillis());}};// 延迟 1 秒后执行每 3 秒执行一次timer.scheduleAtFixedRate(task, 1000, 3000);} }代码分析 Timer 对象用于安排任务。TimerTask 类需要继承并实现 run 方法定义任务的具体内容。scheduleAtFixedRate 方法第一个参数是要执行的任务第二参数是初始延迟的时间单位为毫秒第三个参数是执行间隔单位为毫秒。 注意事项 Timer 使用单一后台线程执行任务如果某个任务执行时间过长就会影响其他线程的执行。Timer 不适合处理需要精细控制的并发任务。 2. 使用 ScheduledExecutorService 实现定时任务 ScheduledExecutorService 是 Java5 引入的定时任务调度器提供了更加灵活和线程安全的定时任务功能。 示例代码 import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;public class Demo13 {public static void main(String[] args) {ScheduledExecutorService schedule Executors.newScheduledThreadPool(2);Runnable task () - {System.out.println(任务执行时间 System.currentTimeMillis());};// 延后 1 秒执行任务每 3 秒执行一次schedule.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);} }代码分析 ScheduledExecutorService 对象通过 newScheduledThreadPool 方法来创建参数是线程池的数量。Runnable 接口使用 Lambda 表达式来定义任务内容。scheduleAtFixedRate 方法第一个参数是要执行的内容第二个参数是初始延迟第三个参数是执行间隔第四个参数是时间单位。 优势 ScheduledExecutorService 使用线程池可以同时执行多个任务避免单线程。提供更好的异常处理和任务调度能力。 3. 手动模拟实现一个定时器 代码示例 import java.util.PriorityQueue;// 写一个TimerTask class MyTimerTask implements ComparableMyTimerTask {private Runnable task; // 队列中要执行的任务private long time; // 记录任务要执行的时刻public MyTimerTask(Runnable task, long time) {this.task task;this.time time;}//优先级队列要比较的顺序Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}public long getTime() {return time;}public void run() {task.run(); // Runnable task} }// 写一个定时器 class MyTimer {// 1. 需要一个数据结构来管理队列中的任务优先级队列private PriorityQueueMyTimerTask queue new PriorityQueue();// 2. 多个线程操作一个队列势必会存在线程安全问题当前调用 schedule 把任务添加到队列中还有一个线程执行队列中的任务private Object locker new Object();// 3. 实现一个 schedule 方法把任务添加到队列中public void schedule(Runnable task, long delay) {synchronized (locker) {// 以入队列这个时刻作为时间基准MyTimerTask myTimerTask new MyTimerTask(task, System.currentTimeMillis() delay); // 计算出任务的执行时间系统时间延迟时间queue.offer(myTimerTask);// 在执行 schedule 方法时唤醒 waitlocker.notify();}}// 4. 构造方法创建一个线程来执行队列中的任务public MyTimer() {Thread t new Thread(() - {try {while (true) {synchronized (locker) {// 取出栈顶元素while (queue.isEmpty()) {// continue;locker.wait(); // 与 while 搭配在一起使用}MyTimerTask task queue.peek();if (System.currentTimeMillis() task.getTime()) {// 还没到时间// continue;locker.wait(task.getTime() - System.currentTimeMillis()); // 解决忙等问题} else {task.run(); // 执行队列中的任务queue.poll();}}}} catch (InterruptedException e) {throw new RuntimeException();}});// 启动线程t.start();} }public class Demo4 {public static void main(String[] args) {MyTimer myTimer new MyTimer();myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(hello 3000);}}, 3000);myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(hello 2000);}}, 2000);myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(hello 1000);}}, 1000);} }代码分析 这段代码实现了一个简单的定时器类 MyTimer它使用优先队列PriorityQueue来管理任务并在一个后台线程中执行这些任务。 类 MyTimerTask MyTimerTask 实现了 ComparableMyTimerTask 接口以便可以将任务按照执行时间排序。 成员变量: task: 一个 Runnable 对象表示要执行的任务。time: 表示任务应该被执行的时间戳以毫秒为单位。 构造方法: MyTimerTask(Runnable task, long time): 初始化任务和执行时间。 方法: compareTo(MyTimerTask o): 比较两个任务的执行时间确保优先队列中的任务按时间顺序排列。 Override public int compareTo(MyTimerTask o) {return Long.compare(this.time, o.time); }注意原代码中的 (int) (this.time - o.time) 可能会导致整数溢出问题建议使用 Long.compare 方法。 getTime(): 返回任务的执行时间。 run(): 执行任务。 类 MyTimer MyTimer 类负责管理任务队列并在适当的时间执行任务。 成员变量: queue: 一个优先队列用于存储 MyTimerTask 对象。locker: 一个锁对象用于同步访问共享资源。 方法: schedule(Runnable task, long delay): 安排一个任务在指定的延迟后执行。 public void schedule(Runnable task, long delay) {synchronized (locker) {MyTimerTask timerTask new MyTimerTask(task, System.currentTimeMillis() delay);queue.offer(timerTask);locker.notify();} }构造方法 MyTimer(): 创建并启动一个后台线程该线程负责从优先队列中取出任务并在适当的时间执行它们。 public MyTimer() {Thread t new Thread(() - {try {while (true) {synchronized (locker) {while (queue.isEmpty()) {locker.wait();}MyTimerTask task queue.peek();if (System.currentTimeMillis() task.getTime()) {locker.wait(task.getTime() - System.currentTimeMillis());} else {task.run();queue.poll();}}}} catch (InterruptedException e) {e.printStackTrace();}});t.start(); }代码改进点 compareTo 方法: 使用 Long.compare 方法代替 (int) (this.time - o.time)避免整数溢出问题。 Override public int compareTo(MyTimerTask o) {return Long.compare(this.time, o.time); }线程安全: 确保所有对共享资源如 queue的操作都在同步块内进行。 异常处理: 在实际应用中可能需要更健壮的异常处理机制例如重新抛出异常或记录日志。 线程中断: 当前实现没有提供停止后台线程的方法。如果需要停止定时器可以考虑添加一个标志位或其他机制来安全地终止线程。 4. 总结 对于简单的定时任务可以使用 Timer 和 TimerTask。 对于复杂的并发定时任务推荐使用 ScheduledExecutorService以获得更高的灵活性和可靠性。 在实际应用中应根据具体需求选择合适的定时任务实现方式确保程序的效率和稳定性。
http://www.dnsts.com.cn/news/102314.html

相关文章:

  • 网站广告怎么放直播系统源码
  • 商城网站设计定制网站建设平台选用
  • 龙岗义乌网站制作做后期从哪个网站选音乐
  • asp.net网站建设项目实战 董义革百度seo关键词优化市场
  • 搭建网站教程视频别人的wordpress注册
  • 国外网站推广方法关键词你们懂的
  • 手机网站编辑客户关系管理系统流程图
  • 网站备案信息加到哪里重庆建设网站公司简介
  • 中煤浙江基础建设有限公司网站响应式wordpress
  • 公司网站开发哪家好世界青田网app
  • 现在最流行的网站推广方式有哪些洛阳制作网站ihanshi
  • 网站建设规划方案wordpress标签云404错误
  • 电商网站的模块如何做seo搜索优化
  • c2c模式的网站文件管理软件
  • 做企业专业网站一般要多少钱图书馆网站信息化建设
  • 做前后端网站教程wordpress设置文章标题
  • 郑州网站开发培训价格中国建设银行个人信息网站
  • 网站转化率天津城市建设管理职业学院网站
  • 优质网站排名公司西安软件外包公司
  • 防止网站扫描室内设计师培训哪里好
  • 网站建设公司走进深圳一百讯网站制作的软件有哪些
  • 深圳做分销网站设计上海搜索优化推广
  • 制作一个网站难吗易湃智能营销平台
  • 专业网站建设的公司哪家好安卓app安装
  • 导航网站如何被百度收录免费商业源码论坛
  • 邢台做移动网站报价一个服务器可以备案几个网站吗
  • 江西省楚天建设集团有限公司网站六安网站建设报价方案
  • 网站推广途径方法火车头wordpress模块
  • 电子科技东莞网站建设对外网站建设情况汇报
  • 网站盒子怎么做网站客户端制作教程