JAVA高并发学习笔记

/ 0评 / 0

有些收获,但是不能完全理解

笔记如下:

---------------------------------

阻塞:占用资源的线程一直不愿意释放资源,当一个线程进入临界区后,其他线程必须等待

非阻塞:允许多个线程同时进入临界区

并发级别:

  1. 阻塞
  2. 无障碍               以下全为非阻塞
  3. 无锁
  4. 无等待

无障碍:

  1. 无障碍是一种最弱的非阻塞调度
  2. 自由出入临界区
  3. 无竞争时,有限步内完成操作
  4. 有竞争时,回滚数据

无锁:

  1. 无障碍的
  2. 保证有一个线程可以胜出

无等待:

  1. 无锁的
  2. 要求所有的线程必须在有限步内完成
  3. 无饥饿的

线程是进程的执行单元

         Thead.stop:线程终止,不推荐使用,会释放所有锁

线程中断       

Thead.inerrput()

Thead.inInterrupted()

         Thead.interupted()

         Sleep()

挂起和继续执行:         均不推荐使用           临界区资源不会被释放

         Suspend:挂起

         Resume:继续执行

                  如果加锁发生在resume()之前,会发生死锁

等待线程结束和先让:

         Yield()       静态方法,释放cpu,重新竞争

         Join()                 插队,等到你结束,在执行  

ps:join(0)无限等待,join(time),等time时间后,不等了

         notifyall()                  java虚拟机c++实现,线程不要轻易使用notifyall,会对整个系统产生影响

守护线程:

         在后台默默的为系统提供支撑服务

         当一个java应用内,只有守护线程时,java虚拟机就会自然退出 !!!!!

         Thead.setDaemon(true);

         Thead.setPiority()设置优先级

Synchroized   加锁 在虚拟机内部实现,使线程安全,注意加同一个锁

         指定加锁对象          synchroized()

         直接用于实例方法        public synchroized void xxx()

         直接用于静态方法

Wati()       进行等待,必须先获得锁才可以等待,会释放锁

                  Ps:必须把锁的控制权拿回来,才会继续执行,e:notify()执行完所有行代码

Notify()    进行唤醒,必须先获得锁才可以 唤醒,

NotifyAll()        进行唤醒所有,同上

Java内存模型和线程安全

原子性:操作不可中断,一旦开始,不会被其他线程干扰

                I++不是原子操作

有序性:在并发时,程序的执行可能出现乱序

可见性:是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道修改这个值

         各种各样的优化导致问题

-server模式进行优化,客户端不特别进行优化

         可能发生指令重排

Happen-before规则:

         程序顺序原则:一个程序内保证语义的串行性

         Volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性

         锁规则:解锁(unlock)必须发生在随后的加锁(lock)前

         传递性:A先于B,B先于C,那么A必然先于C

         线程的start()方法先于他的每一个动作

线程的所有操作先于线程的终结(Thread.join())

线程的中断(interrput())先于被中断线程的代码

对象的构造方法执行结束先于finalize()方法

线程安全概念:

         指某个函数、函数库在多线程环境中被调用时,能够正确的处理各个线程的局部变量,使程序功能正确完成。

无锁:多个线程可以进来,只有一个线程可以成功

         CAS操作:

                                   原子操作

乐观态度进行,总以为自己可以完成操作。

                                   它包含三个参数CAS(V,E,N)  V表示要更新的变量, E表示预期值,N表示新值,仅当V值,等于E值时,才会将V的值设为N,如果V值和E值不同,则说明有了其他线程进行了更新,则当前线程什么也不做。最后CAS返回当前V的真实值。

         Ps:比加锁操作效率更高

AtonicInterger:继承Number类

         For(;;){

         ......

If(compareAndSet(cureet,next))

}

进行死循环等待,根据和设置的数据进行比较,return结束循环

Unsafe:非安全操作,e:根据偏移量设置值,park(),底层的cas操作,非公开的api,在不同版本的jdk,有很大差异。

