##1.常见名词
1、 同步与异步 同步和异步都表示方法的调用,不同点在于:同步方法调用后必须等待该方法调用返回后才能执行后续行为;而异步方法调用一开始就会立刻返回结果,可以立刻执行后面的行为,而异步方法的所做的工作则放在另一个线程中执行。
2、 并发与并行 并发与并行都表示多个任务一起执行,不同点在于:并行是真正意义上的多个任务同时执行;而并发是多个任务交替执行,多个任务之间可能是串行执行的,相当于每个任务只能获取CPU很短的占用时间,多个任务在很短的时间内交替执行。
3、 阻塞与非阻塞 某一线程访问一公共资源时其他线程必须等待该线程释放占用才可以使用,否则就要挂起线程等待,无法继续工作,我们称这种方式为阻塞;而非阻塞表示线程之间不会发生资源争夺,线程的工作不会妨碍其他线程的执行。 ##2. 多线程的三大性质
4、 原子性 指一个操作是不可被中断的,即使多个线程一起执行,一个操作一旦开始就不会被其他线程干扰。
5、 可见性 当一个线程修改了某一个共享变量的值,其他线程是否能立刻知道这个修改。
6、 有序性 在多线程程序执行时,可能会发生指令重排。也即例如有两个线程A、B同时执行,在线程B看来线程A的指令执行顺序是没有保证的。(而CPU的指令重排就是为了尽量减少流水线中断,提高CPU工作效率)。 ##3. 线程的生命周期 Java线程的状态在Thread的枚举类State中定义:
public enum State{
NEW, //表示刚刚创建的线程,但是还未执行,在start()方法调用后进入RUNNABLE状态
RUNNABLE, //表示线程正在执行
BLOCKED, //如果线程遇到了synchronized同步块,该线程就进入该状态,暂停执行,知道获得请求的锁
WAITING, //无时间限制的等待状态
TIMED_WAITING, //有时间限制的等待状态
TERMINATED; //线程结束
}
##4. 线程的状态和基本操作 ###(1). start()与run()开启线程的区别 start()方法会新建一个线程并让这个线程执行run()方法;而run()方法不能新建一个线程,而是在当前线程中调用run()方法。 ###(2). 开启线程的方法 匿名内部类
Thread t1 = new Thread(){
@Override
public void run(){
//do something
}
};
t1.start();
实现Runnable接口
public class CreateThread implements Runnable{
@Override
public void run(){
//to do something
}
public static void main(String[] args){
Thread t1 = new Thread(new CreateThread());
t1.start();
}
}
###(3). 终止线程-不要使用stop() 一般情况下,线程在执行完毕后就会结束,无需手工关闭,但是我们也经常会创建无限循环的后台进程以持续提供某项服务,所以就需要手动关闭这些线程。 在JDK中也有终止线程的API,例如stop()方法,但是极度不推荐这个方法,因为stop()方法得到调用后,会强行把执行到一半的线程终止,可能会引起数据不一致问题。
例如我们同时开启A、B两个线程去处理同一个对象User,使用线程A去不断改变User的两个属性:age、name,使用线程B不断去读取对象User并打印。当线程A刚刚修改完age属性但还没来得及修改属性name时,调用了线程A的stop()方法强制关闭了线程A,这时线程B获得了User的锁,对User的属性进行读取,则读取到的User是只处理了一般的对象,发生了数据不一致问题。
但是想要终止一个无限循环的线程应该怎么做?
我们推荐的做法是在类中添加一个isStop的布尔值属性,判断isStop为true则跳出循环体,线程执行完毕自动终止,就避免了数据不一致的问题。
###(4). 等待(wait)和通知(notify) 为了支持多线程之间的协作,JDK提供了wait方法和notify方法。这两个方法虽然和多线程有关,但是却不是Thread类中的,而是所有类的父类Object中的,也意味着任何对象都可以调用这个方法。 当一个对象调用了wait方法后,如:objectA.wait(),当前线程就会在这个对象上等待,会释放该对象的锁,直到其他线程调用了objectA.notify()方法为止。 需要注意的是,wait和notify方法都必须获得对象的监视器(锁),在同步代码得到执行后也会释放对象的锁,所以必须被包含在对象的synchronzied语句中。 下面举一个简单的例子:
public class WaitNotifyDemo {
final static Object object = new Object();
public static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(System.currentTimeMillis()+" 线程1开启。。");
try {
object.wait(); //1.先获取object的锁,然后开始等待,并再次释放object的锁。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+" 线程1结束。。"); //4f. 两秒后,线程2执行结束,线程结束,重新获得了object的锁,此句猜得到执行
}
}
}
public static class Thread2 extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(System.currentTimeMillis()+" 线程2开启,并执行notify通知线程1 。。");
object.notify(); //2.获取object的锁成功,通知object的其他线程(即线程1),这里还未释放object的锁!
System.out.println(System.currentTimeMillis()+" 线程2执行notify结束。。");
try {
Thread.sleep(2000); //3. 使线程2暂停2秒,即2秒后线程2才能执行结束,才能把object的锁释放。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+" 线程2结束。。");
}
}
}
public static void main(String[] args) {
Thread thread1 = new Thread1();
Thread thread2 = new Thread2();
thread1.start();
thread2.start();
}
}
以上代码是一个关于wait和notify的示意代码,声明一个静态对象和两个静态内部类表示两个线程,连个线程分别在对象object的同步块中调用wait和notify方法,在线程1中调用了object的wait方法前需要先获取object的锁,然后进入线程等待,并释放锁;然后线程2的notify方法执行前需要获取object的锁,然后通知线程1。但是此时线程1仍然无法执行wait方法后面的代码,原因是线程2Thread.sleep(2000)使得线程2在2秒之后才能退出并且释放object的锁,也即线程1必须等待线程2的object同步代码块执行结束后才能获得锁,去继续执行下面的代码,具体的输出日志如下:
1510300350884 线程1开启。。
1510300350884 线程2开启,并执行notify通知线程1 。。
1510300350884 线程2执行notify结束。。
1510300352884 线程2结束。。 //2秒中之后线程2结束,释放object锁
1510300352885 线程1结束。。 //线程2释放锁之后,线程1获得锁,结束等待状态,继续向下执行。
###(5). 等待线程结束(join)和谦让(yield) 在多线程协同工作时,有时一个线程的某个任务执行需要等待另一个线程执行结束之后才能继续执行,这时就会用到join方法。join方法是Thread中的方法,一个线程A开启后,如果调用了join方法,则表示当前线程就会进入阻塞状态,一直到线程A完成、退出后才能继续执行当前线程的后续工作。 了解了join的工作方式后,我们发现join的功能完全可以由前面的wait和notify方法实现:首先,在当前线程调用线程A的join方法时,相当于调用了当前线程的wait方法,进入等待状态,直到线程A执行结束顺利退出前调用当前线程的notify方法,恢复当前线程的继续运行。
而另一个方法yield,同样来自Thread类,她的作用是让当前线程让出CPU的使用权,但是该方法的执行并不代表该线程放弃了继续执行,而是仍然会进入CPU资源的争夺,只是无法保证会再次抢到CPU的使用权。就好像领导人换届时,上一任的领导会暂时退出当前的位置,但是也会继续参加到领导人的竞选活动当中,争取连任。