Java多线程编程与安全
线程的概念
当我们打开一个应用程序后,操作系统会为该应用程序分配一个进程id。
进程是系统资源分配的基本单位;线程是系统调度的基本单位。
一个进程可以包含很多线程;线程共用进程的资源。
线程的生命周期
- 新建状态
使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start()这个线程。 - 就绪状态
当线程对象调用了start()方法之后,该线程就进入了就绪状态,就绪状态的线程处于就与队列中,要等待JVM里线程调度器的调度。 - 运行状态
如果就绪状态的线程获取了CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 - 阻塞状态
如果一个线程执行了sleep()(睡眠)、suspend()(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。再睡眠时间已到或获得设置资源后可以重新进入就绪状态。 - 死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
创建线程的方法
Java提供了三种状态创建现成的方法
- 通过实现Runnable接口
- 通过己成Thread类本身
- 通过Callable和Future创建线程
线程池
线程资源必须通过线程池提供
- 为什么要使用线程池
使用线程池的好处是减少创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不适用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换的问题”。 - 使用线程池的目的
- 线程是稀缺资源,不能频繁的创建
- 解耦作用:线程的创建与执行完全分开,方便维护。
- 应当将其放入一个池子中,可以给其他任务进行复用。
- 常见的创建线程池的方式有以下几种
- Executor.newCachedThreadPool():无限线程池
- Executor.newFixedThreadPool(nThreads):创建固定大小的线程池。(大小作为参数)
- Executor.newSingleThreadPool():创建单个线程的线程池
线程安全
Synchronized是Java中的关键字,是一种同步锁。它可以修饰一个代码块、一个方法或者一个类。
当他修饰一个方法时,被修饰的方法就叫做同步方法。他的作用范围是整个方法,它的作用对象是调用这个方法的对象。
当我们有多个线程并发的访问Synchronized方法时,在同一个时刻只有一个线程能得到执行,另一个线程会受到阻塞,必须等待当前线程执行完此方法以后才能继续执行以保证数据安全。
死锁
- 概述
线程死锁是指两个或两个以上的线程相互持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其他线程是获取不到这个锁的,而且会一直死等下去,因此就造成了死锁。 - 死锁产生的条件
- 互斥条件:一个资源,或者说一个锁只能被一个线程锁占用,当一个线程首先获取到这个锁之后,在该线程释放这个锁之前,其它线程都是无法获取到这个锁的。
- 占有且等待:一个线程已经获取到一个锁,再获取另一个锁的过程中,即使获取不到也不会释放已经获得的锁。
- 不可剥夺条件:任何一个线程都无法强制获取别的线程已经占有的锁。
- 循环等待条件:线程A拿着线程B的锁,线程B拿着线程A的锁。
- 如何避免死锁
一个方式是,我们的线程不要长时间的持有对象,可以给他设置一个超时时间。
JDK并发工具类
- CountDownLatch:一个或多个线程等待其他线程完成一系列操作后执行
- Semapore:用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。