为什么需要并行
– 并不是为了提高系统性能,而是确实在业务上需要多个执行单元。
– 比如HTTP服务器,为每一个Socket连接新建一个处理线程
– 让不同线程承担不同的业务工作
– 简化任务调度
几个重要的概念
– 同步(synchronous)和异步(asynchronous)
– 并发(Concurrency)和并行(Parallelism)
– 临界区
– 阻塞(Blocking)和非阻塞(Non-Blocking)
– 锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)
– 并行的级别
临界区
– 临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程。使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
阻塞(Blocking)和非阻塞(Non-Blocking)
– 阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其它所有需要 这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起。这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其它所有阻塞在这个临界区上的线程都不能工作。
– 非阻塞允许多个线程同时进入临界区
死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)
死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,而又相互等对方释放锁,此时若无外力干预,这些线程则一直处理阻塞的假死状态,形成死锁。
我们知道多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿。当然还有一种饥饿的情况,一个线程一直占着一个资源不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够得到执行的,如那个占用资源的线程结束了并释放了资源。
活锁这个概念大家应该很少有人听说或理解它的概念,而在多线程中这确实存在。活锁恰恰与死锁相反,死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。当多线程中出现了相互谦让,都主动将资源释放给别的线程使用,这样这个资源在多个线程之间跳动而又得不到执行,这就是活锁。
并发级别
阻塞 、非阻塞 (无障碍 、无锁、无等待 )
阻塞– 当一个线程进入临界区后,其他线程必须等待
无障碍(Obstruction-Free)
– 无障碍是一种最弱的非阻塞调度
– 自由出入临界区
– 无竞争时,有限步内完成操作
– 有竞争时,回滚数据
无锁(Lock-Free)
– 是无障碍的
– 保证有一个线程可以胜出
无等待(Wait-Free)
– 无锁的
– 要求所有的线程都必须在有限步内完成
– 无饥饿的
线程
线程是进程内的执行单元
新建线程
终止线程
Thread.stop() 不推荐使用。它会释放所有monitor
挂起(suspend)和继续执行(resume)线程
– suspend()不会释放锁
– 如果加锁发生在resume()之前 ,则死锁发生
守护线程
在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程
当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出
Thread t=new DaemonT();
t.setDaemon(true);
t.start();
线程优先级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
Thread high=new HightPriority();
LowPriority low=new LowPriority();
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();
高优先级的线程更容易再竞争中获胜
基本的线程同步操作
synchronized
– 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
– 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
– 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
Object.wait() Obejct.notify()