深圳龙华大浪做网站公司,广告行业怎么找客户,创建简单的微信小程序,怎样创建一个平台#系列文章
关于时间复杂度o(1), o(n), o(logn), o(nlogn)的理解
关于HashMap的哈希碰撞、拉链法和key的哈希函数设计
关于JVM内存模型和堆内存模型的理解
关于代理模式的理解
关于Mysql基本概念的理解
关于软件设计模式的理解
关于Redis知识的理解 文章目录 前言一、线程…#系列文章
关于时间复杂度o(1), o(n), o(logn), o(nlogn)的理解
关于HashMap的哈希碰撞、拉链法和key的哈希函数设计
关于JVM内存模型和堆内存模型的理解
关于代理模式的理解
关于Mysql基本概念的理解
关于软件设计模式的理解
关于Redis知识的理解 文章目录 前言一、线程的基本概念线程并发与并行线程创建线程同步线程间通信线程状态 二、创建线程的方式1.继承Thread类2.实现Runnable接口3.实现Callable接口 三、synchronized和volatile区别volatilesynchronized 四、synchronized和lockCAS的区别和案例场景区别案例场景 五、sleep方法和wait方法有什么区别六、ThreadLocal作用和实现方式?ThreadLocal会不会发生内存泄漏?ThreadLocal的作用实现方式ThreadLocal与内存泄漏 前言
Java多线程是Java编程中一个非常重要的概念它允许程序同时执行多个任务。这对于提高程序的效率、响应速度和实现复杂的并发处理至关重要。
一、线程的基本概念
线程
线程是程序执行流的最小单元。一个进程中可以包含多个线程每个线程都运行在进程的上下文中并共享进程的资源如内存、文件句柄等。
并发与并行
并发是指两个或多个事件在同一时间段内发生而并行是指两个或多个事件在同一时刻同时发生。在多核CPU上线程可以真正并行执行在单核CPU上通过时间片轮转实现了线程的并发执行。
线程创建
继承Thread类实现Runnable接口实现Callable接口
线程同步
由于多个线程共享进程资源可能会引发数据不一致的问题因此需要线程同步机制来控制对共享资源的访问。Java提供了以下几种同步机制 synchronized关键字 可以用于方法或代码块确保同一时间只有一个线程可以执行该段代码。 Lock接口 java.util.concurrent.locks.Lock提供比synchronized更灵活的锁定机制允许尝试锁定、定时锁定以及公平性锁等
线程间通信
wait(), notify(), notifyAll() 这些方法用于线程间的等待/通知机制必须在同步代码块或同步方法中使用。 join() 让当前线程等待调用join方法的线程结束后再继续执行。 线程池 通过ExecutorService接口和它的实现类如ThreadPoolExecutor来管理和控制线程以提高资源利用率和响应速度
线程状态
新建New 线程被创建但尚未启动。 可运行Runnable 线程可以在任何时刻被JVM选中执行。 阻塞Blocked 等待获取监视器锁以便进入同步区域执行。 等待Waiting 无限期等待直到被其他线程显式唤醒或者调用了Object.wait()。 超时等待Timed Waiting 类似于等待状态但可以在指定时间内自行返回如通过Thread.sleep(long millis)。 终止Terminated 线程已结束执行。
二、创建线程的方式
1.继承Thread类
创建一个新的类继承Thread类重写其run()方法在这个方法中定义需要并行执行的代码。然后创建该类的实例并调用start()方法启动线程。 代码如下示例 class MyThread extends Thread {public void run() {System.out.println(通过继承Thread类创建线程);}
}MyThread t new MyThread();
t.start();2.实现Runnable接口
创建一个类实现Runnable接口实现run()方法然后将该类的实例作为参数传递给Thread类的构造器创建Thread对象后调用start()方法启动线程。 代码如下示例 class MyRunnable implements Runnable {public void run() {System.out.println(通过实现Runnable接口创建线程);}
}Thread t new Thread(new MyRunnable());
t.start();3.实现Callable接口
在Java中除了继承Thread类和实现Runnable接口之外还可以通过实现Callable接口结合Future和ExecutorService来创建线程。这种方式相比前两者提供了更强大的功能特别是能够获取线程执行的结果并且可以处理异常。
Callable接口类似于Runnable但提供了返回结果和抛出异常的能力。它有一个抽象方法call()该方法可以返回结果并声明抛出异常。
import java.util.concurrent.Callable;public class MyCallable implements CallableInteger {Overridepublic Integer call() throws Exception {int sum 0;for (int i 0; i 100; i) {sum i;}return sum; // 可以返回计算结果}
}
Future接口 Future接口代表异步计算的结果。它提供了检查计算是否完成、获取计算结果、取消计算等方法。
ExecutorService接口 ExecutorService是一个更高级的线程管理工具它是java.util.concurrent.Executor接口的实现可以用来管理Runnable和Callable任务的执行。
下面是如何使用Callable、Future和ExecutorService创建并执行线程的例子 代码如下示例 import java.util.concurrent.*;public class CallableAndFutureExample {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建一个线程池ExecutorService executorService Executors.newSingleThreadExecutor();// 提交Callable任务并获取Future对象FutureInteger future executorService.submit(new MyCallable());// 在这里主线程可以继续执行其他任务不必等待计算完成// 获取并打印结果如果计算未完成则会阻塞直到有结果System.out.println(结果为: future.get());// 关闭线程池executorService.shutdown();}
}在这个例子中我们创建了一个MyCallable类实现了Callable接口用于计算0到99的和。然后通过ExecutorService的submit(Callable task)方法提交任务并立即返回一个Future对象。我们可以使用future.get()来获取异步计算的结果此方法会阻塞直到结果可用。最后记得关闭ExecutorService来释放资源。
通过这种方式Java提供了更灵活、功能更全面的多线程编程模型特别适合需要获取线程执行结果或需要处理线程中抛出异常的场景。
三、synchronized和volatile区别
volatile和synchronized都是Java中用于实现线程同步和保证数据一致性的关键字但它们的工作机制、使用场景和提供的保证各不相同
volatile
作用范围 volatile只能应用于变量不能用于方法或代码块。 保证特性 它保证了变量的可见性和有序性但不保证原子性。这意味着一旦一个线程修改了volatile变量其他线程可以立即看到这个修改。同时可以防止指令重排优化确保程序按照预期的顺序执行。 性能 相比于synchronized使用volatile变量通常具有更好的性能因为它不会引起线程的阻塞。 适用场景 适用于那些读多写少、只需保证可见性而不涉及复杂同步操作的场景比如标志位、双重检查锁定中的非volatile变量初始化检查等。
synchronized
作用范围 synchronized可以应用于方法、代码块或者静态方法范围更广泛。 保证特性 synchronized不仅保证了可见性和有序性还确保了原子性即在synchronized代码块或方法内的操作不会被其他线程打断实现了线程安全。 性能由于其通过互斥锁机制实现同步可能会导致线程的阻塞和上下文切换因此在某些情况下性能不如volatile。 内存开销与阻塞 使用synchronized会有额外的内存开销因为它涉及到锁的获取与释放。当一个线程获取到锁时其他请求锁的线程将会阻塞直到锁被释放。 适用场景适用于需要保护的临界区操作如对集合的修改、多线程间的协作控制等复杂同步逻辑。
四、synchronized和lockCAS的区别和案例场景
synchronized和基于CASCompare and Swap的Lock机制如ReentrantLock在Java中都是用于实现线程同步和解决并发问题的工具但它们在实现原理、使用方式和特性上有所不同。
区别
实现机制 synchronized是Java语言层面的原生支持基于JVM层面的Monitor对象来实现通过进入和退出Monitor来实现同步控制。 基于CAS的Lock如ReentrantLock是Java并发包java.util.concurrent.locks中的接口实现它依赖于AbstractQueuedSynchronizerAQS框架使用自旋、阻塞队列等技术并通过CPU级别的CAS指令来实现乐观锁以达到线程同步的目的。
锁的获取与释放 synchronized自动管理锁的获取和释放。对于非静态同步方法或同步代码块锁与对象实例相关联对于静态同步方法锁与类的Class对象关联。当同步块或方法执行完毕无论是正常结束还是异常结束JVM都会自动释放锁。 使用Lock接口如ReentrantLock需要手动调用lock()获取锁unlock()释放锁。这要求程序员必须在合适的时机手动释放锁否则可能导致死锁。
公平性 synchronized锁默认是非公平的即等待的线程获取锁的顺序不确定可能会导致某些线程长时间等待饥饿现象。 ReentrantLock允许选择公平策略通过构造函数指定是否为公平锁。公平锁会按照线程等待的先后顺序分配锁减少饥饿现象但可能会影响性能。
性能与可中断性 synchronized在早期版本中性能较差但在Java 6之后有了显著优化如偏向锁、轻量级锁等机制性能与Lock差距缩小。 Lock提供了更灵活的锁等待机制如tryLock()方法尝试获取锁而不阻塞以及支持可中断的等待tryLock(long time, TimeUnit unit)提高了并发控制的灵活性和响应性。
案例场景
synchronized场景 当需要简单的线程同步且锁的生命周期与同步代码块或方法紧密相关时使用synchronized更为简洁便利。例如保护一个共享资源的读写操作防止数据不一致问题。
Lock如ReentrantLock场景 当需要更细粒度的锁控制如尝试获取锁、可中断的等待、公平锁等高级特性时选择Lock更为合适。例如在构建高性能的并发组件、需要控制锁的持有时间以减少阻塞时或者需要精确控制锁的释放时机比如在finally块中释放锁以确保异常安全。
五、sleep方法和wait方法有什么区别
sleep方法和wait方法在Java中都用于暂停线程的执行但它们之间存在几个关键区别
来源与所属 sleep方法属于Thread类是一个静态方法这意味着它可以被任何线程调用且作用于当前执行该方法的线程。 wait方法属于Object类是一个实例方法必须在某个对象上调用。这意味着要调用wait线程必须已经拥有了该对象的监视器即执行同步代码块或方法时。
锁的处理 当线程调用sleep方法时它不会释放任何锁。即使在同步代码块或方法中调用sleep当前线程仍会保持它所持有的锁。 调用wait方法会导致当前线程释放它所持有的对象的监视器锁从而使其他等待该对象锁的线程有机会执行。这促进了线程间的交互与通信。
唤醒机制 sleep方法会根据指定的时间让线程暂停执行时间结束后线程会自动恢复执行无需其他线程干预。 wait方法使线程等待直到其他线程调用同一个对象上的notify()或notifyAll()方法来唤醒它。如果没有指定等待时间wait会一直等待直到被通知。
异常处理 sleep方法声明抛出InterruptedException当线程在等待期间被中断时会抛出此异常。调用者可以选择捕获并处理这个异常。 wait方法同样声明抛出InterruptedException但实践中通常在调用wait时不需要显式捕获这个异常。
使用场景 sleep通常用于让当前线程暂停执行一段时间以避免过于频繁的操作或让出CPU给其他线程不涉及线程间的通信。 wait主要用于线程间的同步当一个线程需要等待某个条件满足时通常由其他线程改变该条件会调用对象的wait方法等待其他线程通过notify或notifyAll来唤醒。
总结来说sleep主要用于让线程自身暂时休息不涉及线程间的交互而wait则是线程间同步和通信的重要手段需要与notify/notifyAll一起使用来协调线程之间的执行顺序。
六、ThreadLocal作用和实现方式?ThreadLocal会不会发生内存泄漏?
ThreadLocal的作用
ThreadLocal主要用于创建线程局部变量。它的主要特点和作用包括
线程隔离每个线程都有自己的ThreadLocal变量副本线程之间互不影响这有助于避免多线程环境下的同步问题。 简化共享数据的访问在多线程环境下ThreadLocal可以为每个线程提供独立的变量副本减少了线程间的数据传递和同步开销。 降低耦合度它可以降低代码间尤其是跨类、跨层的耦合使得变量的访问更加方便和安全。
实现方式
ThreadLocal的实现基于内部类ThreadLocalMap这是一个定制化的哈希映射键是ThreadLocal实例本身值是各个线程需要保存的变量副本。每个线程都有一个这样的ThreadLocalMap存储着属于自己的ThreadLocal变量副本。
当你通过ThreadLocal的set(value)方法设置值时实际上是将这个值放入当前线程的ThreadLocalMap中对应的键就是这个ThreadLocal实例。 当调用get()方法时会从当前线程的ThreadLocalMap中根据ThreadLocal实例找到对应的值。 如果没有设置过值通常get()会返回ThreadLocal的初始值如果有提供的话。
ThreadLocal与内存泄漏
ThreadLocal确实有可能引起内存泄漏主要原因在于它的内部实现ThreadLocalMap。以下是可能导致内存泄漏的情况
未清理的ThreadLocal引用 当ThreadLocal对象不再使用时如果没有调用remove()方法那么其对应的Entry键值对仍然会保留在ThreadLocalMap中。即使ThreadLocal实例本身被垃圾回收但由于ThreadLocalMap的生命周期与线程相同其Entry中的键即弱引用的ThreadLocal实例虽然会被回收但value仍可能保持强引用状态导致无法被垃圾回收。
线程池场景下的问题 在使用线程池的场景下线程会被复用如果ThreadLocal变量没有被正确清理随着线程的反复使用ThreadLocalMap中可能会积累大量不再使用的Entry从而造成内存泄漏。
如何避免内存泄漏 ThreadLocalMap的设计中已经考虑到这种情况也加上了一些防护措施在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。