回顾
线程的创建:
- 继承Thread类(单继承)[2种]
- 继承Runnable run[4种]
- jdk 1.5 实现Callable +futureTask
线程的构造方法:
- 设置线程名
- 设置线程任务
- 设置线程分组
线程的常见属性:
- ID
- Name
- 状态
- 优先级
- 守护线程
线程的分类:
- 守护线程
- 用户线程(main)
注意:
在守护线程中创建的新线程,默认是守护线程
设置守护线程的状态必须放在线程启动之前
线程
run方法 和 start方法 的区别:
- run()方法属于普通方法,而start() 方法属于启动线程的方法
- run方法可以执行多次,而start方法只能执行一次
线程终止
- 使用全局自定义变量的方式来终止线程
public class ThreadDemo {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
//转账线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在转账");
}
System.out.println("终止转账");//当flag为true走到这
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(310);
} catch (InterruptedException e) {
e.printStackTrace();
}
//改变变量的状态来中宗线程执行
System.out.println("有内鬼,终止交易");
flag = true;
}
});
t2.start();
t1.join();
t2.join();
}
}
在收到终止指令之后,需要执行完当前的任务才会停止
- 使用线程自带的
interrupt
终止方法类终止线程
//会复位线程的终止状态
//执行判断线程终止为true后会将状态重置为false
Thread.interrupted()
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
//转账线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while(!Thread.interrupted()){//判断线程的终止状态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("终止状态"+Thread.currentThread().isInterrupted());
break;
}
System.out.println("正在转账");
}
System.out.println("终止线程");
}
});
t1.start();
Thread.sleep(310);
System.out.println("有内鬼,终止交易");
t1.interrupt();//终止线程
}
}
//不会复位线程的终止状态
Thread.currentThread().isInterrupted()
public class ThreadDemo13 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//while(!Thread.interrupted()){//判断线程的终止状态
while(!Thread.currentThread().isInterrupted()){
System.out.println("正在转账");
}
System.out.println("终止线程");
}
});
t1.start();
Thread.sleep(10);
System.out.println("有内鬼,终止交易");
t1.interrupt();
}
}
在收到终止指令之后,会立马结束执行
interrupt()
作用:将线程中的终止状态从默认的false
改为true
interrupt()
是全局的方法,它判断完之后会重置线程的状态,则currentThread().isInterrupted()
方法不会重置
使用线程提供的stop()方法来终止线程(不用)
线程状态:
- NEW:新建状态,没有调用线程
start()
之前的状态 - RUNNABLE:运行状态, [ Running执行中,Ready就绪 ]
- BLOCKED:阻塞状态,(多个线程并发试图获取同一把锁)
- WAITING:等待状态,没有明确的等待结束时间(wait())
- TIMED_WAITING:超时等待状态,有明确的等待结束时间(eg:sleep(×××))
- TERMINATED:终止状态
注意:yiled 出让CPU执行权,特点:不一定能正常出让CPU的执行权
public class ThreadDemo {
private static final int maxSize = 1000;
public static void main(String[] args) {
Thread t1 = new Thread(()->{
for (int i = 0; i < maxSize; i++) {
//出让CPU执行权
Thread.yield();
System.out.println("我是t1");
}
},"t1");
t1.start();
Thread t2 = new Thread(()->{
for (int i = 0; i < maxSize; i++) {
System.out.println("我是t2");
}
},"t2");
t2.start();
}
}
线程的工作方式:
先在自己的工作内存中找变量
再去主内存中找变量
线程安全:
多线程执行中,程序的执行结果和预期的结果不相符
++ 、-- 操作:(非原子性)
- load(读取)
- calc (运行)
- save(保存)
线程不安全原因:
- CPU抢占执行
- 非原子性(++ 、-- 操作)
- 编译器优化(代码优化,指令重排序)
在单线程下编译器优化会提升程序的执行效率,但是在多线程情况下会出现混乱,从而导致线程不安全问题 - 内存不可见性
- 多个线程同时修改了同一个变量
解决线程不安全
volatile:
轻量级解决“线程安全”的方案
作用:
- 禁止指令重排序
- 解决内存可见性(当操作完变量后,强制删除线程中工作内存的变量)
注意:
volatile
解决不了原子性问题
解决方案:
- 不能解决CPU抢占资源问题
- 每个线程操作自己的变量(不通用,修改难度大)
- 在关键代码上,让所有的CPU排队执行(加锁)
加锁操作的关键步骤:
1.尝试获取(如果成功拿到锁加锁,排队等待)
2.释放锁
总结
- synchronized 加锁和释放锁(JVM自动帮我们进行加锁和释放)
- lock (程序员自己加锁和释放)
注意:加锁操作时,同一组业务一定是同一个锁对象
synchronized实现原理:
- 操作系统层面: 依靠互斥锁实现mutex
- JVM层面:帮助程序员实现监视器锁的加锁和释放锁的操作
- Java语言层面:有一个锁对象mutex,锁存在的地方(变量的对象头)有个标志存放锁信息
对象头:3部分:对象头,实例数据,对齐填充
偏向线程ID
互斥锁结构体mutex:
Owner :拥有人、Nest 、使用次数(每使用+1,每释放-1)
EntryQ、RcThis、HashCode、Candidate
判断锁是否空闲:判断Owner是否为null或Nest是否为0
synchronized JDK6之前使用重量级锁实现的,性能非常低,所以用的不多
JDK6 对synchronized做了一个优化(锁升级)
无锁:没有人访问
偏向锁:第一个线程第一次访问
轻量级锁:while(true) {//尝试获取锁} 自旋
重量级锁:停止自旋,并且把当前没有获取到锁的线程放到等待队列里
synchronized 锁机制是非公平锁:
公平锁是可以按序执行,而非公平锁效率更高
Java中默认锁都为非公平锁
使用场景:
- 使用synchronized来修饰代码块(加锁对象可以自定义)
- 使用synchronized来修饰静态方法(加锁对象是当前类对象)
- 使用synchronized来修饰普通方法(加锁对象是当前类的实例)
lock实现原理:
注意:一定要将lock放在try外面
原因:
- 如果将其放在try内,当try里面的代码出现异常后,那么就会执行finally里面释放锁的代码,但是这个时候加锁还没成功,就会去释放锁
- 如果将lock()方法放在try里边,那么会执行finally里面释放锁的代码的时候就会报错(线程状态异常),释放锁的异常会覆盖业务代码的异常报错,增加了排除错误的成本
lock默认的锁也是非公平锁,但是lock显示的声明为公平锁
使用场景:
lock只能用来修饰代码块
总结
volatile和synchronized区别:
volatile
可以解决内存可见性问题和禁止指令重排序,但不能解决原子性问题synchronized
用来保证线程安全,可以解决任何关于线程安全的问题
(1. 抢占执行(保证了关键代码排序执行,始终只有一个线程会执行加锁操作),2. 原子性问题,3. 内存可见性问题,4.指令重排序问题)
lock和synchronized区别:
lock
只能修饰代码块;既可以是公平锁,也可是非公平锁(ReentrantLock
默认为非公平锁,可通过构造函数设置为true声明它为公平锁);ReentrantLock
更加灵活(eg:tryLock
);ReentrantLock
需要自己手动加锁和释放锁synchronized
可以修饰代码块、静态方法、普通方法;只有非公平锁的策略;是自动加锁和释放锁;