多线程总结-java

Java 多线程学习

Java-Interview/MD/collection/HashSet.md

一、线程基础

 1.线程概念、好处

      概念线程是操作系统调度的最小单元,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程中高速切换,让使用者感觉线程在同时执行。

      好处:(为什么使用多线程)

     1)充分利用多处理器核心。将计算逻辑分配到多个处理器核心上,就会显著减少程序运行时间,随着更多的处理器核心的加入变得更有效率。

     2)多线程有更快的响应时间。将对数据一致性要求不高的业务逻辑放到多线程处理,使相应用户请求的线程尽快完成,缩短响应时间。

    3)更好的编程模型。Java为多线程编程提供了良好考究并且一致的编程模型,是开发人员可以专注于问题解决上。

 2.线程状态

        6种状态:

              新建,

              就绪,

              运行,

            阻塞(等待,超时等待): 阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块时的状态。 而阻塞在java.util.concurrent包中lock接口的线程状态确实等待状态(因为Lock接口使用了 LockSupport类中方法)。

死亡。              

 3.线程优先级

        通过 setPriority()来设置,优先级从0-10,默认为5,优先级越高分配到的cpu时间片次数越多。 若频繁阻塞(IO,休眠)的线程需要设置较高的优先级,偏重计算 (需要较多cpu时间的)要设置较低的优先级,确保处理器不会被独占。一些操作系统会忽略设置。

 4.Demon线程

       是一种支持性线程,主要被用作程序中后台调度以及支持性的工作。当虚拟机中不存在非Daemon线程时,java虚拟机会退出。可通过 Thread.setDaemon(true)将线程设置为Daemon。(要在启动线程之前设置) java的GC就是一个守护线程;

5.Sleep,wait,notify等常用方法解析

  1. Sleep: 是Thread类的静态方法,使当前线程放弃cpu,不会占用系统资源,让其他线程占用,如果是在同步块中,则不会释放对象锁;
  2. Wait()是对象的方法,要在sychronazed中使用,会放弃对象锁,进入等待队列,直到线程被notify唤醒;
  3. Notify()和wait()方法一起使用,完成线程间通信,但调用notify后,会唤醒等待队列中的一个线程,使线程处于就绪状态。

上下文切换

        指cpu为每个线程分配时间片(通常为几十毫秒),cpu通过不停地切换线程来执行任务。在线程切换前要保存任务执行的状态,任务执行后要保存任务执行后的状态,任务从保存到加载的过程就是一次上下文切换。

多线程不一定快,受资源限制的影响(硬件和软件)。

资源限制的影响:

        加速代码执行将串行执行部分改成并发执行,有可能会因为资源限制,增加上下文切换和资源调度时间,反而达不到提速的目的。

解决方案:

       硬件资源: 并行计算,像hadoop yarn并行存储计算,将任务分配到多台主机。

       软件资源:   使用连接池,将资源复用。如:数据库和socket连接复用,webservice接口只建立一个连接。

6.启动终止线程

  1. 启动线程的方式: 调用Start() 方法
  2. 线程如何中断: Thread.interrupt() 中断
  3. 如何安全的终止线程: Thread.stop() stop(),resume(),suspend()方法都被废弃了,它们会立即停止线程运行,但不会释放占有的资源(锁),会占着资源引发死锁。 Interrupt()方法停止线程,不会立即终止线程,只会打一个中断标记位。

          A) 用 interrupt()方法中断来停止线程。

            Interrupted() 静态方法,判断当前线程是否被中断; 执行后将具有状态标志清楚为false;只是在当前线程打了一个停止标记,并没有真正停止线程,要停止线程要结合isInterrupted来判断; 

           Isinterrupted() 普通方法,判断线程是否被中断。执行后不会清除状态标志。

              (1.在线程睡眠时中断线程,会抛出中断异常;2.不睡眠,直接启动线程,然后中断,会直接停止线程;3.用return , if(this.isInterrupted()) return; 直接返回,不抛出异常)

           B)用volatile boolean变量来标记是否需要停止该线程。

     public void run(){while(flag &&  !Thread.currentThread().isInterrupted()){ i++;}}

     Public void cancle(){ On=false; }

    线程在睡眠中被 interrupted()时,会抛出interrupted异常; 如果在sleep()之前中断,则不会抛异常.

     线程间的协作通信

  1. volatile 和 Synchronized

volatile 是轻量级的synchronized,没有上下文切换和调度,比Synchronized执行成本会低;保证了线程中共享变量的内存可见性, 对声明为volatile的变量进行写操作时,jvm会想处理器发送lock前缀的指令,使这个变量的所有缓存行都回写到系统内存,根据缓存一致性协议,将数据刷新成最新值。

