多线程
多线程技术概述
进程与线程
进程
- 是指一个内存中运行的应用程序,每个进程都用独立的内存空间
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
- 线程是在进程的基础上进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干线程
线程调度
分时调度
- 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
- 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),
Java使用的线程调度为抢占式调度
抢占式调度
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而 CPU在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
同步与异步
同步
排队执行,效率低但是安全
异步
同时执行,效率高但是不安全
并发与并行
并发
并发:指两个或者多个事件在同一时间段内发生
并行
并行:指两个或者多个事件在同一时刻发生(同时发生)
多线程技术
public class Demo01 {
public static void main(String[] args) {
MyThread mt = new MyThread();
//线程调用run()方法,是使用.start()方法
mt.start();
for (int i = 0; i < 10; i++) {
System.out.println("曾许天下第一流"+i);
}
}
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("莫忘少年凌云志"+i);
}
}
}
在整个执行的过程中,子线程任务所调用的方法都在子线程中运行。public void run()
每一个线程都拥有自己的栈空间,公用一份堆内存 public void run()在线程的栈空间运行
由一个线程所调用的方法,这个方法也会在执行在这个线程里面
实现多线程的方法
1、继承Thread类
2、实现Runable接口
// 创建一个任务对象
MyRunnable m = new MyRunnable();
// 创建一个线程对象,并为其分配一个任务
Thread t = new Thread(m);
// 执行线程
t.start();
实现Runnable接口 与 继承Thread 相比有如下优势:
- 通过创建任务,然后给线程分配的方式来实现的多线程, 更适合多个线程同时执行相同任务的情况
- 可以避免类单继承的局限性
- 任务和线程本身是分离的,提高代码的健壮性
- 后续的学习线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
Thread类
构造方法
new Thread();
new Thread(Runnable target);
new Thread(Runnable target , String name); 通过getName()获取任务名
常用方法
start();导致此线程开始执行; Java虚拟机调用此线程的run方法。
getName();返回此线程的名称。
getId();返回此Thread的标识符。
getPriority();返回此线程的优先级。
setPriority(int newPriority();更改此线程的优先级。
sleep(long millis);导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
setDaemon(boolean on);将此线程标记为 daemon线程或用户线程。
currentThread();返回对当前正在执行的线程对象的引用。
线程休眠
Thread.sleep(毫秒数) 1000毫秒 =1秒
线程阻塞
线程阻塞不只是线程休眠,线程阻塞也可以简单地看作是所有消耗时间的操作,(文件的读取以及用户的输入操作),线程阻塞也称为耗时操作。
线程中断
Thread.interrupt();
在线程的方法中进行try-catch捕获,在catch中做出相应的处理操作。
守护线程
Thread.setDaemon(Boolean值);
线程分为:用户线程和守护线程
用户线程:当一个进程不包括任何存活的用户线程时,进程结束
守护线程:守护用户线程,当最后一个用户线程死亡时,所有的守护线程自动死亡
线程不安全问题
线程不安全的原因是:多个线程同时执行操作一个数据
解决方法:当A线程在处理当前数据时,确保其他数据(B,C)不允许操作此数据[排队执行]
线程不安全会导致数据出错
线程同步
使用到关键词:synchronized
线程不安全的解决方法:
同步代码块和同步方法都称为隐式锁
- 同步代码块:
格式:synchronized(锁对象){ } - 同步方法:
将需要排队执行的代码放在synchronized修饰的方法中
如果该方法没有被static关键字修饰,那么这个锁对象就是指当前类的对象
如果该方法有被static关键字修饰,那么这个锁对象就是指 当前类.class 的对象 - 显示锁 Lock----子类ReentrantLock
创建锁:Lock l = new ReentrantLock();
开启锁:l.lock()
关闭锁:l.unlock()
使用方式:将你需要锁起来的代码前面添加l.lock(),在代码后面添加l.unlock()
注意:
当一个类中有多个同步代码块或者多个同步方法时,该锁对象都是当前类对象,当线程A在执行该类的第一个同步代码块时,剩下的同步代码块或者同步方法都是被锁住的(例如:一栋楼有很多房间,但是楼就一把锁,一个人进去后,把门反锁了,其他的房间也进不去 了)
显示锁与隐式锁的区别
公平锁和非公平锁
创建显示锁对象时,可以给对象添加参数,参数类型为boolean值。
Lock lock = new ReentrantLock(boolean);
当布尔值为true时,说明是公平锁。(排队进行,先到先执行)
当布尔值为false时,说明是非公平锁。(谁先抢到,谁就执行)
同步代码块、同步方法、显示锁都是非公平锁。
线程死锁
解决方法:在任何有可能导致锁产生的方法里,不要再调用另外一个方法让另外一个锁产生
多线程通信问题
线程的六种状态
线程状态。 线程可以处于以下状态之一:
- NEW
尚未启动的线程处于此状态。 - RUNNABLE
在Java虚拟机中执行的线程处于此状态。 - BLOCKED
被阻塞等待监视器锁定的线程处于此状态。 - WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。 - TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。 - TERMINATED
已退出的线程处于此状态。
线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
Java种的第三种线程实现方式:Callable
- 编写类实现Callable接口 , 实现call方法
class XXX implements Callable {
@Override
public call() throws Exception {
return T;
} } - 创建FutureTask对象 , 并传入第一步编写的Callable类对象
Callable callable = new MyCallable();
FutureTask future = new FutureTask<>(callable); - 通过Thread,启动线程 new Thread(future).start();
task.get();可以调用任务的返回值,当返回值被调用时,子线程结束,main线程执行
task.isDone();判断子线程是否执行完毕
task.cancle(boolean);取消线程操作;boolean值为true则确认取消;返回值类型是boolean,返回值为true时,说明子线程还在执行,否则子线程已执行完毕。
线程池
- 缓存线程池
(长度不限制)
任务加入后的执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在,则创建线程,并放入线程池,然后使用 - 定长线程池
(长度是指定的数值)
执行流程:
【1】判断线程池是否存在空闲线程
【2】存在则使用
【3】不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
【4】不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程(排队等待) - 单线程线程池
执行流程:
【1】判断线程池的那个线程是否空闲
【2】空闲则使用
【3】不空闲,则等待,池中的单个线程空闲后使用 - 周期定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("好好学习,天天向上");
}
},3,2,TimeUnit.SECONDS);
执行流程:
【1】 判断线程池是否存在空闲线程
【2】存在则使用
【3】不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
【4】不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
周期性任务执行时:定时执行, 当某个时机触发时, 自动执行某任务
Lanbda表达式
使用前提:该接口或者类只有一个方法