java内存模型基本概念
原子性:32位机器中long型的读写是不具有原子性的。
可见性:在并行关系中,当一个线程修改了某一共享变量的值时,其他的线程是否能立即知道这个修改。就比如CPU1 存在共享变量t的cache值,而CPU2修改了t的值,而CPU1还是直接从cache处取值,数据不一致。,产生可见性的问题。(指令重拍以及编译器优化都可能产生这个问题.)
有序性: 就是执行时候,代码排在前的后执行,原因就是在执行的时候,可能会发生指令重排,重排后的顺序未必一致,线程A的指令执行顺序在线程B看来是没有保证的。
caution:指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。
哪些指令不能重排 Happen-Before原则
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写先发生于读,保证了可见性。
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前。
传递性:A先于B,B先于C 则A必然先于C;
线程的start()方法,先于它的每一个动作。
线程的所有操作,先于线程的终结(Thread.join()
)
线程的中断(interrupt())先于被中断的代码。
对象的构造函数执行,结束,先于finalize();
线程的5种状态
NEW—>RUNNABLE—->BLOCKED–>WAITING—>TIMED_WAITING—>TERMINATED
线程的基本操作
新建线程
Thread t1 = new Thread();
t1.start();
注意:
执行t1.run()
也可以通过编译,也能正常执行,只不过,却不能新建一个线程,而是作为一个普通的方法调用。
终止线程
Thread.stop()
这个方法已经被标注为废弃的方法了,原因是使用这个方法结束线程的时候,会立即释放该线程所持有的锁,而这些锁是用来保持数据一致性的,如果此时写入数据进行到一半,那么对象会被破坏。此时其他线程读取数据的时候,就可能产生数据不一致的情况。
线程中断
线程中断并不会让线程立即退出,而且通知线程,“有人希望你退出”而后至于之后如何处理,线程自行决定。
public void Thread.interrupt() //中断线程,实例方法
public boolean Thread.isInterrupted() //判断是否被中断,实例方法
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态,类方法。
中断标志位表示当前线程已经被中断了。
添加中断处理:
if(Thread.currentThread().isInterrupted()){
System.out.println("Interrupted");
break;
}
Thread.sleep()
方法
public static native void sleep(long millis) throws InterruptedException;
这个方法是让当前线程休眠若干时间,会抛出InterruptedException,不是运行时异常,也就是说程序必须捕获并处理它,当线程处于休眠状态的时候,被中断,就会抛出异常。 如下
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interrupted When Sleep");
//设置中断状态
Thread.currentThread().interrupt();
}
Thread.yield();
}
}
});
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
输出
:Interrupted When Sleep
:Interrupted
代码在线程休眠以后,收到了t1.interrupt();
抛出异常,并设置中断状态,然后由于子线程是while(true),所以判断当前线程中断,并break结束线程。
caution:由于Thread.sleep()由于中断而抛出异常,此时会将中断标志位去除,如果不去除,在下一轮循环是将无法捕获这个中断。就是说原来已经中断了,然后捕获到异常,应该清除中断标志位,才能做后续if (Thread.currentThread().isInterrupted())
判断处理。
wait和notify
属于Object类的方法:
public final void wait() throws InterruptedException();
public final native void notify()
当线程A的obj.wait() 时,那么这个线程就会在这个对象上等待,进入等待状态,进入等待队列直到另外的线程调用obj.notify() 显然obj可以作为2个线程通信的有效手段。
在Object对象的等待队列中,可能有多个线程都在等待某一个对象,在执行某一对象的notify方法时,处于等待队列中的某一个会线程会被唤醒。是完全随机的选择,不公平的选择。另外还有notifyAll()方法,用于唤醒所有的线程。
Object.wait()语句需要包含在synchronized中,无论执行wait和notify都需要获取目标对象的监视器。
T1 T2
取得obejct监视器
obejct.wait()
释放object监视器
取得obejct监视器
obejct.wait()
等待object监视器 释放object监视器
重获object监视器
继续执行
例子:
import java.util.Date;
/**
* Created by loveqh on 2016/11/19.
*/
public class SimpleWaitNotify {
final static Object obj = new Object();
public static class T1 implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println(new Date(System.currentTimeMillis())+ " T1 start");
try {
System.out.println(new Date(System.currentTimeMillis()) + " wait for obj");
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date(System.currentTimeMillis()) + " T1 end");
}
}
}
public static class T2 implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println(new Date(System.currentTimeMillis()) + " T2 start notify one thread ");
obj.notify();
System.out.println(new Date(System.currentTimeMillis()) + " T2 end");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new T1());
Thread t2 = new Thread(new T2());
t1.start();
t2.start();
}
}
Thread.sleep() 和Object.wait()的主要区别就是Thread.sleep()不会释放任何资源,而wait()方法会释放目标对象的锁。
suspend 和 resume 挂起和继续执行
依据被抛弃的方法,原因是suspend后必须等到resume后才能继续执行,而suspend不会释放任何资源,其他线程如果想要获取这些锁资源的时候,就会受牵连。但是如果意外地resume在suspend之前执行了,那么被挂起的线程很难被继续执行了,占有的锁也不会被释放。而被挂起的线程的线程状态居然还是Runnable,进入类似死锁的状态。
join和yield 等待线程结束和谦让
join
join等待另一个线程的结束才执行另外一个线程,比如一个线程的输入是由另外一个线程的输出得到的。那么用到join非常合适。
join的翻译通常就是加入的意思,在线程的体现得也比较合适,因为一个线程要加入另外一个线程,那么最好的方法就是等着它一起走。
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
第一个表示无限等待,直到目标线程执行完毕,第二个给出了最大等待时间,如果超过最大时间,当前线程就会继续执行下去。
/**
* Created by loveqh on 2016/11/19.
*/
public class JoinMain {
public volatile static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (i = 0; i < 100000; i++) ;
}
});
t.start();
t.join();
System.out.println(i);
}
}
输出
100000
如果没有t.join() 则可能输出0 或者一个很小的i值,原因是在不同的线程中,t开启了新的线程,而原来的线程继续往下执行了。这样就会直接输出0或者一个很小的值。来试一下结果是输出0,私以为是t线程还没有构建完成,System.out.println(i);
就已经执行了。
caution
join()的本质是让调用线程wait()zai 当前线程实例上,核心JDK代码如下
join()
内部调用join(0)
而参数为0时执行的代码如下
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
所以相当于锁住当前线程,一直在wait(0)直到 isAlive()为false,即目标线程退出。
yield
public static native void yield();
静态方法,一旦执行,会使当前线程让出CPU,让出CPU不代表当前线程不执行了,让出CPU接下来会进行CPU的争夺,能不能分配到就不一定了。意思是说“我已经做了想做的事情啦,需要休息一下,给其他一些线程一些机会吧”
这种情况一般用在线程优先级低的或者不是很重要的工作,可以尝试Thread.yield()以减少它占用CPU资源。
如本文之前的例子,可以一探究竟,为什么同样都是sleep(2000)但是主线程会先t1.interrupt();
?