线程基础
线程创建
线程的定义
创建线程的两种方法
创建线程有两种方法:通过实现Runnable接口创建线程和通过继承Thread类创建线程。
- MyThread类继承Thread类;重写run方法;实例化对象mythread可以作为线程使用,mythread.start()
A类已经继承了B类怎么办?不能继承Thread类了
- 实现Runnable接口;重写run方法;传入实例化对象new Thread(dog),这里Thread使用代理模式
区别:1. 本质上没有区别,从jdk文档可以看到Thread类本身实现了Runnable接口;2. 从使用上有区别,实现Runnable接口更加适合多个线程共享一个资源的情况,并且不受单继承的限制。
线程运行的原理
内存中发生了什么?run和start的调用?
运行程序,相当于启动进程,首先开启主线程main,主线程开启了子线程,子线程开启后,主线程 不会阻塞,两个线程交替执行。主线程结束了,并不意味着子线程结束,子线程还在执行,那么进程也没有结束。
run方法是一个普通的方法, 并没有真正启动线程,程序是串行化执行,并不是并行执行,会阻塞在run方法处,当run方法执行完毕后,才会继续向下执行。
Thread类的start方法会调用start0方法, start0是native本地方法,由JVM调用。start方法调用后,该线程并不会立马执行,而是将该线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度。
Thread t = new Thread(dog);t.start();
线程终止
- 当线程完成任务后,会自动终止
- 通过变量控制run方法的结束,来终止线程,这种方法被称为通知
线程中断
Thread常用方法:
- setName:设置线程名称
- getName:Thread.currentThread().getName()获取当前线程的名称
- start
- run
- setPriority
- getPriority
- sleep
- interrupt 中断不是停止
- 线程优先级的范围:MAXPRIORITY=10, NORMPRIORITY=5,MINPRIORITY=1
- interrupt并没有结束线程,用于中断正在休眠的线程,提前结束休眠
- sleep使当前线程休眠
线程插队
常用方法第二组:
协调线程的方法。在线程t1中调用Thread.yield()方法,可以让出cpu,让t2先执行。在线程t1中调用t2.join()方法,可以让cpu先执行t2
- yield:线程的礼让。让出cpu,让其他线程先执行,礼让的时间不确定,所以不一定能让成功。
- join:线程的插队。插队的线程一旦插队成功,则先执行完插入线程的所有任务,再执行其他线程。
守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束,常见的守护线程有垃圾回收机制。
线程七大状态
Thread.State_
静态属性有六种取值
- NEW
- RUNNABLE: READY/RUNNING,就绪态或运行态
- BLOCKED:等待进入同步代码块的锁
- WAITING:wait或join进入,notify唤醒
- TIMED_WAITING:sleep或wait进入,时间结束后进入runnable
- TERMINATED
查看状态:
thread01.getState()
线程同步机制
- 在多线程中,一些数据不允许被多个线程同时访问,此时使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
- 理解:当有一个线程在对内存进行操作时,其他线程都不能对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
两种同步:synchronized
- 同步代码块:
synchronized (对象){}
- 同步方法:
public synchronized void method01(){}
这里的锁加在当前对象this
利用关键字解决售票问题:
- 采用synchronized关键字修饰run方法,不合理,导致的结果是只有第一个线程会一直执行,直到没有票
- 将买票作为sell方法,run方法执行sell方法。对sell方法采用synchronized关键字修饰,多个线程轮流卖票,不会出现超卖。
互斥锁
互斥锁的基本理解:
- java中引入对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应一个互斥锁的标记,该标记可以保证某一时刻,只能由一个线程访问该对象
- 关键字synchronized用来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象只能某一时刻由一个线程访问
- 同步的局限性:程序执行效率降低
- 非静态的同步方法的锁默认是当前对象this,也可以是其他对象(要求是同一个)
- 第一种:当前对象this
public synchronized void sell(){..同步代码..}
相当于
public void sell(){
synchronized (this) {..同步代码..}
}
- 第二种:先定义一个属性为obj对象
Object obj = new Object()
public synchronized void sell(){..同步代码..}
相当于
public void sell(){
synchronized (obj) {..同步代码..}
}
这里一定要保证不同线程的obj是同一个对象
- 静态的同步方法的锁为当前类本身,当前类.class
public synchronized static void m1(){..同步代码..}
相当于
public static void m1(){
synchronized (SellTicket03.class){..同步代码.}
}
互斥锁的使用:解决售票问题
- 先分析需要上锁的代码
- 选择同步代码块或者同步方法
- 要求多个线程的锁对象为同一个
死锁
是什么? 多个线程都占用了对方的锁资源,并且互不相让,导致了死锁。
为什么? 假设线程AB已经分别拿到obj1,2的对象锁,此时线程A尝试获取obj2的锁,但是线程B还没有释放obj2的锁,所以线程A会持有obj1的锁并且阻塞,同样的,由于线程A没有释放obj1的锁,线程B也不能获取obj1的对象锁,会持有obj2的锁并且阻塞,这就会造成线程死锁。
怎么做? 模拟线程死锁:在线程构造器中给定属性flag,run方法中,有if-else分支,不同的flag进入不同的分支,每个分支中都有两把锁,分支1先获取obj1的对象锁,再尝试获取obj2的对象锁,分支2先获取obj2的对象锁,再尝试获取obj1的对象锁。
如何释放锁?
- 当前线程的同步方法或同步代码块执行结束
- 当前线程在同步代码块或同步方法中遇到break和return
- 当前线程在同步代码块或同步方法中出现error或exception
- 当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
什么情况不会释放锁?
- 当线程执行同步代码和同步方法时,程序调用了Thread.yield()或Thread.sleep()方法,会暂停当前程序的执行,但不会释放锁
- 当线程执行同步代码和同步方法时,其他线程调用了该线程的suspend()方法将该线程挂起,但不会释放锁