并发和虚拟机小结

时间:2022-12-27 12:29:30
一.JAVA内存区域划分和内存溢出异常
1.通常对于java来说,他将内存区域划分为这么几块:
a.程序计数器:首先我们程序在执行的时候,会有一个指针来指向程序下一步需要执行的指令,来满足程序代码顺序,循环,异常等,这块区域是唯一不会出现OutOfMemoryError的区域。
b.虚拟机栈:虚拟机在执行程序的时候,会分为不同的线程,
c.内地方法栈(native):和虚拟机栈类似,只是这部分是由本地方法调用所产生的。
d.堆区(heap):这部分区域是几乎所有实例和数组的容身之地
e.方法区:在对一个类进行加载之后,这个类的方法区的代码信息,静态成员,常量信息会存储在这块区域
f.常量池:这部分存储静态成员信息,常量信息,以及还有可以将String动态添加到常量池,string.intern()。

%常量池中 主要存在两大类文件 字面量Literal和符号引用Symbolic Reference
字面量就是接近我们java语言层面的常量概念,文本字符串,声明为final类型的常量值
符号引用则是类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。
g.非堆(nonheap):这部分区域不在java内存区域划分中,它受本机内存限制,目的主要是为了提升性能,避免在虚拟机内存和物理内存上进行来回复制。它由ByteBuffer在内存中指向物理内存,主要是调用native方法。

2.针对不同区域,可能会发生不同情况的内存溢出情况




二.GC收集器和内存回收策略
三.内存分析工具


四.类文件结构

1.同步指令
方法级同步是隐式的,虚拟机采用管程monitor来处理,也就是两条指令,monitorEnter,monitorexit指令,当我们对一个方法进行调用的时候,首先回去检测常量池方法表结构中ACC_SYNCHRONIZED访问标志来得知一个方法有没被设置,如果被设置了,那么这个虚拟机必须等到成功持有管程,然后才能执行方法,最后不管是成功执行完还是异常执行完方法,这个管程都会正常释放。

2.虚拟机规范了有且只有五种情况需要立即对类进行初始化过程
1.使用new(新建一个实例) putstatic(设置一个静态变量) getstatic(获取一个静态变量)  invokestatic(调用一个类的静态方法的时候)
2.使用反射调用一个类的时候
3.类加载main方法入口时
4.类初始化一个类时,如果发现其父类还没进行初始化,则需要先触发其父类的初始化过程。(如果是接口,则并不要求其父接口全部完成初始化,只有在真正使用到该父接口的时候才进行初始化)
5.1.7动态语言支持时,最后解析结果ref_getstatic,ref_putstatic,ref_invokestatic,并且这个方法的句柄所对应的类并没有进行初始化的话,那么则需先进行初始化。

不会触发初始化的过程
1.子类引用父类的静态字段,子类不会初始化。
2.a类引用b类的静态常量final,并不会触发b类的初始化,因为虚拟机常量传播优化,这个时候会把b类的常量复制到a类常量表中,以后对b类的常量引用都转化为b类自身的引用了,这个两个类在编译完成之后就失去联系了。
3通过数组来定义引用类,并不会触发次类的初始化。

五.虚拟机类加载机制
1.加载
A.通过类的全限定名来获取一个二进制流
B.通过该二进制流的静态存储结构转化为方法区运行时数据结构
C.生成一个Class对象,作为访问这个类的入口


2.验证
a 文件格式验证 cafebaby标志 版本 utf-8等
b 元数据验证 是否有父类,是否继承了final类,是否实现了接口或抽象类所有的方法,final字段不能覆盖
c 字节码验证 
d 符号引用验证 通过全限定名能够找到对应的类 在指定类中是否存在符合方法的字段描述符

3.准备 正式为类变量static变量分配内存以及设置初始值的阶段,而实例变量则会随着对象实例化分配在堆中 ,假如是final型则会根据具体指在准备阶段赋值
4.解析 是虚拟机将常量池中的符号引用替换为直接引用
5.初始化 根据程序员通过程序制定的主观计划去初始化类变量和其他资源,初始化阶段是执行类构造器<clinit>()方法的过程,它由编译器自动收集类中所有的类变量的赋值操作和静态语句块的语句合并而成
6.使用
7.卸载


