3.3.1 Thread
进程和线程阐述:
java语言支持多线程,一个正在执行的java程序是一个进程,一个进程中包含多个线程,线程是单一的顺序控制流,CPU执行计算指令都是按照顺序执行,但由于其执行速度很快,可将事件分成多个时间片,交替执行。
进程和线程的区别:
进程创建开销大于线程,进程之间通信比线程要难。
线程不可独立存在,需要依托于进程,线程可看作轻量级的进程。
进程稳定性高于线程,进程运行不会影响其他进程,线程崩溃可能导致程序崩溃
Thread类简介:
Thread类位于java.lang包下,JDK1.0引入,在hotspot虚拟机中,线程使用的是基于操作系统的1:1内核实现模型来创建线程,线程的创建、调度、执行、销毁由内核进行控制,调度过程通过抢占策略进行控制。
Thread类生命周期
java语言中线程的6个生命周期,在任意时刻线程只会有1种状态,java提供了特定的api,某些场景可以实现状态的切换。6种状态使用java.lang.Thread.state枚举来表示,状态如下:
新建(NEW):线程创建但未启动
运行(RUNNABLE):线程正在执行或正在等待操作系统分配执行时间
阻塞(BLOCKED):当多个线程争抢资源,等待获取一个排他锁的线程会处于该状态,线程获取到锁后状态变为RUNNABLE,其他等待锁的线程状态依旧为BLOCKED,一般使用synchronized关键字修饰或ReentrantLock包裹的代码块会触发到线程阻塞
无限期等待(WAITING):此状态下的线程不会被分配执行时间,需要被其他线程显式唤醒。通过 对象实例.wait, 线程实例.join, LockSupport.port()等方法会使线程处于此状态
限期等待(TIMED_WAITING):计时等待的线程,不会被操作系统分配执行时间,也不需要其他线程显式唤醒,在计时结束后由操作系统自动唤醒。以下方法可导致线程处于此状态:Thread.sleep(Long millis),object.wait(timeOut),Thread.join(Long missil)、LookSupport.parkNanos()、LookSupport.parkunti)
终止(TERMINATED):线程执行结束后处于此状态
Thread构造方法:
Thread线程优先级:
创建线程方式:
继承Thread类,重写run方法
实现Runnable接口,重写run方法
实现Callable接口,重写call方法。将Thread和Callable联系在一起,需要通过FutureTask
Callable接口是JDK1.5版本后新增的接口,位于java.util.concurrent包下,call方法是run方法的增强版,可以通过实现Callable接口时传入泛型执行call方法的返回值,并可声明抛出异常。
native方法:
Thread.yield(); 此方法表示当前线程自愿放弃对处理器的使用
thread.join();某个线程在另一个线程之前执行
Thread.sleep();当前线程暂停执行指定的毫秒数
thread.iterrupt();中断线程
守护线程:
通过thread.setDaemon(true)将线程设置为守护线程或用户线程。此方法必须在线程启动前调用。作用是服务非守护线程。例如垃圾回收线程就是守护线程。守护线程中产生的线程依旧是守护线程。
3.3.2 ThreadLocal
定义:线程变量,ThreadLocal线程中填充的变量属于当前线程,对其他线程是隔离的,是当前线程独有的变量。
ThreadLocal为变量在每个线程都创建一个副本,每个线程都可以访问自己内部的副本变量,且该副本只能由当前线程使用,所以不存在多线程共享的问题
适用场景:适用于每个线程都需要自己独立的实例且该实例在多个方法中使用。即变量在线程间隔离而在方法或类间共享的场景。
与synchronized的区别:
相同点:都用于解决多线程并发访问
不同点:Synchronized用于线程间的数据共享,而ThreadLocal用于线程间的数据隔离。
Synchronized是利用锁的机制,使变量或代码块在某一时刻只能被一个线程访问;而ThreadLocal为每一个线程提供了变量的副本,使得每个线程在同一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
ThreadLocal原理:
set方法:首先获取当前线程,然后获取线程的ThreadLocalMap属性,如果map属性不为空,则更新value值,否则先实例化ThreadLocalMap,然后初始化value
ThreadLocalMap是ThreadLocal的内部静态类,使用Entry来存储数据,并且继承弱引用(只能存活到下次垃圾回收之前)。
在Entry内部使用ThreadLocal作为key,使用设置的value作为value
get方法:首先获取当前线程,然后获取线程的ThreadLocalMap属性,如果map属性不为空,获取map中的值,
否则初始化ThreadLocalMap,key为threadLocal,value为null,返回null
remove方法:首先获取当前线程,然后获取线程的ThreadLocalMap属性,map不为空,直接将threadLocal对应的值从map中删除
删除原因:涉及到内存泄露问题;因为ThreadLocalMap中使用的key是ThreadLcoal的弱引用,而弱引用的特点在下一次垃圾回收时必然会被清理。但是value是强引用,不会被清理,这样在ThreadLocalMap就会出现key为null的value。同时ThreadLocal是与线程绑定的变量,生命周期和线程共存,而通常线程池都是采用线程复用的办法,线程很难甚至永远不会结束,意味着线程的生命周期难以预测,甚至与JVM生命周期一致。对于重复利用的线程,如果没有手动删除对应key,就会导致(null,value)的对象越来越多,从而导致内存泄露
Thread、ThreadLocal、ThreadLocalMap之间的关系:ThreadLocalMap是Thread线程的属性,ThreadLocal是维护ThreadLocalMap的工具类。
Thread线程可以拥有多个ThreadLocal维护的线程独享变量。
常见使用场景
1.每个线程需要有自己单独的实例
2.实例需要在多个方法中共享,但不希望被多线程中共享
正确使用方式:
将ThreadLocal定义成private static,这样ThreadLocal生命周期更长,并且是强引用,不会被回收清理,可以保证任何时候都可以通过threadLocal的弱引用访问到Entry的value值
每次使用完ThreadLocal,务必调用remove方法清除数据,避免内存泄露
参考资料:https://blog.csdn.net/u010445301/article/details/111322569
https://blog.csdn.net/u010445301/article/details/124935802
3.3.3 Synchronized
1.定义:java关键字。
2.作用:保证同一时刻最多只能有1个线程执行被synchronized修饰的代码块或方法
3.应用场景:保证线程安全,解决多线程的并发同步问题(实现的是阻塞型并发)
具体场景如下:
1.修饰实例方法/代码块,(同步)保护的是同一个对象方法的调用&实例对象
2.修改静态方法/代码块:(同步)保护的是静态方法的调用&类对象
4.原理:
1.依赖JVM实现同步
2.底层通过一个监视器(monitor)对象完成,wait,notify方法也依赖monitor对象
监视器锁(monitor)的本质依赖于底层系统的互斥锁(Mutex Lock)实现
5.具体使用:
5.1 使用规则
5.1.1 锁对象设置
a:修饰代码块时,需要一个reference(引用)对象作为锁的对象
b:修饰实例方法时,默认锁对象是当前实例对象
c:修饰静态方法时,默认锁对象是当前类的class对象
5.1.2 根据锁对象的不同,一把锁同时最多只能被一个线程持有
若目标锁已被当前线程持有,其他线程只能阻塞等待当前线程释放目标锁
若当前线程持有目标锁,其他线程仍然可以访问类中无synchronized修饰的方法
5.1.3 当对象获取多个锁时,必须以相反的顺序释放&在与所有锁被获取时相同的词法范围内释放所有锁
若线程进入由线程已拥有的监控器保护的synchronized块,就允许线程继续执行
只有线程推出它进入的第一个synchronized块时,才释放锁
5.1.4 注意
java类中,实例对象可以有多个,类对象只能有1个(类的不同实例共享该类的class对象)
静态方法&实例方法上的锁默认不一样,静态方法加锁,和其他所有加锁的静态方法互斥
5.2 锁的类型&等级
对象锁:
定义:含synchronized方法/代码块的类的实例对象
作用:控制同步方法之间的同步
锁对象:实例对象
锁数量:多个
表示形式:修饰实例方法或实例代码块
应用场景:控制实例方法之间的同步
使用:
使用前必须先获得对象的锁(线程进入synchronized方法时获取对象锁,若此时对象锁已被其他线程占用,则需等待此锁被释放后才能调用)
java所有对象都有一个互斥锁,此锁由JVM自动获取和释放
synchronized正常返回or抛出异常终止,JVM都会自动释放对象锁
方法锁(也属于对象锁):
定义:使用synchronized修饰的方法
作用:控制同方法之间的同步
锁对象:实例对象
锁数量:多个
表示形式:修饰实例方法或实例代码块
应用场景:控制实例方法之间的同步
使用:synchronized方法控制对类成员变量的访问
类锁:
定义:使用synchronized修饰静态方法/静态代码块(类锁实际上就是锁Class对象,具体表现是 锁静态方法/静态代码块)
作用:控制静态方法之间的同步
锁对象:类对象
锁数量:1个
表示形式:修饰静态方法或静态代码块
应用场景:控制静态方法之间的同步
使用:一个静态方法被synchronized修饰,此类所有实例化对象在调用此方法时,共用同一把锁
一个类不论实例化多少次,其静态方法在内存中始终只有1份
5.3 注意
使用synchronized修饰较大方法,存在一定缺陷,将会大大影响效率;
例如:使用synchronized方法修饰线程类的run方法,由于run()方法在线程整个生命周期内一直运行,因此将导致本类的其他synchronized方法永远被阻塞
解决办法:使用synchronized修饰代码块 & 任意指定上锁的对象。精确锁定需要同步指定的代码片段,避免无需同步的代码进行同步,影响效率
synchronized(syncObject){}
6. 特点
保证原子性(一个操作/一系列操作 要么全部执行,要不全部不执行)、可见性(某个线程修改了共享属性的值,其他线程可立刻看到共享属性的值)、有序性(程序运行顺序和编码顺序一致,但计算机实际执行并不一定)
可重入性:某个线程获取锁后,调用其他需要同样锁的代码时可直接调用
重量级:底层通过monitor(监视器)对象完成,wait,notify等方法也依赖monitor对象
监视器锁的本质依赖于底层操作系统的互斥锁(Mutex Lock)实现,而操作系统实现线程切换需要从用户态转换到内核态
上述执行时间较长,所以synchronized效率低&重量级
7.其他控制并发/线程同步方式
7.1 Lock、ReentrantLock
7.2 CAS Compare and swap(比较并交换) ,是一种解决并发操作的乐观锁;而synchronized是同一时刻只能由一个线程执行,属于悲观锁
参考资料:https://blog.csdn.net/carson_ho/article/details/82992269
https://blog.csdn.net/weixin_43849277/article/details/108358035
https://blog.csdn.net/weixin_36759405/article/details/83034386
3.3.5 java.util.concurrent
1.定义:是java中用于并发编程的重要工具集,提供了线程池、原子变量、并发集合、同步工具类、阻塞队列等一系列高级并发工具类,
使用这些工具类可以极大的简化并发编程的难度,减少出错的可能性,提高程序的效率和可维护性。
2.内容细分
2.1 Executor FrameWork 执行器框架
定义:Executor FrameWork提供了标准的方法来启动、管理和控制线程的执行,执行器框架主要由接口和类构成,
如Executor、Executors、ExecutorService、Runnable、Callable等。这些组件共同协作,提供了灵活高效的线程管理机制
关键组件说明:
Executor:基本接口,定义execute方法,用于接收实现runnable接口的对象,并启动新线程执行该对象的run方法
Executors:工具类。提供了多种类型线程池的创建方法,如newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool等。这些线程池内部实际上
都是实现了ExecutorService接口的对象
ExecutorService:继承自Executor 接口,添加了一些管理和控制线程的方法,如shutdown、shutdownNow、awaitTermination(等待所有任务执行完毕关闭线程池)
Future和Callable接口:两接口通常一起使用,callable接口类似runnable接口,区别就是由返回值,并且可以抛出异常;
Future表示异步计算的结果,提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
2.2 并发集合 Concurrent Collections
定义:是用于支持并发编程的一组数据结构,用于避免多线程访问修改共享数据,可能出现的数据不一致和其他并发问题。
常见并发集合类:
ConcurrentHashMap:线程安全的HashMap,支持多个线程同时读写映射表,而不会相互阻塞
CopyOnWriteArrayList:线程安全的ArrayList;修改时复制底层数组来实现线程安全;读取操作在内部数组的快照上进行,无需锁定;写入时是创建新的数组副本,修改之后原子地将其替换为当前数组
ConcurrentLinkedQueue:基于链接节点的无界线程安全队列,按照FIFO(先进先出)原则对元素进行排序,多线程安全并发访问此队列,使用高效非阻塞算法实现
ConcurrentSkipListMap和ConcurrentSkipListSet:基于跳表(Skip List)数据结构实现的并发有序集合,与TreeMap和TreeList类似,但是支持更高并发的读写
ConcurrentLinkedDeque:线程安全的双端队列,允许多个线程并发访问
BlockingQueue接口及其实现:线程安全队列。检索队列如果队列为空会阻塞线程,直至队列中有元素;添加元素如果队列已满会阻塞线程,直至队列有空间。
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue和SynchronousQueue等都是此接口的实现
2.3 Atomic Variables 原子变量
定义:并发编程中实现线程安全的一种机制,原子变量提供了在多线程环境中安全读取、修改、更新变量的方式;
常用原子类:java.util.concurrent.atomic.AtomicInteget、AtomicLong、AtomicBoolean等
2.4 Synchronizers 同步器
定义:java并发编程中用于协调线程之间同步的组件,提供了机制,使线程能等待、通知、限制其他线程的执行。有效的避免死锁、竞态条件和其他线程问题。
主要类:
ReentrantLock:可重入锁,一个线程可以多次获取同一个锁,而不会导致死锁;支持公平锁和非公平锁;底层通过AQS链接实现
CountDownLatch:同步辅助工具,允许一个或多个线程等待其他线程完成一系列操作。初始化需要设置一个计数值,每个在其任务完成后调用countDown减少计数值,当计数值减少至0时,所有等待线程都会被唤醒
CyclicBarrier:同步辅助工具,允许一组线程相互等待,直到所有线程达到某一个状态后再一起运行。初始化时会设置一个屏障的初始阶段数,线程完成任务后调用await方法进行等待,当阶段数减少至0时,所有线程继续执行
Semaphore:信号计数器,提供了对资源的精细控制,维护了一个许可证书,只有当获取许可证书的线程才能访问受保护的资源,当线程完成了对资源的访问,会释放许可证,允许其他线程获得许可证。
参考资料:https://zhuanlan.zhihu.com/p/651441418
https://blog.csdn.net/qusikao/article/details/135705993
https://blog.csdn.net/qq_45871274/article/details/130462252
https://blog.csdn.net/outsanding/article/details/111126726
https://blog.csdn.net/CNCDXX_88/article/details/129119877
https://blog.csdn.net/fanrenxiang/article/details/80623884
3.3.6 Lock
路径:java.util.concurrent.locks.Lock
类型:interface
定义:类似于synchronized块的同步机制
与synchronized的区别:
synchronized是关键字;Lock是接口
synchronized无需手动释放锁;Lock需要手动进行释放锁,如果未释放,可能出现死锁
Lock可以配置公平策略,实现线程按照先后顺序获取锁
Lock提供了trylock方法,可以试图获取锁,获取成功或失败,有不同的返回值,可让程序灵活处理
lock方法和unlock方法可以在不同的方法中执行,比synchronized更加灵活
抽象方法:
void lock(); :获取锁,如果锁不可用,则出于线程调度目的,当前线程将被禁用,获取锁之前处于休眠状态
boolean trylock(); :尝试获取锁,可用返回true,不可用返回false
boolean trylock(long time,TimeUnit unit) throws InterruptedException ;尝试获取锁,如果锁可用返回true,不可用,则出于线程调度目的,禁用当前线程,并设置状态为休眠,直到以下三种情况之一为止(当前线程获取到锁;当前线程被中断,并且支持中断获取锁;经过指定等待时间获得了锁返回true,未获取到依旧返回false)
参考资料:https://blog.csdn.net/m0_50370837/article/details/124471888
3.3.7 ReentrantLock
路径:java.util.concurrent.locks.ReentrantLock
类型:class。实现了Lock接口
定义:重入锁,也叫做递归锁。同一线程外层函数获得锁之后,内层函数仍然有获取该锁的代码,但不受影响,可避免死锁问题。synchronized也可重入。
可重入测试:编写类实现runnable接口,实例化ReentrantLock。编写两个方法,方法中使用同一个lock进行锁定和释放锁,并且其中一个方法调用另外一个方法,重写的run方法中调用包含另一个方法的方法,启动线程,两个自定义方法都会执行。
参考资料:https://blog.csdn.net/m0_50370837/article/details/124471888
https://blog.csdn.net/zhengzhaoyang122/article/details/110847701
https://it-blog-cn.com/
3.3.8 CAS
1.定义:比较并替换,轻量级的线程同步机制,可在不使用锁的情况下,对共享数据进行线程安全的操作
2.思想:三个参数,内存值V,旧的预期值A,新值B,当预期值A和内存值V相同时,将内存值V更新成B,否则什么都不做。此过程是原子操作,底层依赖于CPU的原子指令
3.原理相关的Usafe类:JDK提供的不安全的类,提供了底层的操作,作用是让java直接操作内存,提高效率。大多都是一些native方法,直接交由JVM或c++去实现。
参考资料:https://blog.csdn.net/Aqting/article/details/129465893
https://blog.csdn.net/yujing1314/article/details/105943128
3.3.9 Java线程和Linux线程的联系
java线程的实现方式,是内核线程的实现方式。这种方式是直接由操作系统内核完成线程切换,内核通过操纵调度器(Thread Scheduler)实现线程调度,并将线程任务反映在各个处理器上。
虽然线程是由内核线程实现,但是程序并不直接使用内核线程,而是使用内核线程的高级接口-轻量级进程(LWP)(即是线程)
KLT:内核线程/内核分身
LWP:轻量级进程/线程
大型程序开辟的线程数量至少等于机器的CPU内核数量
java使用 Runtime.getRuntime().availableProcessors(); 获取CPU内核数量
如果线程有一半的时间是阻塞,线程数量应为内核数量的2倍
线程数量=内核数量/(1-阻塞率)
参考资料:http://www.hongyanliren.com/2015m07/35068.html