偏移量:如同C语言的基地址+偏移量操作

AtomicReference:对引用进行修改,是一个模板类,抽象化了数据类型

AtomicStampedReference:stamp唯一(e:时间戳):对过程状态敏感的操作

AtomicInterArray

AtomicIntergerFieldUpdater:让普通变量也具有原子操作

                  AtomicIntergerFieldUpdater  aa=AtomicIntergerFieldUpdater.new Updater(类的反射,“字段名”)

                  aa.incrementAndGet(要操作的类)

AomicReferenceArrary:存对象,8*2^30大小,

                  Doit:进行compareAndSet操作,没有重试,重试写在外层代码

JDK并发包:

各种同步控制工具的使用

ReentranLock:  可重入       

         ReentantLock  lock=new ReentrantLock();

         lock.lock();

         try(

         i++;

)

Finally(

                  lock.unlock();

         )                           ps:别忘放锁,加几次锁,放几次锁

可中断:

         lock.lochInterruptbily()                  //可中断的加锁

         lock.isHeldByCurrentThead()                           //判断是否有锁

         t.interrupt()

         e:

可限时:避免长期等待的一种锁

         Boolean Lock.tryLock(5,TimeUnit.SECONDS) //在有效的实效内,想获得这把锁,成功继续执行,失败执行其他锁(释放自己占用的锁)

公平锁:保证线程先来先拿到锁,后来后拿到锁

         ReenTrantLock(True)//公平锁性能很差

Condition:

         类似于wait()和noitfy();与ReenTrantLock结合使用

         创建对象:

                  Public static ReenTrantLock lock =new ReentrantLock();

                  ...     ...      

Condition condition=lock.newCondition();

awaitUninterruptibly()和await方法基本相同,等待过程中不会响应中断

single()=notify()        singleAll= notify All()

信号量:Semaphore互斥的,排他的  为共享锁

                  允许多个线程进入临界区

Acquire() 获得信号量

acquireUninterrwpibly()获得信号量,不支持中断   

try Acquire()  尝试获取信号量,不尝试等待

try Acquire(long,time) 等多长时间

E:

ReadWriteLock是jdk5中提供的读写分离锁

         读读不互斥

         读写互斥

         写写互斥       

ConutDownLatch倒数计时器

         一种典型的场景就是火箭发射。为了火箭发射前,万无一失,往往还要进行各项设备。

        CountDownLatch end=new CountDownLatch(10);

         End.countDown();    一个线程完成任务执行这个 10 --;

         End.await();                写在所有检查完成后要执行的语句

CycllicBarrier 循环栅栏,这个计数器可以反复使用。

比如,介入我们将计数器设为10  和上一个类似

CyclicBarrie(int,ruunable)  int,参与者数

Await()  等所有参与者都到达时间线后,再进行操作

E:

LockSupport:提供线程阻塞原语

         LockSupport.park():线程停止挂起

         LockSupport.unpark():继续执行

         !!!!!!!Unpark发生在park之前,park不会把线程阻塞

         能响应中断,但是不抛出异常

ReentrantLock实现:应用级实现,不是很底层

         CAS状态:该锁有没有被占用

         等待队列:如果线程没有拿到锁,进入等待队列

         Park():等待队列的线程做park操作,当前一个线程unlock时,该线程unpark

ConcurrentHashMap:高性能的并发解决方案的HashMap

BlockingQueue:阻塞队列   使线程安全,效率不高

                  如果队列为空,读取线程进行等待

                  如果队列为满,写入线程进行等待

ConcurrentLinkedQuene: 高性能的并发阻塞队列  

线程池:把线程进行复用

JDK自带线程池

Runable没有返回值

Calabele有返回值

线程池的种类:
         newfixedThreadPool
线程数固定的线程池

         newsingleThreadExecutor  单一线程线程池

newCachedThreadPool 缓存线程池,根据任务密集度,来扩展线程数量,线程池空闲时,线程逐渐减少