2018.04.30
1.并发和并行
2.控制并发访问资源数量

###线程管理

线程的创建和运行
1.实现线程的三种方式
  public class MyThread extends Thread ->实例启动start()
  public class MyRunable implements Runable ->重写run()方法,实现具体逻辑,作为参数注入 到thread中,进行启动
  public class MyCallable implements Callable -> 重写call()方法,获取返回值,作为参数提交到线程池中,通过Future来获取其结果,其完成状态,是否取消
线程信息的获取和设置
  Thread类中提供了一些方法以供获取到当前线程的id,islive,name,priority,status等状态
线程的中断
  Thread类提供了stop方法来停止线程,但是这个方法是线程不安全的。后面提供了interupted()方法来中断该线程
线程中断的控制

线程的休眠和恢复
Thread类提供了sleep()方法来使当前线程放弃cpu资源,不占用cpu时钟,但是这个并不会释放当前的锁,相反wait()方法会释放当前所持有的锁,让其他线程来持有该锁进行业务逻辑处理,resume来唤醒当前睡眠的线程
等待线程的终止
在多个线程资源进行协作的时候,其他线程如果需要等待某个资源进行完成之后才能进行,也就是说必须等到这个初始化线程完成,这个时候就可以用到initicialThread.join()的方法来处理,这个就会等到这个初始化线程完成之后,其他线程才能继续执行。

Thread类提供了terminated()方法来终止一个线程
守护线程的创建和执行
通常我们会有一个辅助线程来处理这个业务逻辑的话,它在职责上主要是为了其他普通线程提供服务,他们不能来做和用户交互性很即时的操作,我们可以将一个线程设置为setDeamen(true),这个方法可以将这个线程设置为后台线程。
线程中不可控异常的处理

线程局部变量的使用

线程的分组

线程组中不可控异常的处理

使用工厂类创建线程
ThreadFactory() return new Thread(Runnable r)

###线程同步的基础
1.使用synchronized来实现同步
synchronized 属于虚拟机内置锁,在使用这个内置锁的时候,它可以使用在方法上,代码块上面,但是使用该方法,我们通常需要提供一个对象来作为锁,这个对象通常是this,也就是当前对象。
synchronized(this){}

2.使用非依赖属性来实现同步。

3.使用同步代码块中使用条件
  由于在使用同步锁的时候,我们一定要获取当前锁,但是当持有锁进入到当前逻辑代码块里面的时候,我们发现当前逻辑不能继续往下进行的话,这个时候,我们可以使用锁对象.wait()方法,来等待并释放当前持有的锁
  synchronized(this){
         this.wait();
  }
4.使用锁来实现同步
  如果在业务上,我们需要更加灵活的锁的时候,这个时候,我们可以使用JUC中提供的Lock,它提供了tryLock(TIme)方法,可是实现非阻塞的逻辑,另外这个可以实现中断。
Lock lock = new ReentrantLock();
Try{
   lock.lock()
}finally{
   lock.release();
}

5.使用读写锁来实现同步
在业务上,读写锁主要是利用在业务上存在读写概率不一致的情况下进行设计的策略,通常,我们对同一个关键资源进行读写,这个资源可以被多个线程同时读,但是只能由一个线程对它进行写操作。针对这种情况,java提供了读写重入锁
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock= lock.readLock();
Lock  writeLock= lock.writeLock();

Try{
   lock.lock()
}finally{
    lock.release();
}
6.使用锁来使用多个条件
当我们使用锁的时候,可能需要多个条件来对这个锁进行控制,这个时候使用内置锁是不能解决问题的,内置锁只能支持一个条件。
Lock lock = new ReentrantLock();
Condition ca = lock.newCondition();
Condition cb = lock.newCondition();

Try{
   lock.lock();

    ca.await();
    ca.signl();
    ca.signlAll();

   
}finally{
    lock.release();
}

