声明:资料来源于bilibili动力节点,仅供学习使用
1.多线程的概述
线程是进程的处理单元,一个进程可以有多个线程,多线程之间可以并发处理,一个线程一个栈。
在单处理器机器中进程看似是并发的,其实是串行处理,因为其处理速度过快。
2.栈,堆,方法区
在java虚拟机中,一个线程对应一个栈。所有线程共享堆内存和方法区。但每个线程是内的资源是不共享的。
3.创建线程的三种方式
3.1 编写一个类继承java.lang.Thread类,重写run方法。
//定义线程类
public class MyThread extends Thread{
public void run(){
}
}
//创建线程对象
MyThread t =new MyThread();
t.start();
3.2 实现Runnable接口
//定义一个可运行的类
public class MyRunnable implements Runnable{
public void run(){
...
}
}
//创建线程对象
Thread t=new Thread(new MyRunnable());
t.start();
//采用匿名内部类方式创建线程对象
//Thread t=new Thread(new Runnable(){
// @Override
// public void run(){
// ....
// }
//});
//t.start();
3.3 实现Callable接口 新特性
FutureTask task=new FutureTask(new Callable(){
@Override
public Object call() throws Exception{
.....
return xxx;
}
})
4.调用线程类的run()与start()区别
直接调用run()并不会开启线程,而是在当前线程的栈中压入run方法,普通的调用方法。
start()方法的作用是:开启一个线程分支,在jvm中开辟一个新的栈空间,这条代码瞬间就执行完成。启动成功后会立刻调用run方法,并且run方法在新栈的底部。
5. *****线程的生命周期*****
新建状态,就绪状态,运行状态,阻塞状态,死亡状态。
新建状态:线程对象刚被new出来。
就绪状态:线程具有抢占cpu时间片的权利。
运行状态:线程正在占据cpu时间片。
阻塞状态:线程因等到某个事件的发生,放弃当前抢占的时间片,持续等待事件的发生。
死亡状态:run方法结束。
6.sleep方法
sleep是静态方法,传入的参数为long类型,为毫秒。Thread.sleep(long ms)。sleep方法在哪个线程上使用,哪个线程就进入睡眠。例如在主线程创建了一个线程对象t,调用t.sleep()。此时不是t睡眠而是main睡眠。t.sleep被转换成Thread.sleep()。
7. interrupt方法
interrupt方法可以终止线程睡眠。利用异常处理机制,让线程抛出异常,终止睡眠。
8. 合理终止一个线程
stop方法以及弃用,直接关闭线程会导致丢失信息。最佳的方法是在线程类中定义一个Boolean flag=true。run内判定flag是否为true。外部可以随时修改线程的flag值。
9. 线程的调度
就绪态中的所有线程抢夺cpu时间片。线程有优先级之分,优先级高的线程抢夺的能力越强。
10. 线程不安全的条件
多线程并发,多个线程共享某个资源,资源有修改的行为。
11. 解决线程不安全的方法
对资源的访问排队进行。对资源进行加排他锁。
12. 同步和异步的理解
同步就是排队 异步就是并发
13. synchronized的三种用法
方法中加入同步代码块 修饰方法 修饰静态方法
有一个余额类,类中有余额属性等于10000元,取钱方法withdraw。在main中创余额类的一个实例对象act。然后创建两个相同的线程t1 t2通过构造方法传入act对象。run方法中对调用withdraw方法取5000块。main开启这两个线程。此时会出现这种情况,t1线程拿到余额为10000,要拿走5000,由于网络原因,修改余额出现延迟。此时t2线程访问余额,看到的还是10000。所以要让t1 t2线程排队进行操作。那就需要让t1 t2线程排队执行withdraw方法。可以将withdraw方法体放入同步代码块,或者方法上加入synchronized。同步代码块synchronized(){....} 括号中填的应该是对于t1 t2线程来说是唯一的东西。给这个东西上锁,就能让t1 t2排队执行withdraw方法体的内容。这个东西可以是this,因为this指的是act对象,对于t1 t2线程来说是唯一的。也可以是act对象中的某一实例变量,因为act对象是唯一的,那么该实例对象也是唯一的。甚至可以是一段字符串"abc",因为abc在字符串池中,是唯一的,如果用"abc"那么根据该线程类创建的所有线程想执行同步代码块的内容都需要排队。所以括号里的内容不是固定的,按具体要求选择。
synchronized修饰方法代表对该对象加锁。一个对象一把锁。比如synchronized修饰act类中aa方法,t1 t2共享act1 t3 t4共享act2那么t1 t2与t3 t4。那么t1 t2对应调用这个方法act1.aa方法是同步的,t3 t4对应调用这个方法act2.aa方法是同步的。t1t2与t3t4是异步的。
synchronized修饰静态方法表示对类加锁,一个类一把锁。比如synchronized修饰act类中aa静态方法,t1 t2传入act1 t3 t4传入act2,及时对象不同 t1 t2 t3 t4对应调用这个方法aa方法是同步的。
线程进行排队的必要条件是方法上有synchronized标志。同步的概念对其他没加synchronized方法来说是不存在的。即不管有没有该对象的锁都能调用。
面试题1:doOther方法执行的时候需要等待doSome方法的结束吗?
不需要。 因为doOther方法没有用synchronized修饰,所以t2线程调用此方法不需要抢占mc的锁。
面试题2:doOther方法执行的时候需要等待doSome方法的结束吗?
需要。t2线程需要抢占mc的锁才能执行doOther方法。
面试题3:doOther方法执行的时候需要等待doSome方法的结束吗?
不需要。mc1 mc2分别被t1 t2线程独享。所以t1 t2线程异步。
面试题4:doOther方法执行的时候需要等待doSome方法的结束吗?
需要。类锁只有一把,不管new了多少个对象。
14. 死锁
线程t1得到a资源,想要b资源。而线程t2得到b资源,想要a资源。一直等待对方释放对象的锁
synchronized(a){
synchronized(b)
}
synchronized(b){
synchronized(a)
}
15. wait notify notifyAll
wait notify属于Object类的方法。
o.wait是当前线程释放对象o的锁,进入锁池,开始无限期的等待。直到o.notify被调用才解除等待。
o.notify唤醒当前对象o正在等待的单个线程。
o.notifyAll唤醒当前对象o正在等待的所有线程。
用于生产者消费者模型。