java_多线程
进程
程序是静止的,只有真正运行时(CPU资源分配给程序)的程序,才被称为进程
单核CPU,在任何时间点上,只能运行一个进程;宏观并行、微观串行
线程
线程(轻量级进程)。程序中的一个顺序控制流程,同时也是CPU基本的调度单位。
多线程
进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。
线程的组成
1、CPU时间片:操作系统(OS)会为每个线程分配执行时间。
2、运行数据:
对空间:存储线程需使用的对象,多个线程可以共享堆中的对象
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
3、线程的逻辑代码
创建线程的两种方式
第一种方式
1、继承Thread类。2、覆盖run()方法。3、创建子类的对象。4、调用start()方法
第二种方式(常用)
1、实现Runnable接口。2、覆盖run()方法。3、创建实现类的对象。4、创建线程对象。5、调用start()方法。
线程的状态(基本)
常见方法
• 休眠:
• public static void sleep(long millis)
• 当前线程主动休眠 millis毫秒。
• 放弃:
• public static void yield()
• 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
• 结合:
• public final void join()
• 允许其他线程加入到当前线程中。
线程的状态(等待)
线程的安全问题
线程不安全:
当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
临界资源:共享资源(同一对象)一次仅允许一个线程使用,才可保证其正确性。
原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
线程安全–同步方式(1)
• 同步代码块:
synchronized( 临界资源对象 ){ // 对临界资源对象加锁
// 代码(原子操作)
}
每个对象都有一个互斥锁标记,用来分配给线程的 。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
线程安全–同步方式(2)
• 同步方法:
synchronized 返回值类型 方法名称(形参列表){//对当前对象this加锁
// 代码(原子操作)
}
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
线程退出同步方法时,会释放相应的互斥锁标记.
同步规则
• 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
• 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
• 已知 JDK 中线程安全的类:
• StringBuffer
• Vector
• Hashtable
• 以上类中的公开方法,均为 synchonized 修饰的同步方法。
线程的状态(阻塞)
阻塞状态的进入条件:未得到标记锁
注:JDK5 之后就绪、运行统称Runnable
线程通信( wait()和notify() )
• 等待:
• public final void wait()
• public final void wait(long timeout)
//•必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
• 通知:
• public final void notify()
• public final void notifyAll()
//•必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响。
经典问题
• 死锁:
• 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
• 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
• 生产者、消费者:
• 若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
高级多线程
• 现有问题:
• 线程是宝贵的内存资源,单个线程约占 1MB 空间,过多分配易造成内存溢出。
• 频繁的创建及销毁 线程 会增加虚拟机回收频率,资源开销;造成程序性能下降
• 线程池:
• 线程容器,可设定线程分配的数量上限。
• 将预先创建线程对象存入池中,并重用线程池中的线程对象。
线程池原理
• 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。
获取线程池
Executor 顶级接口
ExecutorService线程池接口,通过submit(Runnable task)提价任务代码
Executors工厂类
newFixedThreadPool(int nThreads)
//获取固定数量的线程池。参数:指定线程池中线程的数量。
----------------------------------------------------------
newCachedThreadPool()
//获得动态数量的线程池,如不够则创建新的,没有上限
Callable接口与Runnable
• JDK5 加入Callable,与 Runnable 接口类似,实现之后代表一个线程任务。
• Callable**具有泛型返回值、可以声明异常**。Runnable不能声明异常
Future接口
概念:异步接收 ExecutorService.submit() 所返回的状态结果,当中包含了 call() 的返回值。
方法:V get()以阻塞形式等待 Future 中的异步处理结果( call() 的返回值)
Lock接口
• JDK5加入,与 synchronized 比较,显示定义,结构更灵活。
• 提供更多实用性方法,功能更强大、性能更优越 。
• 常用方法:
void lock() //获取锁,如锁被占用,则等待。
boolean tryLock()//尝试获取锁(功返回true失败返回false,不阻塞)
void unlock() //释放锁
重入锁ReentrantLock–>Lock的实现类
与 synchronized 一样具有互斥锁功能
读写锁
ReentrantReadWriteLock :
• 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
• 支持多次分配读锁,使多个读操作可以并发执行。
互斥规则
• 写- - 写:互斥,阻塞 。
• 读- - 写:互斥,读阻塞写、写阻塞读。
• 读- - 读:不互斥、不阻塞 。
• 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
Collection集合体系(高亮:线程安全集合)
List 、Set 、Queue
Conllections也提供了获得线程安全集合的方法
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
--------------------------------------------------
public static <T> List<T> synchronizedList(List<T> list)
--------------------------------------------------
public static <T> Set<T> synchronizedSet(Set<T> s)
-----------------------------------------
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
--------------------------------------------------
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
--------------------------------------------------
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
JDK1.2 提供,接口统一、维护性高,但性能没有提升,均以 synchonized 实现
CopyOnWriteArrayList
• 线程安全的ArrayList,加强版读写分离。
• 写有锁,读无锁,读写之间不阻塞,优于读写锁。
• 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
• 使用方式与ArrayList无异。
CopyOnWriteArraySet
• 线程安全的 Set ,底层使用 CopyOnWriteArray List 实现 。
• 唯一不同在于,使用 addIfAbsent() 添加元素,会遍历数组,
• 如存在元素,则不添加(扔掉副本)。
ConcurrentHashMap
• 初始容量默认为 16 段( Segment ),使用分段锁设计。
• 不对整个 Map 加锁,而是为每个 Segment 加锁。
• 当多个对象存入同一个 Segment 时,才需要互斥。
• 最理想状态为 16 个对象分别存入 16 个 Segment ,并行数量 16 。
• 使用方式与 HashMap
Queue接口(队列)
• Collection 的子接口,表示队列 FIFO ( First In First Out )
• 常用方法:
• 抛出异常:
• boolean add(E e)
// 顺序添加一个元素(到达上限后,再添加则会抛出异常)
• E remove()
// 获得第一个元素并移除(如果队列没有元素时,则抛异常)
• E element()
// 获得第一个元素但不移除(如果队列没有元素时,则抛异常)
• 返回特殊值: 推荐使用
• boolean offer(E e)
// 顺序添加一个元素 (到达上限后,再添加则会返回 false )
• E poll()
// 获得第一个元素并移除 (如果队列没有元素时,则返回 null )
• E keep()
// 获得第一个元素但不移除 (如果队列没有元素时,则返回 null
ConcurrentLinkedQueue
• 线程安全、可高效读写的队列,高并发下性能最好的队列。
• 无锁、 CAS 比较交换算法,修改的方法包含三个核心 参数 ( V,E,N )
• V :要更新的变量、E :预期值、 N :新值。
• 只有当 V==E 时, V=N
BlockingQueue接口(阻塞队列)
• Queue 的子接口,阻塞的队列,增加了两个线程状态为无限期 等待的方法 。
• 方法:
• void put(E e) // 将指定元素插入此队列中 ,如果没有可用空间,则等待。
• E take() // 获取并移除此队列头部 元素,如果没有可用元素,则等待。
• 可用于解决生产生、消费者问题
阻塞队列
• ArrayBlockingQueue :
数组结构实现,有界队列。(手工固定上限)
• LinkedBlockingQueue :
• 链表结构实现,无界队列。(默认上限 Integer.MAX_VALUE)
小结
• ExecutorService 线程池接口、 Executors 工厂。
• Callable 线程任务、 Future 异步返回值。
• Lock 、 ReentrantLock 重入锁、 ReentrantReadWriteLock 读写锁。
• CopyOnWriteArrayList 线程安全的 ArrayList 。
• CopyOnWriteArraySet 线程安全的 Set 。
• ConcurrentHashMap 线程安全的 HashMap 。
• ConcurrentLinkedQueue 线程安全的 Queue 。
• ArrayBlockingQueue 线程安全的阻塞 Queue 。(生产者、消费者)