线程
2 线程 Thread
2.1 并发\并行
- 并发:指两个或多个事件在同一时间段的发生
- 并行:指两个或多个事件在同一时刻发生(同时发生)
2.2 实现多线程
实现多线程的方式:
方式一:
- 自定义线程类 extends Thread
- 重写run方法
- run方法中定义线程任务
弊端:因为Java是单继承,所以这个类继承了Thread就只能是线程类了
方式二:
- 自定义类 implements Runnable 接口
- 实现run方法
- 通过线程任务来创建线程对象,调用start()方法来启动线程
优势:
- 接口可以多实现,灵活;
- 使用线程池时,只能传递Runnable对象,不能使用Thread对象
方式三:(匿名内部类写法)
- 本质就是继承Thread类
- 本质就是实现Runnable接口
2.3 常用API
- 开启线程:start()
- 获得线程相关信息
- 获得当前线程:static Thread currentThread()
- 阻塞状态:static yield()
- 线程休眠:sleep() (static)
2.4 线程的优先级
setPriority(int newPriority)
优先级1~10 ,数值越大,优先级越大,默认优先级5
作用:提升获取CPU执行的概率
注意:优先级越高并非线程优先运行,有可能优先级高的线程后执行或者后结束
2.5 守护线程 setDaemon(boolean)
当所有前置/前台线程结束后,守护线程自动结束
注意:GC就是一个守护线程
2.6 线程安全问题
多个线程之间共享同一个资源,线程同步,可能会出现线程安全问题
解决线程安全问题方法:添加同步锁
添加同步锁方法:
-
给代码块添加锁 synchronized(对象){代码块}
对象通常是共享对象,通常都是this对象
-
给方法加锁 在方法上添加关键字synchronized
本质上是锁的对象就是 this
结论:解决安全问题是牺牲了一部分效率的,所以锁的范围越小越好
注意:一个对象只能标记一个锁,只能同时被一个线程使用
- 使用lock锁 ReentrantLock
2.7 线程通信
单向通信:t1.join() -> 意味着等待t1线程结束再继续当前线程
join()代码示例:
public class Demo01 {
public static void main(String[] args) {
//加载图片线程
Thread load = new Thread(){
@Override
public void run() {
for (int i = 1; i <= 100 ; i++){
System.out.println("正在加载图片..." + i + "%");
//加载模拟 线程休眠
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//加载完成后输出图片加载完成
System.out.println("图片加载完成");
}
};
//显示图片线程
Thread show = new Thread(){
@Override
public void run() {
//等待加载
System.out.println("正在等待图片加载完成");
//线程单向通信 等待另一个线程的完成
try {
load.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//显示图片
System.out.println("显示图片");
}
};
show.start();
load.start();
}
}
运行结果:
正在等待图片加载完成
正在加载图片...1%
正在加载图片...2%
正在加载图片...3%
正在加载图片...4%
正在加载图片...5%
…………………………
正在加载图片...96%
正在加载图片...97%
正在加载图片...98%
正在加载图片...99%
正在加载图片...100%
图片加载完成
显示图片
双向通信:共享资源
Object:wait() * 3
notify() notifyAll()
wait():进入等待状态中,只能被notify/notifyAll唤醒
wait(long):进入等待状态,时间到自然被唤醒
使用notify和wait方法时,必须带对象锁
public class Picture {
boolean isLoad;//图片是否加载完成,若完成则为true
boolean isShow;
}
public class ShowPicture implements Runnable{
Picture picture = new Picture();
public ShowPicture(Picture picture) {
this.picture = picture;
}
@Override
public void run() {
//等待图片加载完成
synchronized (picture){
if (!picture.isLoad) {
System.out.println("正在等待图片加载完成");
try {
picture.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//显示图片模拟
System.out.println("显示图片ing");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//显示图片完成
System.out.println("图片显示");
picture.isShow = true;
//唤醒load线程
synchronized (picture){
picture.notify();
}
}
}
public class LoadPicture implements Runnable {
Picture picture = new Picture();
public LoadPicture(Picture picture) {
this.picture = picture;
}
@Override
public void run() {
//加载图片
for (int i = 1; i <= 100; i++){
System.out.println("正在加载图片..." + i + "%");
}
//图片加载完成
System.out.println("图片加载完成");
picture.isLoad = true;
//唤醒show线程
synchronized (picture){
picture.notify();
}
//等待图片显示完成
synchronized (picture){
if (!picture.isShow){
try {
picture.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//下载图片
for (int i = 1; i <= 100; i++){
System.out.println("正在下载图片..." + i + "%");
}
//下载完成
System.out.println("图片下载完成");
}
}
public class Demo02 {
public static void main(String[] args) {
Picture pic = new Picture();
LoadPicture loadPicture = new LoadPicture(pic);
ShowPicture showPicture = new ShowPicture(pic);
Thread load = new Thread(loadPicture);
Thread show = new Thread(showPicture);
show.start();
load.start();
}
}
先把思路用注释的形式写在程序中,再根据注释编写代码
笔试题:用线程实现生产者和消费者
扩充
-
每个线程都有自己的独立的栈空间
-
静态方法通过类来调用
-
运行主方法具有两个线程,主线程 + GC线程
-
问:进程什么时候结束?
答:当所有的前置线程结束后,进程也结束了
面试题:
-
锁的种类:代码而言 锁就两种
概念上来说,锁的种类非常多 锁的种类\概念\特点 -
sleep和wait的区别
属于的类的区别,唤醒的区别,对于锁的机制