基于前面对java中的锁做的小结,本文会对多线程的使用做一个小结:
一、多线程的基本使用
二、多线程的进阶应用
一、多线程的基本使用
1、一些基本概念
进程和线程:主要差别在于它们是不同的操作系统资源管理方式。进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
同步和异步:是指函数或方法在调用时的结果返回情况。同步是需要等待所有任务完成后返回结果,而异步的瞬间返回并不代表任务完成。
并行和并发:并行是时间上各不影响的同时执行,而并发在时间上是相互应用时间片段,宏观上看是并行的。
临界区:表示一种公共资源,可以被多个线程使用,但是每次只允许一个线程独占。
阻塞和非阻塞:形容多线程之间的相互影响,当一个线程独占临街资源时,其他线程需要等待,导致线程挂起,这就是阻塞。
死锁:指两个或多个线程在执行的过程中,由于资源相互竞争造成阻塞的现象。若无相关操作,他们将被无限期阻塞下去。死锁是一个静态问题,进程会被卡死,但是不会占用cpu。
活锁:是一个动态过程,比如:线程A、B都需要临界资源a和临界资源b,线程A占用a,需要b,线程B占用b,需要a,放弃了资源以后,A又获得了b资源,B又获得了a资源,如此反复,则发生了活锁。
饥饿:指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。
2、线程的基本状态(来源http://www.cnblogs.com/GarfieldEr007/p/5746362.html)
1)新建状态:被new()出来
2)runnable状态:线程start
3)running状态:线程获得时间片,正执行
4)等待状态:线程被wait,释放掉锁的状态
5)等锁状态:线程被notify后,进入等锁池
6)阻塞状态:线程sleep,join或者Io阻塞
7)dead:线程执行完
线程之间的扭转关系可有:
3、线程的创建和终止
1)常见线程的创建方式:继承Thread,复写run()方法;实现Runnable接口;线程池使用
因为继承只有一个类,所以在某种程度上,实现方式用法更广。当然,考虑到线程创建和销毁的代价,线程池是一种常见的做法,如数据库的连接池等。
2)终止线程:
Thread.stop():会释放掉所有的监管monitor,无论线程执行到哪里,都会立即停止线程,不推荐使用。
线程中断:中断是一种协作机制,调用线程的interrupt方法不一定会中断正在运行的线程,它会要求线程在合适的时机结束自己。每个线程都会有一个boolean的中断状态,interrupt方法只是将状态设置为true,对于非阻塞的线程,只是状态进行了改变,并不一定会立即停止。Thread.sleep,Object.wait,Thread.join这行方法会清除中断状态,Boolean设置为false。
线程挂起suspend和继续执行resume。:suspend不会释放锁,一直占用临界资源,直到被其他线程resume。但是,多线程的执行无法控制先后顺序,如果后来的线程suspend,会导致线程卡死。
yield:yield方法是把自己占有的cpu时间释放掉,然后再和其他线程一起竞争CPU资源,这与sleep不同,sleep会释放掉CPU资源,当下并不会竞争。
join:是保证线程能够执行完成,而不会因为其他线程先运行完结束。
3)守护线程:JVM中优先级最低的线程,如果当前有其他线程,守护线程会在所有线程结束后结束。
4、线程的优先级
Thread定义了线程的优先级是1-10,但类中提供了三个线程的优先级,MIN_PRIORITY、NORMAL和MAX_PRIORITY。并不是优先级高的线程就一定先执行。
5、wait和notify区别
共同点:1)都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2)wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
不同点:
1)sleep和yield方法都是Thread类方法,wait和notify,notifyAll都是object的方法;
2)sleep睡眠时,保持对象锁,仍然占有该锁;而wait睡眠时,释放对象锁。
3)sleep可以在任何地方使用,并且需要捕获编译异常;wait和notify,notifyAll需要在同步块中使用。
二、多线程中的happen-before
Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系。Java内存模型中的八条可保证happen—before的规则,它们无需任何同步器协助就已经存在。
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
- 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
- 传递性:A先于B,B先于C,那么A必然先于C
- 线程的start()方法先于它的每一个动作
- 线程的所有操作先于线程的终结(Thread.join())
- 线程的中断(interrupt())先于被中断线程的代码
- 对象的构造函数执行结束先于finalize()方法
public class HappenBeforeTest extends Thread {这个程序在client模式下能够正常的打印thread run和thread stop。但是在Server模式下可能将是无限循环。因为虽然getStop函数设定了结束标识,但是线程不一定能取到值,甚至会运行抛出异常。
private boolean flag;
@Override
public void run(){
while(!flag){
System.out.println("thread run");
}
System.out.println("thread stop");
}
public boolean getStop(){
flag = true;
return flag;
}
public static void main(String[] args) throws InterruptedException {
HappenBeforeTest test = new HappenBeforeTest();
test.start();
Thread.sleep(1000);
test.getStop();
Thread.sleep(1000);
}
}
三、线程中断
中断是通过线程的Thread.interrupt()方法做的,该方法修改了线程中断状态。对于非阻塞线程,中断只是修改了中断状态,并不会停止线程的执行,Thread.interrupted()返回true。如果线程上使用了Thread.sleep(), Object.wait(), Thread.join(),这个线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为false。