newScheduLedThreadPool:更像计划任务,E:希望每隔5分钟调用什么任务

线程池的执行者:

ThreadPoolExecutor(int 核心线程池大小(标准),int 最大线程池数(上线),long 存活时间,TimeUnit 时间单位,BlockingQueue 阻塞队列(里面是内容) )

         PS:以上四个种类线程池  都是return ThreadPoolExecute

SynchronousQueue同步队列,容量为0,只起到线程交换的作用

简单线程使用:

         Mytask task =new Mytask();

         ExecutorService se= Executors.newfixedThreadPool(5);

         For(int i =0;i<10;i++){

         Es.submit(task);

}

 E:

Submit和execute的区别:

         Execute会返回一个对象

拒绝策略:DiscardPolicy当要处理的事物过多,应选择拒绝一部分,而不是全部放入队列

丢弃策略:rejectedExecution:直接丢弃 ,丢弃最老的一个没有处理的请求:e.getQueue().poll() 

CallerRunsPolicy由调用者执行

线程工厂:为线程设置各种属性

ForkJoin:

把大任务分解成小任务,最后把小任务完成的结果进行收集

Forkjoin  :ctl                                         64bit

AC:活跃线程数-目标并行度    16bit

TC:总线程数-目标并行度               16bit

ST:线程池是否是激活的         1bit

EC:堆栈顶部等待的线程                  15bit

ID:处于顶端的线程ID是多少        16bit

保证数据一致性

Scan:事先帮别人完成任务,尽可能避免产生饥饿现象

         偷别人base做自己top

多线程设计模式:

         单例模式:单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为

                                   E:

Private static Singleton instance=new Singleton();

Public static Singleton getInstance(){

         Return instance;

}

不好控制何时产生实例

防止访问字段创建实例写法:

Private static LazySingleton instance = null;//访问其他字段这行不会被生成

Public static synchronized LazySingleton getInstance(){    //使用同步关键字

         If(instance == null)

                  Instance =new LazySingleton();

         Return instance;

}

更绝的方法:延迟加载

Public class StaticSingleton{

         Private StaticSingleton(){

         System.out.println(“StaticSingleton is create”);

}

Private static class SingletonHolder{

         Private static StaticSingleton instance = new Static Singleton();

         //初始化定义在了静态内部类当中,初始化的时候静态内部类不会被初始化

}

Public static StaticSingleton getInstance(){

         Return SingletonHolder.instance;

}

}

PS:单例的构造函数必须都是private

         不变模式:

一个类的内部状态创建后,在整个生命期间都不会发生变化时,就是不变类

不变模式不需要同步

Public final class Product{

         //确保 无子类

                  Private final String no;

                  Private final String name;

                  ........

                  Puvlic Product(String no,String name ...){//对象被创建时被赋值

                          Super();

                          This.no=no;

                          This.name=name;

                          This.price=price;

}

}

E: String Boolen Byte Character Double Float Integer Long Short

                          新的替换旧的

         Future模式:

                          核心思想是异步调用

                            调用者做其他事情,需要时凭(订单)拿到数据

                           Public interface Data{

         Public String getResult();

}

Public class FutrureData implements Data{

         Portected RealData realdata= null;    //FutureData是RealData的包装

         Protected boolean isReady = false;

         Public synchronized void serRealData(RealData realdata){

         If(isReady){

         Return;

}

This.raeldata=realdata;

isReady=true;

notifyAll();                                                  /RealData被注入,通知getResult()

}

}

Public synchronized String getResult(){   //会等待RealData构造完成

         While(!isReady){

         Try{

         Wait();                                        //一直等待知道RealData构造完成

}catch(InterruptedException e ){

}

}

Return realdata.result;

}

