一、多线程回顾
-
Thread 和 Runnable
线程的5个状态:
(1)新建
(2)就绪
(3)运行
(4)阻塞
(5)运行
Thread类中枚举类States的状态有:NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED。
Thread一些常见方法:
(1)setProprity(),getProprity() 设置优先级
(2)setDaemon() 设置守护线程
(3)interrupt() 当线程处于等待状态时,唤醒
(4)interrupted() 查看线程有没有被中断,并清除状态
(5)isInterrupt() 查看线程中断有没有被中断,不清除状态
(6)join() 当前调用此方法的线程,等待join进去的线程死亡,才会继续执行
(7)currentThread() 获取当前线程
(8) sleep() 将线程的执行暂停一定时间
(9)setUncaughtExceptionHandler(), 当线程执行出现未校验异常时,该方法用于建立未校验异常的控制器。
创建线程:
Callable 和 Runnable的区别
Runnable执行方法没有返回值,Callable可以返回线程执行结果。
Callable使用方法:
(1)
FutureTask futureTask=new FutureTask(new Callable(){
public Object call() throws Exception{
reutrn null;
}
});
new Thread(futueTask).start();
Object o= futureTask.get(); // 同步等待结果
(2)线程池
ThreadPoolExecutor executor=new ThreadPoolExecutor(5,10,10,TimeUnit.SECONDS,new ArrayBlockingQueue<>(100)){
@Override
public void afterExecute(Runnable r, Throwable t){
// run执行完后执行
}
};
Future<String> future=executor.sumit(new Callable{......});
future.get(); // 同步等待结果
executor.shutdown();
2.synchronized
该关键字给某个对象加锁
锁是一个对象,作用如下
(1)这个对象内部有个标志位,表示该对象有没有上锁,并能记录获取锁的thread id
(2) 没有获得锁的线程放入list中,锁被释放,从list唤醒一个thread。
要访问的共享资源本身也是一个对象,例如下面的MyTest,这两个对象可以合成一个对象,代码就变成了synchronized(this){},要访问的共享资源是对象a,锁加在对象a上。当然也可以创建一个对象,代码变成synchronized(obj1){},这个时候,访问的共享资源是对象a,而锁加在新建对象obj1上。
资源和锁的合二为一,使得在java里面,synchronized关键字可以加在任何对象的成员上。这意味着,这个对象既是共享资源,同时也具备锁的功能。
示例代码:
public class MyTest{
publilc synchronize void test(){
}
public static synchronized void test2(){
}
}
等价于
public class MyTest{
public void test(){
synchronied(this){
}
}
public static void test2(){
synchronized(Mytest.class){
}
}
}
实现原理:
锁如何实现:
在对象头里,有一块数据叫Mark Word. 在64位机器上,Mark Word是8字节,含有两个重要字段:锁标志位和占用该锁的thread ID。不同版本的jvm实现,对象头的数据结构有各种差异。
3.wait()/notify()
为什么要和synchronized一起使用?
两个线程之间要通信,对于同一个对象来说,一个线程调用该对象的wait(),另一个线程调用该对象的notify(),该对象本身就需要同步! 所以在调用wait()、notify()之前,要先通过synchronized关键字同步给对象,也就是给该对象加锁。
synchronized可以加载任何对象的实例方法上面,任何对象都可能成为锁,因此,wait()和notify()只能放在Object里面了。
为啥wait()的时候必须释放锁?
若不释放锁,调用wait进入阻塞状态,一直不能推出synchronized代码块,那么其他线程永远没有办法拿到锁,永远没有机会调用notify,发生死锁。
wait()内部伪代码:
wait(){
// 释放锁
// 阻塞,等待被其他线程notify
// 重新获取锁
}
4.interruptedException与interrupte()方法
只有那些声明了会抛出InterruptedException的函数才会抛出异常,也就是下面常用的函数:
sleep(long) wait() join()
轻量级阻塞和重量级阻塞:
能够被中断的阻塞称为轻量级阻塞,对应的线程状态时WAITING或者TIMED_WAITING;而像synchronized这种不能被中断的阻塞成为重量级阻塞,对应的状态是BLOCKED。
初始线程处于NEW,调用start开始执行后,进入running或ready,如果没有任何阻塞函数,线程只会在running和ready之间切换,也就是系统时间片调度,这两种状态的切换是操作系统完成的,除非手动调用yield函数,放弃对CPU的占用。
一旦调用了任何了wait,sleep,join ,LockSupport.park,线程就会进入waiting或timed_waiting状态,两者的区别只是前者位无限期阻塞,后者则传入了一个时间参数,阻塞一个有限时间。如果使用了synchronized,则会进入blocked状态。
不太常见的阻塞/唤醒函数,LockSupport.park/unpark,这对函数非常关键,Concurrent包中Lock的实现即依赖这一对原语。
因此thread.interrupt()的精确含义是“唤醒轻量级阻塞”,而不是字面意思“中断一个线程”。
thread.isInterrupted()是实例方法,读取中断状态,不修改状态
Thread.interrupted()不仅读取中断状态,还会重置中断标志位。
5.线程优雅关闭
线程是“一段运行中的代码”,一个运行中的方法。运行到一半的线程能否强制杀死?
不能,在Java中,有stop(),destroy()等方法,但这些方法官方明确不建议使用,如强制杀死线程,则线程中所有的资源,例如文件描述符,网络连接等无法正常关闭。
因此一个线程一旦运行起来,不要强行关闭,合理的做法是让其运行完,干净地释放掉所有资源,然后退出,如果是一个不断循环运行的线程,就需要到线程间的通信机制,让主线程通知其推出。还可在线程对象中设置标志位来退出 。
守护线程,当所有非守护线程退出之后,整个JVM进程就退出 了。