全国学渣代表  

java 代码编译后成为字节码,字节码被类加载器加载到jvm里,jvm执行字节码,最后把指令执行到cpu上,。

1 volatile 得应用
volatile 是 轻量级得synchronized,就是当一个线程修改共享变量时,另一个线程能读到这个值。
他不会引起上下文的切换和调度。
volatile
确保共享变量能被准确和一致得更新,线程通过排他锁来单独获得这个变量,如果 一个字段被声明成volatile
那么 java线程内存模型确保所有线程看到这个变量得值是一致性得。
如果声明了 volatile得变量进行写操作,jvm就会向处理器发送一条lock前缀得指令,讲这个变量所在得缓存行得数据
写回到系统的内存,但写回到了内存其他处理器的得缓存值依然是旧的,所以在多处理器的情况下,为了保证
一致性,就会实现缓存得一致性协议,每个处理器通过嗅探总线上传来的数据 判断是否过期,当发现了对应的内存地址发生了改变
就会将当前得缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,就直接去读内存数据 。
2.volatile的使用优化
队列集合类LinkedTransferQueue,使用字节追加得方式来进行优化
除了volatile,Java并发编程中应用较多的 是synchronized。
2 synchronized
1 对于普通同步方法,锁是当前实例对象。
2 对于静态同步方法,锁是当前类得class对象。
3 对于方法块,锁是synchonized括号里的对象。

当一个线程试图访问同步代码块得时候 ,他首先必须得到锁,推出或者发生异常必须释放锁 。
锁在哪里?
锁里得信息 ?

monitor 画图

从jvm得规范中 看到synchonized在jvm得实现原理,jvm基于进入和退出Monitor对象来实现方法的同步
和代码块得同步,但是实现细节不一样,代码块得同步是monitorenter 和monitorexit 指令实现的。
而方法是另一种。
monitorenter 指令是在编译后插入到同步代码块得开始位置,monitorexit是在结束的位置或者异常。
jvm要保证每个monitorenter必须有对应的monitorexit与之配对。
任何对象都有一个monitor与之关联,,当且一个monitor被持有后,它将处于锁定状态。
线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

monitor

count 记录个数,进入成功的话 计数器 记录数字为1
owner 存储当前获取对象锁得线程
waitSet 等待队列 调用wait 会存在waitSet
EntrtList 没有获得锁得线程 存在这里
当多个线程进行加锁之后首先进入entrList得集合中 当线程获得monitor 进入owner,在通过count+1 ;
调用wait得话释放 monitor 也就是锁 owner恢复为null 并且count 自减1
然后waitSet集合被唤醒 。
wait和sleep得区别 就在于 wait 会释放锁,sleep 不会释放锁。
wait 和notify 是一个通信机制 ,执行wait得时候必须在同步锁里面。

notify 并不能立马执行 唤醒

锁的升级与对比

为了减少锁和释放锁带来的性能消耗,引入了偏向锁和轻量锁。
锁得状态: 无锁状态、偏向锁状态轻量锁状态和重量锁状态。 这几个状态随着竞争情况 逐渐升级,锁可以升级但是不可以降级,锁升级的策略 目的是提高获得锁和释放锁的效率 。
偏向锁:
偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如 有必要可以使用JVM参数来关闭延迟:
-XX:BiasedLockingStartupDelay=0。如果你确定应用程 序里所有的锁通常情况下处于竞争状态,
可以通过JVM参数关闭偏向锁:-XX:UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

轻量锁 线程没有抢到锁得过程之后 进入自旋状态之,自旋状态回消耗cpu 资源
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级
成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,
都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮
的夺锁之争。


wait 和notify 必须放在锁里面去执行得,因为wait和notify都是object 必须要获得锁。


线程调用了wait方法后,他会释放锁,然后进入waitset等待队列,然后在通过notifyAlll 来 释放 之后进入同步队列(EntryList)然后等待被cpu 加到锁。
run方法终止线程 通过调用run方法来终止线程结束 或者interrupt()结束 等线程运行结束后终止 。
线程是通过os指令来执行得 而不是根据代码得顺序执行的

join(); 可以进行排序 ;
join 实习原理:join 底层是通过wait()方法将线程进入等待队列,然后在结束后 底层在通过notify进行唤醒,进入锁池里,再通过获取锁进入 就绪状态。
锁 : synchronized
修饰代码块 或者方法,对于不安全或者多线程得时候 保证线程是穿行访问避免线程产生影响,保证线程得可行性。
synchronized 加锁后面表达的是作用域
synchronized (放入的对象就是全局锁)

jmm 模型