Public class RealData implements Data{

         Protected final String result;

         Public RealData(String para){

                  //RealDat的构造可能要很慢,需要用户等待很久,这里使用sleep模拟

                  StringBuffer sb = new StringBuffer();

                  For(int i =0;i<10;i++){

                  Sb.append(para);

                  Try{

                          //sleep,模拟很慢的过程

                          Thread.sleep(100);

}catch(InterruptedException e)

                                                     }

}

Result=sb.toString;

}

Public String getResult(){

         Return result;

}

}

Public class Client{

         Public Data request(final String queryStr){

         Final FutureData future =new FutureData();

}

New thread(){

         Public void run(){//RealData的构建很慢

         //所以在单独的线程中进行

         RealData realdata = new RealData(queryStr);

         Future.setRealData(realdata);

}

}.start();

Return future; //FutureData会被立即返回

}

主函数:

Public static void main(String [] args){

         Client client =new Client();

         //这里会立即返回,因为得到的是FutureData而不是RealData

         Data data=client.rquest(“name”);

         System.out.println(请求完毕);

         Try{

                                            //这里可以用一个sleep代替了对其他业务逻辑的处理

                                            //在这里处理这些业务逻辑的过程中,RealData被创建,从而充分利用了等待时间

                                            Thread.sleep(2000);

}catch(InterruptedExeption e){

}

                  //使用真实数据

System.out.println(“数据”=+data.getResult());

}

Jdk中有包装和实现 :

生产者消费者模式 :

经典的多线程模式,两个线程之间共享数据,在公共区域进行读写处理

BlockingQueue:

X↓生产者

                                                                                                                  消费者↑

NIO和 AIO

         NIO = NEW I/O新的io标准

         NIO基于块的,以块为基本单位

         为所有原始类型提供Buffer缓存支持

         增加通道(Channel)对象,作为新的原始io抽象

         支持锁和内存映射文件访接口

         提供了基于Slector的异步网络锁

         FileInputStream fin =new FileInputStream(new File(“d:\\demp_buffer.tnp”));

         FileChannel fc=fin.getChannel();

ByteBuffer byteBuffer=ByteBuffer.allocate(1024);

         fc.read(byteBuffer);

         fc.close();

         byteBuffer.flip();

NIO复制文件:

         Public static void nioCopyFile(String rescource,String destination)throws IOException{

                  FileInputStream fis =new FileInputStream(resource);

                  FileOutputStream fos=new FileOutputStream(destination);

                  FileChannel readChannel = fis.getChannel();                                //读文件通道

                  FileChannel writeChannel= fos.getChannel();                             //写文件通道

                  ByteBuffer buffer = ByteBuffer.allocate(1024);                            //读入数据缓存

                  While(true){

                          Buffer.clear();

                          Int len = readChannel.real(buffer);

                          If(len == -1){

         Break; 

         //读取完毕

}

                  Buffer.flip();              //读写转换

                  writeChannel.write(buffer);

//写入文件

}

ReadChannel.close();

writeCHannel.close();

}

Buffer的三个重要参数:位置position,容量capacity,上限limit

         位置:当前缓冲区的位置

         容量:缓冲区的总容量上限

         上限:写:缓冲区的实际上限,它总是小于等于容量,通常情况下和容量相等。

                    读:代表可读取的总容量,和上次写入的数据量相等。

Flip:

                  该操作会重置position,通常,将buffer从写模式转换为读模式时需要执行此方法

                  Flip()操作不仅重置了当前position为0,还将limit设置到当前position的位置。

其他操作:

                  Rewind():将position置零,并清除标志位(mark)

                  Clear():将position置零,同时将limit设置为capacity的大小,并清除了标志mark

总结:

         NIO会将数据准备好后,再交由应用进行处理,数据的读取过程依然在应用线程中完成

         节省数据准备时间(因为Selector可以复用)

AIO:

读完了在通知我

不会加快io,只是在读完后进行通知

使用回调函数,进行业务处理

AsynchronousServerSocketChannel:

                          Server=

AsynchornousServerSocketChannel.open().

