线程与进程
区别:
进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位
同一进程的线程共享本进程的地址空间,而进程之间是独立的地址空间
关系:一个程序至少一个进程,一个进程至少一个线程
多线程的优点:
增加CPU的利用率
同步进行的操作,效率高,且用户体验好
多线程的缺点:
线程要进行context switch
线程需要多余的memory
线程间的通信方式:
全局变量
消息
事件CEvent类
线程间的同步方式:
wait/notify
互斥量Synchronized
信号量Semaphore
事件
进程的通信方式:
管道及命名管道:管道可用于具有亲缘关系的父子进程间通信满,而有名管道除了具有管道所具有的功能外,还允许与无亲缘关系进程间的通信
信号:用于通知接收进程某个时间已经发生
消息队列
共享内存
套接字
Thread & Runnable
Thread是实现了Runnable接口的类,使得run支持多线程
实现Runnable接口相比继承Thread类的好处:避免继承的局限性,一个类可以实现多个接口
Java对象头
Mark Word:用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等
Klass Pointer:是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
monitor:一个同步工具,本质是依赖底层操作系统mutex lock实现。线程私有的数据结构monitor record,每一个线程都有一个可用的monitor record列表,每一个锁住的对象都会和一个monitor record关联。
锁
互斥锁mutex简单实现:https://www.jianshu.com/p/a5c98230a93d
锁存在的4种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态
偏斜锁:当没有竞争出现时,自动会使用偏斜锁。JVM会使用CAS操作,在对象头上的Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁
自旋锁:结合CAS确保线程获取锁,就是让线程进行一端无意义的循环等待,而不被挂起,看持有锁的线程是否会很快释放锁
轻量级锁:当关闭偏向锁功能或多个线程竞争偏向锁导致偏向锁升级为轻量级锁。也不用申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record
重量级锁:通过对象内部的monitor实现
synchronized底层实现
同步代码块是使用monitorenter和monitorexit实现的。线程执行monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁
同步方法依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。synchronized方法在Class文件的方法表中将该方法的access_flags字段中的synchronized标志设为1,表示该方法是同步方法
synchronized用的锁是对象头里的锁,所以synchronized是重量级锁。同步块在已进入的线程执行完成前,会阻塞后面的其他线程进入。Java的线程是映射到操作系统中的原生线程上的,如果要阻塞或者唤醒一个线程,都需要操作系统来帮忙,这就需要我们从用户态切换成内核态,因此这个状态转换是非常耗费CPU的。
Synchronized同步静态方法和非静态方法
synchronized修饰非静态方法,实际上是对调用该方法的对象加锁。同一个对象在两个线程中分别访问该对象的两个同步方法,会产生互斥。不同对象在两个线程中调用同一个同步方法,不会产生互斥
synchronized修饰静态方法,实际上是对该类对象加锁(java中一个类所有的实例都共享一个class对象,锁这个class对象)。用类直接在两个线程中调用两个不同的同步方法,会产生互斥。用一个类的静态对象在两个线程中调用静态方法或非静态方法,会产生互斥
多线程中synchronized锁升级的原理
在锁对象的对象头里面有一个threadid字段,在第一次访问的时候threadid为空,jvm让其持有偏向锁,并将threadid设置为线程id。再次进入的时候会判断threadid id是否与线程id一致,如果一致就可以直接使用该对象。如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数后,如果还没有正常获取到要使用的对象,就把轻量级锁换成重量级锁
Synchronized & Lock
实现层面:synchronized是JVM,Lock是JDK
响应中断:Lock可以让等待锁的线程响应中断,而Synchronized只能一直等待
读写锁:Lock可以提高多个线程进行读操作的效率
公平锁:Lock可以实现公平锁,Synchronized不能
显示获取和释放:Lock需要用unlock() 来释放,而synchronized在发生异常时,会自动释放锁
公平锁和非公平锁:https://www.jianshu.com/p/f584799f1c77
可重入锁:同一个线程可以多次获取同一把锁
Synchronized & ReentrantLock
ReentrantLock可以设置获取锁的等待时间,可以获取锁的各种信息
ReentrantLock是类,只适用于代码块,而Synchronized是关键字,可以修饰方法及代码块
Synchronized是JVM,ReentrantLock是JDK
ReentrantLock可以指定是公平锁还是非公平锁,Synchronized是非公平
ReentrantLock可以分组唤醒需要唤醒的线程,而Synchronized只能唤醒全部线程
ReentrantLock可以中断等待锁的线程,通过lock.lockInterruptibily()
Synchronized底层 + lock底层 + ReentrantLock底层?
ReentrantLock实现了Lock,运用了CAS:原理是CAS自旋和volatile变量
Volatile实现原理
当读取一个被volatile修饰的变量时,会直接从共享内存中读取,而非线程专属的存储空间中读。当volatile变量写后,线程中本地内存中共享变量就会设为置为失效状态,因此另一个线程需要从主内存中去读取该变量的最新值。
Happens before https://juejin.im/post/6844903864802476045
线程池工作原理
作用:减少创建和销毁线程的开销,实现线程的复用,提高线程的管理性
实现:主要由两部分组成,多个工作线程和一个阻塞队列。工作线程是一组已经处在运行中的线程,他们不断的向阻塞队列中领取任务执行,而阻塞队列用于存储工作线程来不及处理的任务
运行机制:
若当前实际线程数少于corePoolSize,即使有空闲线程,也会创建一个新的线程
若当前实际线程数处于corePoolSize于maximumPoolSize之间,并且阻塞队列没有满,则将任务放入阻塞队列中等待执行
若当前实际线程数小于maximumPoolSize,但阻塞队列已满,则之间创建新线程处理任务
若当前实际线程数量已经达到maximumPoolSize,并且队列已满,则使用饱和策略
核心参数:
corePoolSize:基本线程数量,线程池会尽量把实际线程数量保持在这个值上下
maximumPoolSize:线程数量的上界。如果实际线程数量达到这个值:阻塞队列未满,任务存入阻塞队列等待执行;阻塞队列已满,调用饱和策略
keepAliveTime:空闲线程的存活时间。当线程数量超过corePoolSize时,若线程空闲的时间超过该值,就会被停止
timeUnit:keepAliveTime单位
runnableTaskQueue:任务队列,存放任务的阻塞队列
AbortPolicy(饱和策略):CallerRunsPolicy只用调用者所在的线程执行任务。DiscardOldestPolicy丢弃任务队列中最久的任务。DiscardPolicy丢弃当前任务