cpu 和内存之间存在高速缓存 ,
基于物理层面得虚拟模型 ,cpu 离高速缓存越近 ,缓存越快,cpu各个缓存是不共享得缓存是支持数据共享得


多个线程多个运行 cpu核心存在数据缓存里面 保证缓存一致性的原因,因此提供了 两种类型的锁,
一种是 总线锁 一种是缓存锁,
总线锁 就是当cpu处理这个当前线程的时候 其他的cpu不可以进行处理这个缓存
缓存锁 mesi 协议 缓存一致性 ,保证修改的时候 其他缓存失效,跟 内存的缓存去做对比 如果指向不一致 就再次同步

jmm 内存模型 保证数据一致性 可致性 原子性

线程之间通信的模型 jmm, 线程a和线程b 同时都有各自的 本地内存,本地内存连接主内存也就是堆内存,
当线程a int i=1 为 int i =2 的时候 ,主内存数据进行更改为2 ,然后线程b的本地内存也
随之变化,使线程之间达到同步的标准。
可见性 通过 volatile 保证可见性的原理。
volatile 使用cpu的缓存锁来保证数据的可见性。

内存屏障

cpu 有自己的缓存 ,弊端不能实时 进行交换 ,内存屏障 解决信息差异化的问题,
java 虚拟机 为了屏蔽和操作系统的屏障差异性,java 虚拟机提供了4种屏障
硬件:LoadBarrier 读屏障 ,StoreBarrier 写屏障
volatile: 那么写操作之前 会插入一个store store屏障 ,在写之后插入store 夹肉storeload
再读之前 插入 loadload屏障 在读之后插入 load store屏障 。
java 4种屏障指令 :
load load 做一个 load 第一个要先加载
load store 确保 load 加载比写优先 store 写到内存
store store
store load

jvm 四种指令

指令重排序

不论怎么排序,单线程的结果不可以改变 。最后编译器要遵循 as -if-serial 而volatile 就防止了指令重排 。

线程的使用 ?

1 出现耗时间的算法 ,并行计算。
2 等待网络 io响应导致的耗时。典型的案列nio 多线程 客户端访问,socke端使用线程池进行多线程接收
如何应用多线程?
1 继承thread
2 实现runnable
3 callbule/future 带返回值


锁阻塞:当t1线程进入后 获取了锁之后,t2线程再次获取属于阻塞状态,需要等待t1线程释放之后 t2线程再次获取锁 。

jdk 1.6以后加入locksuport.park()

线程的状态 ?

1 new
2 runnable 运行状态
3 blocked 阻塞状态
等待阻塞: wait
锁阻塞: synchronized
其他阻塞: sleep /join
4 waiting 等待状态
5 thread_wating
6 terminated
synchronized 状态

watting 状态

sleep 状态

interrupt

navtive 调用c的方法

#线程的启动和终止
start 是 native里面的方法 然后通过run 方法来执行 
1 stop interrup  优雅的中断
2 自定义通过volatile boolean isStop =true ; 

通过使用 Thread.interrupted();//线程复位

synchronized锁

每一个对象底层都拥有monitor对象,而进入锁和释放锁的标志就是 monitorenter和monitoexit, monitoexit在线程结束和抛出异常时会自动释放线程。monitoexit 退出后从而让在entryList的线程去竞争锁作用多线程加锁的方式保证线程同步 ,synchironized是锁得一种机制 ,解决了 原子性,可见性,有序性 。
synchronized的作用域范围:
非静态方法使用 synchronized 修饰的写法,修饰实例方法时,锁定的是当前对象,synchronized放在方法前以及代码块加(this),这两种方式是对象锁。
public synchronized void method1(){
}
public void method2(){
synchronized(this){}
}
}
这两种锁的是类对象,即类锁(类锁只是个概念,因为本质锁的本质还是对象)
public void methodC(){
synchronized(xxx.class){
}
}
public static synchronized methodD(){
}
首先wait线程先进入之后调用了wait方法后,wait要释放锁,因为只有wait释放了 那么notify才能获得锁,因为wait和notify是一起出现的,wait存储的位置就是ObjectMonitor对象内的WaitSet队列 这个时候notify唤醒后就会在waitSet里面取出一个线程放到EnterList 来等待何时可以抢到锁,所以wait 和notify 是不一定什么时候抢到锁的。

Wait 第一步把当前线程做成Object类型
第二步放到waitSet
第三步park 挂起
Notify唤醒的时候就是用unpark 获取Object得到waitSet线程,不为空就唤醒。
所以wait获得锁的原因 一是释放当前对象的锁,第二十让当前的线程进入阻塞状态 。
唤醒是需要唤醒谁 因此要获取对象锁,要去waitSet取出一个线程,然后加入EnterList。Notify不会释放锁,必须等monitorexit 等结束后才可以释放 。

