1 线程运行原理
a.流程图:讲义当中的彩色图。
b.内存图:栈内存线程独立,堆内存线程共享。
2.使用继承类的方式创建多线程
定义一个类,然后继承Thread
覆盖重写run方法
调用start启动线程
3.继承类的Thread成员方法
public String getName():获取线程的名称 public void start():启动一个线程,会瞬间完成的。 public void run():指定线程的任务内容 public static Thread currentThread():获取当前线程 public static void sleep(long time):睡眠指定毫秒时长
Thread类还有两个构造方法:
public Thread(Runnable target):创建一个线程,参数代表线程的任务内容。
public Thread(Runnable target, String name):创建一个线程,第二个参数代表线程的名称。
4.通过一个接口调用run方法:Runnable接口
使用实现接口的方式创建多线程
. 定义一个类,实现Runnable接口
覆盖重写run方法
. 创建一个线程,使用Runnable作为构造参数。
. 调用start启动线程。
接口方式的好处:
实现Runnable接口比继承Thread类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
4. 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类。
Runnable接口的使用步骤: 1. 定义一个实现类,实现Runnable接口。 2. 实现类必须覆盖重写run方法。 3. 创建一个Runnable实现类对象,作为Thread构造参数。 4. 调用线程对象的start()方法,启动线程。 启动线程的两种方式,效果一样。 1. 继承Thread类,覆盖重写run方法。 2. 实现Runnable接口,覆盖重写run方法,然后再作为Thread构造参数
5.匿名内部类:在这里其实就是一个runnable接口;
匿名内部类的三种使用场景:
(1). 接口可以使用匿名内部类,需要覆盖重写抽象方法。
(2). 抽象类也可以使用匿名内部类,需要覆盖重写抽象方法,或者普通方法也行。
(3). 普通类也可以使用匿名内部类,只要不是final的,就行。
6.线程安全:模拟场景-卖电影票
为什么抢票的结果会出现一个负值或者一个重复值?
哒:本来拆开的动作,被第三者插足。
哒:1.因为买票有两个动作:a.检查>0; b.出票
2.还有可能出现相同的数字,因为“后。。”并非原子性操作,
a.先取原来本来的值 b.向下
这两个动作中间也有可能被其他线程插足。
7.解决问题:
上锁:在同一时刻智能允许一个人进去。
本来是抢占票的问题,现在换成一个抢锁的问题。
使用方式:1.同步代码块,
2.同步方法
3.lock接口
8.同步代码块:
小括号当中必须是一个引用类型,不能是基本类型。
含义:
只要执行到了sync这一行,那么就会尝试霸占锁对象。
如果霸占成功,那么就进入大括号执行内容。
如果霸占失败,将会卡死在sync这一行,等待正在用的人释放锁,我才能重新抢这个锁。
如果执行离开了大括号范围,那么将会自动释放锁。
9.同步方法:
同步方法的格式:
修饰符 synchronized 返回值类型 方法名称(参数列表) {
方法体
}
10.Lock接口:
获取锁,释放锁.
java.util.concurrent.locks.Lock接口: 1. 创建一个全局唯一的Lock接口对象:Lock lock = new ReentrantLock(); 2. 获取锁:.lock(),如果抢锁成功,那么就会继续执行;如果抢锁失败,那么将会卡死在这一行。 3. 释放锁:.unlock(),释放锁。
11.线程的6种状态:
new、
Runnable、
blocked:挠门等待的状态-锁阻塞
waiting、平静的等待(经过协商过的等待)
timed-waiting、
termintted、
- NEW(新建) 线程刚被创建,但是并未启动。
- Runnable(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
- Blocked(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
- Waiting(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
- Timed Waiting(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
- Teminated(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
12.与waitting状态的两个方法:
public void wait():当前线程主动释放锁,进入WAITING无限平静等待,死等。等待别人通知我,我再继续。 public void notify():通过锁调用,通知在锁上处于等待状态的人,让其结束等待。 这两个方法是一对儿,必须通过同一个锁进行调用,必须在同步代码范围之内调用。
wait方法的重载形式: public void wait(long time):主动释放锁,进入TIMED_WAITING状态,定时等待,最多等待指定的毫秒时长。 如果在此期间之内,收到了通知,那么将会结束等待。 如果超过了指定的时间,仍然没有通知,我也会结束等待。
13.sleep的意义:
sleep方法只是让线程睡一会儿,与所竞争无关。不会释放锁,也不会抢锁。 这个方法也会导致进入TIMED_WAITING状态,但是原理与wait(long)不同。 【sleep与锁无关】,wait会释放锁。
14.补充知识点:
(1)jvm虚拟机的线程转换状态图。
也叫状态机图
(2) 问题:
请描述Thread类中的start()方法与run()方法的区别。
da:
线程对象调用run()方法不开启线程,仅是对象调用方法。
线程对象调用start()方法开启线程,并让jvm调用run()方法在开启的线程中执行。
(3) 创建线程的两种方式。
da:
- 第一种方式是将类声明为 Thread 的子类。
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动该线程。
- 第二种方式是声明一个类实现Runnable 接口。
1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,Thread对象才是真正的线程对象。
3. 调用线程对象的start()方法来启动线程。