bind(new InetSocketAddress(PORT));

                  使用server上的accept方法:

                          Public abstract<A> void accept (A attchment

                                            CompletionHandler<AsynchronousSocketChannel,? Super A> //回调接口

                          Handler);

         -read:    异步:立即返回

                  AsynchronousServerSocketChannel.read()

                          参数ByteBuffer             读到这里去

                                            Long                                   超时时间

                                            TimeUnit

                                            A

                                            CompletionHandler<Integer,? Super A>)   读完之后做这个事情

Hashset存对象,不允许有重复的值

Hashmap存键值对,线程不安全的 可以接受空值

Hashtable存键值对,线程安全

锁优化的思路和方法:         对阻塞的方式进行优化

         减少锁持有时间:

减小锁粒度:

                  将对象,拆成小对象,增加并行度,降低锁竞争

                  偏向锁,轻量级锁成功率提高

                  ConcurrentHashmap拆成若干个hashmap

                          允许多个线程同时进入

         锁分离:

                  根据功能进行锁分离

                  ReadWriteLock

                  读多写少的情况,可以提高性能

  读锁 写锁
读锁 可访问 不可
写锁 不可访问 不可

                  进一步思想:只要操作互补影响,锁就可以分离

                          LinkedBlockingQueue:任务偷窃:从两端进行处理任务

         锁粗化:

                  不断请求锁,也会浪费系统资源

         锁消除:

                  发生在编译器的事情,发现不可能被共享的对象,则可以消除这些对象的锁操作

虚拟机内部对锁的优化

         对象头Mark

                  Mark Word,对象头的比较,32

                  描述对象的hash,锁信息,垃圾回收标记,年龄

         偏向锁:

                  大部分没有竞争的,所以可以通过偏向锁来提高性能

                  偏心的锁,会偏心已经占有锁的线程

                  将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark

                  只要没有竞争,获得偏向锁的线程,在将来进入同步块,不需要做同步

                  当其他线程请求相同的锁时,偏向模式结束

                  -xx:+UseBiasedLocking

                          -默认启用

                  在竞争激烈的场合,偏向锁会增加系统负担

         轻量级锁:

                  BasicObjectLock:嵌入在线程栈中的对象

                  普通的锁处理性能不够理想,轻量级锁是一种快速的锁定办法

                  如果对象没有被锁定

如果轻量级锁失败,表示存在竞争,升级为(重量级)常规锁

在没有锁竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗

在竞争激烈时,轻量级锁会多做很多额外操作,导师性能下降

         自旋锁:

                  当竞争存在时,如果线程可以很快获得锁,那么可以不再OS层挂起线程,让线程做个空操作(自旋)  最后的保障

                  JDK1.6中-xx:+UseSpinning开启

                  JDK1.7中,去掉次参数,改为内置实现

                  如果同步块很长,自旋失败,会降低系统性能

                  如果同步块很快,自旋成功,节省线程挂起切换时间,提升系统性能

总结:偏向锁,轻量级锁,自旋锁总结

                  不是java语言层面的锁优化方法

                  内置于JVM中的获取锁的优化方法和获取锁的步骤

错误案例:

         原因:Int为不变模式

ThreadLocal:

         把锁完全去掉,为每一个线程都提供对象实例

JDK8对并发的新支持:

Esciple 条件断点进行多线程程序调试

         Suspend thead:在线程上断点

         Suspend VM :在虚拟机上断点

LongAdder

         累计器:原子性,性能很好

         和AtomicInteger类似的使用方法

         在AtomicInreger上进行了热点分离

         Public void add (long x)

CompletableFuture

 完成后得到通知

StampedLock

Stamp类似时间戳

         StampedLock的实现思想

Jetty分析:

public Server(@name(“port”) int port)

new Server():

  1. 初始化线程池-QueueTheadPool-execute()方法-BlockingQueue
  2. 初始化ServerConnector
  3. 设置port
  4. 关联Server和Connector

发表评论

电子邮件地址不会被公开。 必填项已用*标注