文章目录
回顾:
线程创建方式:
1.继承Thread
2.实现Runnable接口
3.实现Callable接口(有线程的返回值)
线程常用方法和属性:
- start() run()
- sleep 休眠
- join 等待一个线程执行完成
- interrupt 终止线程
- yield 让出CPU的执行权
属性:
优先级
线程分组
TIMED_WAITING:有明确结束等待时间
线程安全问题
线程不安全的五个因素:
1.CPU抢占式执行
2.内存可见性 volatile可解决
3.指令重排序(编译器优化) volatile可解决
4.原子性 加锁可解决
5.多个线程同时修改了同一个变量
线程安全问题解决方案:
1.锁(让多线程排队执行)
2.使用私有变量
锁:
1.synchronized
2.Lock 手动锁
synchronized
实现原理:
三种使用场景:
1.6性能优化(四种状态)
synchronized和volatile 和Lock的区别
死锁:
定义:在多线程编程中(两个或两个以上的线程)因为资源抢占而造成线程无限等待的问题
线程和锁的关系:一对多
一个线程可以拥有多把锁,而一把锁只能被一个线程拥有。
死锁代码:
public class ThreadMain36 {
public static void main(String[] args) {
//锁A(资源A)
Object lockA=new Object();
//锁B(资源B)
Object lockB=new Object();
//线程1
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//得到锁A
synchronized (lockA){
System.out.println("线程1:获取了锁A");
try {
//休眠1s,让线程2先得到锁B
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:等待获取锁B");
//尝试获取资源B
synchronized (lockB){
System.out.println("线程1:得到了锁B");
}
}
}
}, "t1");
t1.start();
//创建并启动线程2
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
//得到锁B
synchronized (lockB){
System.out.println("线程2:获得了锁B");
try {
//休眠1s,让线程取到了锁B
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2:等待获取锁A");
//尝试获取资源A
synchronized (lockA){
System.out.println("线程2:得到了锁A");
}
}
}
},"t2");
t2.start();
}
}
死锁操作的四个条件(同时满足):
- 1.互斥条件(一个资源只能被一个线程持有,当被一个线程持有之后就不能被其他线程持有)--------不可改变
- 2.请求拥有条件(一个线程持有了一个资源之后又试图请求另一个资源)-------可被修改
- 3.不可剥夺条件(一个资源被一个线程拥有之后,如果这个线程不释放此资源,那么其他线程不能强制获得此资源)-----不可修改
- 4.环路等待条件(多个线程在获取资源时形成了一个环形链)—可以修改
排查死锁的工具:
JDK的bin目录下
1.jconsole.exe
2.jvisualvm.exe
3.imc.exe—>启动jmx控制台----->线程----->死锁检测
如何解决死锁问题:
- 请求拥有条件
- 环路等待:最容易实现
解决死锁可以通过控制获取锁的顺序来解决(破坏了环路等待条件)
sleep休眠缺点:必须有明确的结束数据。
线程通讯机制:
定义:一个线程的动作可以被另一个线程感知到就叫线程通讯。
wait(休眠)/notify(唤醒)/ nofifyAll(唤醒全部)
wait为什么要加锁:
wait在使用的时候需要释放锁,在释放之前必须要有一把琐,所以要加锁
wait为什么要释放锁:
wait默认是不传任何值的,当不传递任何值的时候表示永久等待,这样就会造成一把锁被一个线程一直持有。为了避免这种问题的方式,所以在使用wait时一定要释放锁
wait /notify / nofifyAll 使用注意事项:
1.在使用以上方法时一定要加锁
2.加锁对象和 wait /notify / nofifyAll 的对象必须保持一致
3.一组 wait / notify / nofifyAll 必须是同一对象
4.nofifyAll只能唤醒当前对象的所有等待线程
wait () :void 不传参时表示传的是0-----永久等待
wait(long):void 毫秒级别的最大等待时间
wait(long,int):void 纳秒级别的最大等待时间
Thread.sleep(0) 和 Object.lock(0)的区别
1.sleep是Thread的静态方法,而lock 是Object的方法
2.sleep(0)立即触发一次CPU资源的抢占,而lock(0) 永久的等待下去
wait和sleep的区别
相同点:
- 1.都可以让当前线程休眠
- 2.都必须处理一个Interrupt 异常
不同点:
- 1.sleep是Thread的静态方法,wait来自于Object中的一个方法
- 2.传参不同。wait可以没有参数,sleep必须有一个大于等于0的参数
- 3.wait 使用必须加锁,sleep使用时不用加锁
- 4.wait使用时会释放锁,而sleep不会释放锁
- 5.wait默认不传参的情况下会进入WAITING状态,而sleep会进入TIMED_WAITING状态
为什么wait会放到Object中而不是Thread?
wait必须要加锁和释放锁,而锁又是属于对象级别而非线程级别(线程和锁是一对多的关系,也就是一个线程可以拥有多把锁),为了灵活起见(一个线程之中会有多把锁),就把wait放在Object。
wait和LockSupport的区别:
相同:
- 1.两个都可以进行休眠
- 2.两者都可以传参或者不传参,并且二者线程状态也是一致的
不同:
- 1.wait必须配合synchronized一起使用(必须加锁),而LockSupport不需要加锁。
- 2.wait只能唤醒全部和随机一个线程,而LockSupport可以唤醒指定线程。