。Synchronized 可以为普通方法,静态方法,代码块加锁,通过对加锁对象的 monitorenter 进入锁,执行锁定方法,然后monitorexit 退出锁。

  1. 生产者消费者模型

 

  1. Join方法

等待线程执行结束

可以用来控制多个线程的执行的先后顺序

  1. ThreadLocal线程变量详解

线程变量,可以通过set()为线程绑定一个值,然后通过get()取出值,用于耗时统计等;

  1. 实战应用
  1. 连接池

 

  1. 线程池

作用:1.减少了频繁撞见销毁线程的系统资源开销。2.面对过量任务的提交能够平缓的劣化。

原理: 一个工作队列连接着工作线程和客户端线程,当客户端线程将job提交到工作队列,然后返回,工作线程从队列中拿出任务去执行。当工作队列为空时,工作线程阻塞 wait(),当有客户端提交job后,唤醒工作线程notify()。

 

 

 

  1. 如何计算合适的线程数

 

  • 线程安全
  1. 概念
    1. 性能与可伸缩性
    2. 解决死锁
    3. 死锁定义
    4. 如何让多线程下的类安全
    5. 类的线程安装性定义
    6. 实战:实现一个安全的单例模式
  2. Java锁详解
  1. Volatile关键字
      1. 使用场景 :修饰变量,为了保证多线程下的内存可见性
      2. 指令重排序 (编译器的重排序和cpu指令的重排序)
      3. 内存语义: 对总线加锁,当处理器中的缓存与总线中的不一致后,为了保证缓存一致性,处理器的缓存会失效。总是读到最新的值。
  2. 原子操作的实现原理
      1. CAS机制 compareAndSave(先与旧值比较,如果不同就)
      2. CAS的ABA问题

锁:用来控制多个线程访问共享资源的方式,一个锁能够防止多个线程同时访问共享资源,如独占锁,有些锁可以同事访问共享资源,如读写锁。

Java 5 之后,有了Lock接口,使加锁更加方便。

Synchronize 关键字加锁,只能先获取再释放,流程固化了,扩展性不足,隐式加锁。

Lock接口实现的锁,拥有锁获取,释放的可操作性,可中断的获取锁和超时获取锁等多种synchronized 关键字所不具备的同步特性。

  1. Synchronized 的实现原理

 

  1. Lock接口
  2. 队列同步器 AbstractQueuedSynchronized(抽象类),用来构建锁或者其他同步组件的基础架构。

同步组件要继承同步器类,实现里面的方法来管理同步状态。

锁面向使用者; 同步器面向锁的实现者,隐藏了同步状态管理,线程排队,等待,唤醒等底层操作。

同步器的原理:  维护一个同步队列,同步对列中的所有节点是等待进入锁的线程。

    独占:当线程要获取锁的时候,先将线程封装成node,加入同步队列尾部(为了保证线程安全用compareAndSetTail()方法添加,如果添加失败则一直循环尝试添加,成功后则返回),加入同步队列的节点一直自旋,当同步队列的头结点出队(执行完成)或者阻塞线程被中断时,会唤醒node节点,此时node节点判断自己的前节点是否是头结点,如果是,则获取同步状态,如果获取成功则结束自旋,将当前node设置成头结点。

 

中断: synchronized: 如果在线程进入synchronized块中以后中断,则线程会修改中断标记,但会阻塞在同步块中,等待锁释放。

Lock: acquireInterruptibly(int arg) 在当前线程等待获取同步状态时,被中断,立刻返回,抛出Interrupted异常。

    超时获取:当超时时间小于1000纳秒时,将不会再超时等待,而是进入快速自旋。

 

ReenTrantLock 可重入锁,可以在进入锁后,继续加锁,不会自己阻塞自己。synchronized支持重入锁。

再次获取锁:通过判断当前线程是否已经获取到锁来获取同步状态,如果是再次获取锁的请求,则将同步状态增加。

释放锁:减少同步状态值。

当同步状态值变为0后,tryRelease()才会放回ture,将占有线程置为null,并返回true,表示释放成功。

 

LockSupport 工具 park() unpark()方法;

Condition 接口: 每个condition维护一个等待队列,存储调用await()后进入等待状态的线程(同步队列中的头节点 –> 等待队列的尾节点)。 当调用signal()方法后,等待队列的节点将会唤醒(会唤醒等待队列中等待时间最长的节点,即首节点),然后移到同步队列中自旋等待竞争锁资源。