7.修改锁的公平性



###线程同步辅助类

1.资源的并发访问控制
  通常我们会对临界资源区进行访问,假如我们想对其访问数量进行控制的话,我们可以采用Semaphore信号量来处理,它是一个计数器,它可以控制一个或多个资源对临界区的访问。
  CountDownLatch : 通常在线程之间进行交互的时候,一个线程需要等待另外一组线程完成操作之后。
  CyclicBarrier:它允许多个线程在集合点相互等待。
  Phaser:它把并发任务分为多个阶段完成。开始下一阶段前,当前阶段中所有的任务必须全部完成。
  Exchanger :允许两个线程之间进行数据交换

2.资源的多副本的并发访问控制

3.等待多个并发事件的完成

4.在集合点的同步

5.并发阶段任务的运行

6.并发阶段任务中的阶段切换

7.并发任务间的数据交换




###线程执行器
在我们平常使用直接使用new线程的方式来创建线程,这样在大量创建线程的过程中会消耗大量资源,这个时候可以考虑使用线程池来缓存线程,避免了一部分创建线程所带来的消耗。另外,如果需要去大量的管理这部分线程的生命周期,这个也是比较困难的,这个时候为了避免这些劣势,提供了一个 excutor framework框架,提供了 executor 和 executorService接口,以及实现了这两个接口的ThreadPoolExecutor.为了避免用户错误调用,这个提供了四种典型的用法,提供了一个工厂类,Executors.
1.创建线程执行器
   我们可以选择自定义线程执行器
2.创建固定大小的线程执行器
  ExecutorService exe =  Executors.newCachedThreadPool() 缓存线程,进行线程重用减少新建线程所消耗的时间。
                                    =  Executors.newFixedThreadPool() 超过线程最大数,不再创建新的线程,剩余的任务将阻塞直到执行器有空闲的线程可用,避免线程过大给应用程序带来性能不佳的情况。
                                    =  Executors.newSchedualThreadPool() 执行定时任务和周期性执行任务 提交的任务 任务执行前需要等待的时间 等待的时间单位
                                    =  Executors.newSingleThreadPool() 保证了线程串行执行
                                    =. Executors.new
3.在执行器中执行任务并返回结果
Future<?> future = exe.submit(callable);
通常对于我们需要返回结果的线程,我们可以使用实现Callable的方式,将这个线程提交到线程执行器中去,这样的话,线程执行器会返回一个Future对象,这个对象可以管理我们提交的这个任务的生命周期,我们可以cancel,isDone来检查任务是否已经完成,get这个方法一直阻塞到任务完成,等方式来获取这个任务的状态以及控制这个任务的进行
4.运行多个任务并处理第一个结果,或者返回所有结果
通常我们假如仅仅想获取到提交任务列表中最快的一个的话,我们可以使用Future<?> future = exe.invokeAny(list(callable));
5.在执行器中执行延时任务,周期性执行任务

6.在执行器中取消任务,控制任务的完成


7.在执行器中分离任务的启动与结果的处理

8.处理执行器中被拒绝的任务


###并发集合
Blocking Collection:在对这个集合进行添加和删除的操作的时候,这时被调用的操作不能立即执行,调用这个方法的线程将会一直阻塞直到这个方法可以被成功执行
NoBlocking Collection:在调用这个方法的时候,这个时候如果方法不能立即执行,这个时候则返回null或者异常,但是调用这个方法的线程不会阻塞。
1.使用非阻塞式线程安全列表
 ConcurrentLinkedDeque
2.使用阻塞式线程安全列表
LinkedBlockQueue
3.按优先级排序的阻塞式线程安全列表
PriorityBlockingQueue 放入的所有元素必须实现Comparable()接口
4.带延迟元素的线程安全列表
DelayQueue 需要实现Delayed接口
5.使用线程安全可遍历映射
ConcurrentSkipListMap 使用键值来排序所有元素,除了提供返回一个具体元素的方法外,这个类也提供了获取子映射的方法
6.使用原子变量
AtomicLong