6.1 多线程
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
什么是进程?什么是线程
- 进程是指在系统中正在运行的一个应用程序;程序一旦运行就是进程,或者更专业化来说:进程是指程序执行时的一个实例。
- 线程是进程的一个实体。
进程和线程的区别
- 进程拥有独立的堆栈空间和数据段,系统开销比较大;线程拥有独立的堆栈空间,但是共享数据段,开销比较小,切换速度快,效率高。
- 进程之间互不干扰,相互独立;线程共享数据段。
- 线程必定也只能属于一个进程;进程可以拥有多个线程而且至少拥有一个线程
- 线程是调度的基本单位;进程是拥有资源的基本单位。
- 同一进程的中线程的切换不会引起进程的切换;不同进程中进行线程切换会引起进程的切换。
并行与并发
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
同步
Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。
线程的生命周期
线程5种基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。(就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;)
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
- 等待阻塞 -- 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
产生死锁的条件
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
解决死锁的方法
- 撤消陷于死锁的全部进程;
- 逐个撤消陷于死锁的进程,直到死锁不存在;
- 从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失。
- 从另外一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态
避免死锁的方法
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
- 对数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
sleep() 和 wait() 区别
- sleep()方法是线程类(Thread)的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(Runnable)状态),因为调用 sleep 不会释放对象锁。
- wait() 是 Object 类的方法,对此对象调用 wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。
sleep() 和 yield() 区别
- sleep() 方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield() 方法只会给相同优先级或更高优先级的线程以运行的机会;
- 线程执行 sleep() 方法后转入阻塞(blocked)状态,而执行 yield() 方法后转入就绪(Runnable)状态;
- sleep() 方法声明抛出InterruptedException,而 yield() 方法没有声明任何异常;
- sleep() 方法比 yield() 方法(跟操作系统相关)具有更好的可移植性。
线程同步以及线程调度相关的方法
- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
Java中实现线程?
创建线程有两种方式:
- 继承 Thread 类,扩展线程。
- 实现 Runnable 接口。