一个锁中可以创建多个condition对象,用于线程间通信。

 是AbstractQueuedSynchronized的内部类,通过Lock lock = new ReetrantLock(); Condition  condition = lock.newCondition()来获取;每个condition都有一个等待队列,用来实现等待通知功能。

 

Lock.lock()

    Condition.await() //会相应中断

Lock.unlock();

   

 

  • Java常用并发工具接口和类源码分析
  1. CountDownLatch

用于计数,

  1. CyclicBarrier
  2. Semaphore
  3. ConcurrentHashMap

线程安全的map,使用锁分段技术来提高并发访问效率;

http://www.importnew.com/28263.html

这里面看着很复杂,先把基本概念记一下:

数据结构:

由segment数组结构和HashEntry 数组结构组成; segment是一个重入锁,hashEntry存储键值对; 一个ConcurrentHashMap中包含一个segment数组, segment数组的每个元素中都守护一个HashEntry数组,在修改hashEntry数组数据时要首先获取segment锁;

初始化segment数组:

    initalCapcity :map的初始大小

loadFactor :每个segment的加载因子

concurrencyLevel :用来计算segment个数(ssize 是小于它的最大的2的倍数,默认是16)

段偏移量segmentShift  

段掩码 segmentMask

 

  1. Fork/Join

Java7提供的一个并行执行任务的一个框架,把一个大任务分割成若干个小任务,最终汇总每个小任务的结果后,得到大任务结果的框架。

设计思想:

任务拆分:需要一个Fork类,把大任务分割成小任务,直到分割出的子任务足够小。

    执行任务并合并结果:分割的子任务放到一个双端队列中,然后几个启动线程分别从双端队列中取任务并执行。子任务执行完后,会将结果放到一个队列中,启动一个线程从队列中拿数据,然后合并这些数据。

每个工作线程出来一个任务队列,当一个线程的任务队列执行结束之后,可以去其他线程的任务队列中拿任务执行。(work Stealing工作窃取,其它 ThreadPoolExecutor并没有这种功能,需要每个线程处理自己的任务。)

       ForkJoinPool

ForkJoinTask

需要从谢一个compute()方法,首先判断任务大小是否合理,如果很大,则进行拆分(递归调用),如果合理则定义计算逻辑。

 

实现原理:

    ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,

ForkJoinTask数组: 负责存放提交给ForkJoinPool的任务。

ForkJoinWorkerThread数组:负责执行任务。

    ForkJoinTask类: fork() 会调用 ForkJoinWorkerThread的 pushTask()方法,把当前任务存放到ForkJoinTask数组中,   然后调用ForkJoinPool的signalWork()方法唤醒或者创建一个工作线程来执行任务。

 

  1. Hashmap
  2. List
        • 线程池和Executor框架
  1. 线程池ThreadPoolExecutor使用

https://blog.csdn.net/u013851082/article/details/53516777

 

线程池都有哪些:

工具类Executors 中可创建3种线程池:

1ExecutorService es = Executors.newSingleThreadExecutor(); 

    2static RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();

    static ThreadFactory threadFactory = Executors.defaultThreadFactory();

    // 根据各个机器 合理地估算线程池大小

    static ThreadPoolExecutor executorPool = new ThreadPoolExecutor(10, 50, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, rejectionHandler);

 

传递的参数:

int corePoolSize:核心线程数;

          int maximumPoolSize:最大线程数

          long keepAliveTime:超过corePoolSize的线程在没有执行任务之后,最多保持都长时间被销毁。

          TimeUnit unit:时间单位

BlockingQueue<Runnable> workQueue:用于存储任务的阻塞队列

RejectedExecutionHandler:ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时,execute()方法将要调用的Handler

 

newFixedThreadPool

       可重用固定线程数的线程池;调用execut()犯法后,执行过程如下:

当线程数<corePoolSize时,每提交一个任务就会创建一个线程来执行该任务。

当线程数=corePoolSize时,提交的任务会被放到任务队列中等待执行。

当任务队列满后,会创建新的线程来执行后来提交的任务,知道线程数达到maxMumPoolSize

当队列满了,并且线程数达到maxMumPoolSize后,再次提交了任务后,会由拒绝策略来处理(有的是抛异常)

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。

有界队列和无界队列的区别:

       有界队列:

              需要设置queue 大小,吴国不设置,默认是Integer.MAX_VALUE(无界队列)则只有corePoolSize一个参数起作用。

 

