多线程笔记

1.线程的6种状态(Thread.State)

  (1)New(新创建):

  new新线程,还未运行。

  (2)Runnable(可运行)

  调用start方法后。

  (3)Blocked(被阻塞)

  当前线程试图获取内部的对象锁但该锁被其他线程持有时,该线程进入阻塞状态;当其他线程释放该锁,且线程调度器允许本线程持有它的时候变成非阻塞状态。

  (4)Waiting(等待)

  当线程等待另一个线程通知线程调度器某个条件时,它自己进入等待状态。如执行(Object)wait、(Thread)join方法 ,或是等待java.util.concurrent库中的Lock或Condition时。

  (5)Timed Waiting(计时等待)lock

  执行带有超时参数的方法时,如(Thread)sleep、(Object)wait、(Thread)join、(Lock)tryLock、(Condition)await。 

  (6)Terminated(终止)

    a.run方法正常退出而自然死亡;

    b.由于未捕获的异常导致run方法异常退出而意外死亡。

2.线程属性

2.1线程优先级

MIN_PRIORITY=1,MAX_PRORITY=10,NORM_PRIORITY=5

当线程调度器有机会选择新线程时,优先选择优先级高的。

线程优先级是高度依赖于宿主系统的,Java线程优先级映射到宿主系统时可能更多也可能更少。

2.2守护线程

setDaemon(true);//为其他线程提供服务;在线程启动之前调用

注:守护线程应该永远不去访问固有资源,如文件、数据库等,因为它可能会在任何时候甚至一个操作的中间发生中断。

2.3未捕获异常处理器

线程的run方法不能抛出任何被检测的异常,而未检测异常又会导致线程终止。

不需要用catch处理可以被传播的异常,在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。

3.同步

3.1原因:多线程共享同一数据

3.2实现方式

方式(一)synchronizd关键字

方式(二)java.util.concurrent.locks.Lock接口,实现类有:

  1. ReentrantLock 
  2. ReentrantReadWriteLock.ReadLock
  3. ReentrantReadWriteLock.WriteLock

注:解锁(unlock)语句应在finally块中。

3.3锁对象

Reentrantlock();

锁是可重入的,即线程可以重复获得已持有的锁,锁保持一个持有计数来跟踪lock方法的嵌套调用。

3.4条件对象(条件变量)

原因:要用一个变量来管理那些已进入临界区,即获得了锁却不能做有用工作的线程。

Condition c=bankLock.newCondition();

当条件不满足时,调用c.await()方法,线程进入该条件的等待集。

注意与等待锁的线程区分:当锁可用时,该线程不能马上解除阻塞,相反,它要阻塞直到其他线程调用同一条件的signalAll()方法。

signal()方法随机解除等待集中某个线程的阻塞状态。

小结:

提供高度的锁定控制。

  1. 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码;
  2. 锁可以管理试图进入被保护代码段的线程;
  3. 锁可以拥有一个或多个相关的条件对象;
  4. 每个条件对象管理那些已进入被保护代码段但还不能运行的线程。

3.5 synchronized关键字

Java中的每个对象都有一个内部锁,并且该锁有一个内部条件。

wait()/notifyAll()方法相当于condition.await()/signalAll()方法。

静态方法也可以声明为synchronized(类对象的内部锁)。

所以,synchronized静态方法有什么意义吗?

内部锁和条件的局限性:

  1. 不能中断一个正在试图获得锁的线程;
  2. 试图获得锁时不能设定超时;
  3. 每个锁仅有单一条件,可能是不够的。

建议:既不使用Lock/Condition,也不实用synchronized;推荐使用java.util.concurrent 包中的一种机制。

3.6 同步阻塞

synchronized(obj){

...

obj.method1();

obj.method2();

}

又叫客户端锁定,必须保证(obj)类的所有可修改方法都使用内部锁。

因此,该方法很脆弱,不推荐使用。

3.7 监视器

在程序员不需要考虑如何加锁的情况下保证多线程安全。

监视器的特性:

  1. 监视器是只包含私有域的类;
  2. 每个监视器类的对象有一个相关的锁;
  3. 使用该锁对所有的方法进行加锁;
  4. 该锁可以有任意多个相关条件。

Java对象的synchronized类似监视器,但不同(安全性下降):

  1. 域不要求必须是private;
  2. 方法不要求必须是synchronized;
  3. 内部锁对客户是可用的。

3.8 volatile域

提供一种免锁机制;编译器和虚拟机知道该域可能被另一个线程并发更新。

不提供原子性。

3.9 原子性

如果对共享对象除了赋值之外没有别的操作,可以将其声明为volatile。

3.10 死锁

两个或两个以上的线程在执行过程中,因争夺资源而产生的一种互相等待的现象。

产生死锁的4个必要条件:

  1. 互斥条件:一个资源同时只能有一个线程访问;
  2. 请求与保持条件:一个线程请求阻塞时,对于已获得的资源保持不放;
  3. 不可剥夺条件:一个线程获得的资源在只用完之前不能被剥夺,只能在使用完毕后释放;
  4. 循环等待条件:若干线程形成头尾相接的循环等待资源关系。

解决死锁的方法:

  1. 预防死锁:设置限制条件,破坏产生死锁的必要条件(之一);效率降低;
  2. 死锁避免:允许前三个必要条件,但通过明智的选择确保永远不会到达死锁点;比死锁预防允许更多的并发;
  3. 死锁检测:不须采取任何限制措施,允许发生死锁。但通过系统设置的检测机构及时检测死锁的发生,并精确地确定死锁相关的线程和资源,采取相关措施将死锁清除;
  4. 死锁解除:与死锁检测相配套,常用方法:撤销或挂起一些线程,以便回收资源分配给已阻塞的线程;死锁检测和解除会获得较好的资源利用率和吞吐量,但实现难。

3.11 读写锁

  • java.util.concurrent.locks.ReentrantReadWriteLock 

对所有的获取方法加读锁——readLock()

对所有的修改方法加写锁——writeLock()

4. 阻塞队列

生产者、消费者

多线程程序中,使用不会抛出异常的poll、peek和offer方法。

 

  • java.util.concurrent.LinkedBlockingQueue<E> 容量无上界,但也可以指定最大容量。
  • java.util.concurrent.LinkedBlockingDeque<E> 双端版本
  • java.util.concurrent.ArrayBlockingQueue<E> 构造时需要指定容量,并且有一个可选参数用来指定是否需要公平性。
  • java.util.concurrent.PriorityBlockingQueue<E> 是优先级队列,而非先进先出。

5.线程安全的集合

java.util.concurrent包提供了映射表、有序集和队列的高效实现,如:

  • java.util.concurrent.ConcurrentHashMap<K,V>
  • java.util.concurrent.ConcurrentSkipListMap<K,V>
  • java.util.concurrent.ConcurrentSkipListSet<E>
  • java.util.concurrent.ConcurrentLinkedQueue<E> 

并发的散列表可以高效的支持大量读者和一定数量的写者;保证原子性。

6.执行器

线程池(Thread pool):程序中创建了大量生命期很短的线程时使用;减少并发线程的数目。

  将一个Runnable对象交给线程池,就会有一个线程调用run方法。

执行器(Executor)类有许多静态工厂方法用来构建线程池。

 

posted @ 2017-07-30 20:06  花宝宝爱学习  阅读(119)  评论(0编辑  收藏  举报