synchronized作用域结论:
1 持有同一个实例的话,在访问不同的代码块的时候也会产生锁的效果 ,method1(),method2(),因为是同一个实例。
2 如果都new了一次实例那么是锁不住的,因为持有的实例对象不是同一个
3 methodC(),methodD(),不论是new还是实例,无论是否同一个实例,都会互斥。
4 对象锁和类锁,相互不影响
synchronized在jvm底层的应用:
对象头:
synchronized用的锁是存在Java对象头里的,Java对象头里的Mark Word里默,在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化,Mark Word可能变。

synchronized关键字下的锁状态升级

先了接一下自旋:所谓自旋锁,就是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
Synchronizationzed 每一个对象都有Monitor对象,然后通过对象头markWord把锁的数据存在ObjectMonitor中,而且锁的对应要有Monitorenter和Monitorexit 的结合出现,Monitorexit 是结束线程或者抛出异常后发生的。
自旋:就是在一个短时间内通过cpu对线程的自己获取锁,如果获取到了锁就存储到对象头保存信息,线程进入后通过ObjectMonitor 去判断,这样会比直接park挂起 更加快速,节约了cpu资源。
Cxq 后进先出 ,先进入这个队列,是一个CAS操作保证线程安全,每一个线程进入之后都有一个自旋 ,进入之后尝试获得锁,失败的话就park挂起一个线程。如果直接挂起再去唤醒 切换 性能比较高,所以采用自旋的方式。如果获得锁得话就会出队列 ,出队列会有一个头指针,CAS操作时间就很小,不需要重第一个做操作。
WaitSet: 调用wait方法进入WaitSet notify唤醒后进入Entrylist
Owner:获取线程
Recursions :重入次数

wait 和notify 为什么加锁,为什么wait notify 抢夺资源不清楚具体的运行时间?

1 偏向锁
  不存在竞争,并且都由一个线程获取锁。会在对象头里存储masterid,在进入的时候直接测试一下markword是否存储,偏向锁是一种等到竞争出现的时候才释放的锁机制,当其他线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放,偏向锁的撤销,需要等待全局安全点,就是没有字节码在运行了,偏向所在1.7之后是默认启动的,在应用程序启动几秒钟之后才被激活, 如果程序里面的锁属于竞争状态可以通过参数:-XX:-UseBiasedLocking=false,随之就会进入轻量锁。
2 轻量级锁
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并
将对象头中的Mark Word复制到锁记录中,然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
  轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
一旦锁升级成功后,就会不在降级。
3 重量级锁
监视器 OBJECTMONITOR 
升级目的在竞争加剧的时候 ,为了让线程得到锁 
waitSet: 当线程调用了wait方法后线程释放锁并且进入等待队列,而wait要和notify进行配套使用,notify同时获得锁并且同时唤醒wait等待的线程,并进入到entryList内去竞争锁。
entryList:是等待线程在没有获取锁之前存在的队列,当其他线程释放锁后,monitorexit后会通知entryList内的线程去竞争锁。
owner:当前线程的拥有者

monitor流程图:

锁的优化:

全局锁 :
Public synchronized void test(){
}
Public void test(){ synchronized(this){}}
对象锁 :
public static synchronized void test(){}
public void test(){synchronized(Demon.class){ }}

Lock
Reentranlock:
public class ReentrantLock implements Lock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
static final class NonfairSync extends Sync {
}
static final class FairSync extends Sync {
}
}

}

Lock demon
public class ThreadLockDemon {
//public ReentrantLock(boolean fair) {
// sync = fair ? new FairSync() : new NonfairSync();
// }
static Lock lock = new ReentrantLock(); // 支持公平锁和非公平重入锁
private static int count = 0 ;

public static void test() throws InterruptedException {
    Thread.sleep(400);
    lock.lock(); //加锁
    count++;
    lock.unlock();//释放锁
}

}
synchronized与 lock的区别 :
1 synchronized 不确定什么时候释放(异常或者线程结束后) 而lock 可以按照自己的程序去释放锁。
2 Lock 可以去判断锁得状态 ,其次比较灵活.
3 lcok 支持公平锁和非公平锁而synchronized 只支持非公平锁
重入读写锁:ReentantReadWriteLockDemon
在读多写少的场景比较实用 重入读写锁
//重入读写锁
public class ReentrantReadWriteLockDemon {
static Map map = new HashMap();
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static Lock read = reentrantReadWriteLock.readLock();
static Lock wirte = reentrantReadWriteLock.writeLock();
public static final Object read(String key){
try{
read.lock();
return map.get(key);
}finally {
read.unlock();
}
}
public final Object write(String key,Object value){
try{
wirte.lock();
return map.put(key,value);
}finally {
wirte.unlock();
}
}
}
当读的时候支持多个线程共享,当写的时候先在写的方法进行写的操作,发生互斥锁,写之后其他线程才可以去读。
共享锁:支持多个线程共享读的信息。