SingleThreadExecutor:

   使用单个工作线程的Executor;

   当运行的线程小于corePoolSize时,则创建一个新线程来执行任务。

    当当前运行线程数=corePoolSize时,将新任务提交到阻塞队列中。

    然后,线程循环向队列中去任务执行。

 

newChechedThreadPool:

 使用SychronousQueue 阻塞队列,不存储元素,每个Put操作之前都要执行tabe()操作,SybchronousQueue队列起到一个传递任务的作用。吞吐量很大(单位时间传递的数据量)

    CorePooleSize=0

    MaxPoolSize=Integer.MAX_VALUE;

当执行SynchronousQueue.offer()提交任务的时候,要先看maxMumPool中是否有空闲线程在做poll()操作,如果有则直接提交;

若没有空闲线程或者maxmunPool中线程为空,则要新建一个线程,来执行任务。

当线程执行完后,会执行poll()操作,如果在keepAliveTime时间之前有任务提交,则执行,若没有任务提交,则把超过 keepAliveTime的空闲线程终止。

 

  1. 线程池ThreadPoolExecutor
  2. 原理
  3. Executor框架
    1. 组成部分

 

    1. Executors
    2. FutureTask
    3. CompletionService

 

 

--------------------------------分割线---------------------------------------

Volatile:

内存可见性: 指当一个线程修改变量值时,其他线程能够及时地看到修改。

实现原理:通过将java代码转换成汇编,发现:lock 指令,即 JVM会想处理器发送一条lock前缀指令,将这个变量所在的缓存行的数据写到系统内存,同时为了保证各个处理器缓存的一致性,会将其他cpu里缓存的该内存地址的数据设为无效。

Synchronized:

每个对象都可以作为锁,

    普通方法: 锁是当前实例对象;

    静态方法:锁是当前类

    同步方法块:锁是synchronized里配置的对象。

 

LockSupport 类:

    该类作用类似 java.util.concurrent.Semaphore信号量通过许可证来进行线程通信。ParK() 和unpark()方法,类似于wait()和notify()方法,但是LockSupport 有许可证的概念,unpark调用位置无要求,而wait()需要再notify()之后调用才能被唤醒,并且只能在synchronized同步代码块中才能被调用。

 

Happends-before是JMM(java内存模型的规则,定义了java多线程操作的有序性和可见性,防止了编译器重排序对程序结果的影响。)

HB有哪些规则:

  1. 程序次序:一个线程内,按照代码顺序,书写在前面的操作先执行。
  2. 锁定规则:在监视器锁上的解锁操作必须在同一个监视器上的加锁操作之前执行。
  3. Volatile变量规则:对一个变量的写操作先行发生于后面读操作。
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作;
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

 

 

数据库为什么不用Btree二叉树,而是用B+tree:

    由于当数据量很大时,索引也会很大,不能将索引存在内存中,需要将索引存储在磁盘上的,逐一加载磁盘页,每个磁盘页对应一个节点。这样会使二叉树的磁盘IO 次数在极端情况下由树高决定。

B+tree 每个节点可以存储多个数据,由树阶决定,在查询时,会在内存中进行比较,减少了磁盘IO,是矮胖型树形结构,使相同数量的key在B+tree中生成的节点远小于b-tree,相差的节点数就是减少的磁盘io数量。

红黑树:(左旋,右旋)

    右旋:

 

 

 

内部类和外部类:

内部类能够访问外部类的所有方法和属性;

外部类如果访问内部类的方法需要先创建成员内部类的对象,然后再通通过对象调用方法。

HashTable 是用的Synchronized 来加锁,效率很低。

Hashtable或者Collections.synchronizedMap(hashMap),这两种方式基本都是对整个hash表结构做锁定操作的,这样在锁表的期间,别的线程就需要等待了,无疑性能不高。

 

http://www.importnew.com/16138.html

Hashmap 优化:

装填因子Load factor a=哈希表的实际元素数目(n)/ 哈希表的容量(m) a越大,哈希表冲突的概率越大,但是a越接近0,那么哈希表的空间就越浪费。 一般情况下建议Load factor的值为0-0.7,Java实现的HashMap默认的Load factor的值为0.75,当装载因子大于这个值的时候,HashMap会对数组进行扩张至原来两倍大。

 

冲突解决:

       开放定址法:

       链表法:

指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

 

Unsafe : https://www.cnblogs.com/pkufork/p/java_unsafe.html

 

 

 

 

    在项目中遇到的问题:

       1.Process 类

    在扫描节点的时候,执行启动脚本后会出现界面卡住的情况,然后排查错误,jstack打印线程日志,发现在在process.waitfor()方法中卡住。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值