线程的生命周期
一、实现多线程的三种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口方式(有返回值)
二、start()方法和run()方法的区别
只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码
三、Runnable接口和Callable接口的区别
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
四、sleep方法和wait方法有什么区别
- 对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
- sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
- notify 和 notifyAll有什么区别?
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行
五、Lock和synchronized的不同?
- lock是一个接口,synchronized是一个关键字。
- synchroized在遇到异常时,会自动释放线程占有的锁,因此不会导致死锁的发生;而lock发生异常时,如果没有主动unLock()去释放锁,很可能造成死锁现象,unLock要放到finally块里执行;
- Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
示例:
/**
* 这样效率不高,只能一个人吃,吃的方法为同步方法了
* @author Administrator
*/
public class Apple implements Runnable{
private int num=50;
public void run() {
for(int i=0;i<50;i++){
eat();
}
}
private synchronized void eat() {
if (num > 0) {
try {
// 导致一个资源信息被多个用户同时拿到
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num + "的苹果");
num--;
}
}
public static void main(String[] args) {
Apple a=new Apple();
new Thread(a,"A").start();
new Thread(a,"B").start();
new Thread(a,"C").start();
}
}
//-----------------------------------------------------
public class Apple2 implements Runnable{
private int num=50;
private final Lock lock = new ReentrantLock();
public void run() {
for(int i=0;i<50;i++){
eat();
}
}
private void eat() {
lock.lock();//获取锁
if (num > 0) {
try {
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num + "的苹果");
// 导致一个资源信息被多个用户同时拿到
Thread.sleep(20);
num--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();//释放锁
}
}
}
public static void main(String[] args) {
Apple2 a=new Apple2();
new Thread(a,"A").start();
new Thread(a,"B").start();
new Thread(a,"C").start();
}
}
六、volatile作用
- 其他线程对变量的修改,可以及时反应在当前线程中;
- 确保当前线程对volatile变量的修改,能及时写回到共享内存中,并被其他线程所见;
- 使用volatile声明的变量,编译器会保证其有序性。
七、线程池
- 包括以下四个基本组成部分
- 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
- 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
- 为什么要用线程池:
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
八、Lock接口
- ReadLock
- ReentrantLock
- WriteLock
ReentrantLock的使用:
Lock lock = new ReentrantLock(); lock.lock(); try{ //可能会出现线程安全的操作 }finally{ //一定在finally中释放锁 //也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常 lock.ublock(); }
九、线程池
- 为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
- 比较重要的几个类:
1. newSingleThreadExecutor
创建一个单线程的线程池
2.newFixedThreadPool
创建固定大小的线程池。
3. newCachedThreadPool
创建一个可缓存的线程池。
4.newScheduledThreadPool
创建一个大小无限的线程池。
十、锁机制
- 重入锁:目的是为防止死锁发生。synchronized和Lock均为可重入锁
- 自旋锁:线程被阻塞为系统阻塞,处于内核态,java执行为用户态;为避免经常在用户态和内核态频繁切换导致性能下降,实现了自旋锁
- 偏向锁:线程加锁操作采用CAS操作。当线程已经获得锁后,再重入该锁时,后续操作将不会采用CAS,因为不存在锁竞争。
https://blog.csdn.net/dainandainan1/article/details/85267740