深度分析 AbstatctQueneSynchronizer(AQS)
Aqs相当于同步和线程安全的一个框架 提供了两种功能一个是共享锁,独占锁

Synchronized 使用cxq entryList 使用cas 用自旋的方式 去获取锁没有获取到park 得到锁释放之后 unpark。
在aqs内有一个Node的内部类这个相当于ObjectWaiter, 在自旋没有得到锁得情况下把这个类包装成ObjectWaiter 。
Node就相当于一个ObjectWaiter 双向链表的节点。阻塞的方法是park ,
在没有获得同步的情况下就加入到这个链表,为了保证安全采用CAS机制,CAS(compareAndSet)可以当成一种乐观锁,如果是首次是compareAndSetHead,如果是最后一次compareAndSetTail ,CAS是线程安全的,只有通过cas 才证明是真正加入了。
源码: unsafe相当于一个后门 操作内存
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {

private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

/**

  • CAS waitStatus field of a node.
    */
    private static final boolean compareAndSetWaitStatus(Node node,
    int expect,
    int update) {
    return unsafe.compareAndSwapInt(node, waitStatusOffset,
    expect, update);
    }
    }
    非公平锁就是当加载的时候先去获得锁,并不是先去排队,公平锁就是按照队列的顺序来。
    Condition 属于jdk层面的wait和notify
    private Lock lock ;
    private Condition condition ;

public DemonWaitCondition( Lock lock , Condition condition ){
this.lock=lock;
this.condition=condition;
}
@Override
public void run() {
try {
System.out.println("开始执行wait");
lock.lock();
condition.await();//相当于等待
System.out.println("结束执行wait");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
DemonNotifyCondition
private Lock lock ;
private Condition condition ;

public DemonNotifyCondition( Lock lock , Condition condition ){
this.lock=lock;
this.condition=condition;
}
@Override
public void run() {
System.out.println("开始执行notify");
condition.signal();
System.out.println("结束执行notify");

}

public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
DemonWaitCondition demonWait = new DemonWaitCondition(lock,condition);
demonWait.start();
DemonNotifyCondition demonNotify = new DemonNotifyCondition(lock,condition);
demonNotify.start();
}

线程池

java线程池:是在异步或者并发情况下使用最多的,第一降低
了消耗了资源,第二提高了响应效率 ,第三是提高了线程的管
理性
线程池的执行原理:
1 判断线程池的核心线程是否已经满了,如果没有满,创建一个新的来执行任务,如果核心线程都在执行任务,那就进入下一个流程。 
2 判断工作队列是否已经满了,如果没买则创建新的工作队列,如果满了则进入下一个流程。
3 线程池判断是否处于工作状态,如果没有则创建一个工作线程,如果满了则执行饱和策略。

1 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
2 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务
4如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。.

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 如果线程数小于基本线程数,则创建线程并执行当前任务
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
// 如线程数大于等于基本线程数或线程创建失败,则将当前任务放到工作队列中。
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
// 则创建一个线程执行任务。
else if (!addIfUnderMaximumPoolSize(command))
// 抛出RejectedExecutionException异常
reject(command); // is shutdown or saturated
}
}
线程的创建
woker
private final class Worker extends AbstractQueuedSynchronizer 
会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。
// 创建一个可重用固定个数的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//创建一个定长线程池,支持定时及周期性任务执行——延迟执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 创建一个单线程化的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 创建一个可缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 创建自定义线程池
new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());

关闭线程
shutdownNow : 将线程池的状态设置成STOP,然后尝试停止所有的正在执行。 shutdown   :shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

调用线程池关闭之后 isShutdown方法就会返回true 和isTerminaed方法会返回true

线程池的监控;

•taskCount:线程池需要执行的任务数量。
•completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
•largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是
否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
•getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销
毁,所以这个大小只增不减。
•getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的
beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执
行一些代码来进行监控。例如,监控任务的平均执行时间、最大执行时间和最小执行时间等。
这几个方法在线程池里是空方法。

posted on 2020-04-10 08:59  全国学渣代表  阅读(516)  评论(0编辑  收藏  举报