多线性概述
● 进程
指运行中的程序,只要运行起来就会占用内存空间
比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间,当我们使用微信,又启动了一个进程,操作系统将为微信分配新的内存空间
● 线程
线程是由进程创建的,是进程的一个实体
一个进程可以有多个线程
比如打开百度云(打开进程),下载任务,任务1(线程1),任务2(线程2)。
线程调度
● 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
● 抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为 抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使 用率更高。
同步与异步
● 同步:排队执行 , 效率低但是安全.
● 异步:同时执行 , 效率高但是数据不安全.
并发与并行
● 并发
同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核CPU实现的多任务就是并发。(比如A边开车边打电话)
● 并行
同一个时刻,多个任务同时执行,多核CPU可以实现并行(比如A开车,B打电话)
●实现多线程技术
1.Thread
当一个类继承了Thread类,该类就可以当做线程使用
我们需要重写Thread中的run方法
run方法就是线程要执行的方法
运行不能调用run方法,而是通过thread对象的start()来启动任务
先来两段代码试验:
代码分多次运行,会发现运行结果不一样。(线程随机性)
public class Thread01 {
public static void main(String[] args) {
MyThread m = new MyThread();
m.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);
}
}
}
每个线程都拥有自己的栈空间,共用一份堆内存。
2.Runnable
实现Runnable接口,需要重写实现它中的抽象run方法
public class Thread01 {
public static void main(String[] args) {
MyThread m = new MyThread();
Thread t = new Thread(m);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("疑是地上霜" + i);
}
}
}
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("窗前明月光" + i);
}
}
}
3.实现Callable接口
(1).实现Callable接口,并实现call方法
(2).创建 Callable 接口的实现类的实例,使用 FutureTask 类包装 Callable 对象,该 FutureTask 对象封装了 Callable 对象的 call() 方法的返回值;
(3)使用 FutureTask 对象作为 Thread 类的构造函数的 target 参数创建并启动线程;
public class CallThread implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("大北农" + i);
}
return null;
}
}
public class demo001 {
public static void main(String[] args) {
CallThread callThread = new CallThread();
FutureTask futureTask = new FutureTask<>(callThread);
Thread thread = new Thread(futureTask);
thread.start();
for (int i = 0;i < 5;i++){
System.out.println("茅台" + i);
}
}
}
● 实现 Runnable 与继承 Thread 相比有如下优势:
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
2.可以避免单继承所带来的局限性
3.任务与线程本身是分离的,提高了程序的健壮性
4.后续的线程池技术,接受 Runnable 类型的任务,不接收 Thread 类型的线程
● 设置线程名称
new Thread().setName(String name);
● 获取线程名称
Thread.currentThread().getName();
● 线程的休眠sleep
Thread.sleep(1000);//休眠1000毫秒=1秒
导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数
●线程阻塞
所有消耗时间的操作(文件读取,等待用户输入)
● 线程的中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定
在早期,线程提供了以个 stop()
方法,但是,使用 stop 强行让一个线程死亡,可能导致其没来得及释放资源,导致资源的浪费。所以,我们要使用interrupt()
来让线程察觉到外部标记,然后自己决定死亡
public class Thread01 {
public static void main(String[] args) {
MyThread m = new MyThread();
Thread t = new Thread(m);
t.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//给线程t添加中断标记
t.interrupt();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return;
}
}
}
● 守护线程
setDaemon();
线程分为:守护线程和用户线程
用户线程:当一个进程不包括任何存活的用户线程时,进程结束
守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡
守护线程的设置,一定要在线程之前
.setDaemon(true);
子线程是否结束,都会随着主线程的结束而结束
public class Thread01 {
public static void main(String[] args) {
MyThread m = new MyThread();
Thread t = new Thread(m);
t.setDaemon(true);
t.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
● 线程安全问题
演示经典的不安全售票情况
public class Thread01 {
public static void main(String[] args) {
Ticket t = new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable{
//票数
private int count = 10;
@Override
public void run() {
while (count > 0){
System.out.println("卖票中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,剩票" + count + "张");
}
}
}
票数出现负数还在卖就是不合理,线程不安全问题
接下来我们解决:
解决方案1:同步代码块
public class Thread01 {
public static void main(String[] args) {
//格式 synchronized(锁对象){}
Ticket t = new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable {
//票数
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (count > 0) {
System.out.println("卖票中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,剩票" + count + "张");
}else{
break;
}
}
}
}
}
解决方案2:同步方法
public class Thread01 {
public static void main(String[] args) {
//格式 synchronized(锁对象){}
Ticket t = new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable {
//票数
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while (true) {
synchronized(o){
sell();
}
}
}
public void sell() {
if (count > 0) {
System.out.println("卖票中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,剩票" + count + "张");
}
}
}
解决方案3:显示锁Lock
同步代码块和同步方法都属于隐式锁
public class Thread01 {
public static void main(String[] args) {
//显示锁 Lock 子类 ReentrantLock
Ticket t = new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable {
//票数
private int count = 10;
//显式锁 l 自己创建一把锁
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();//关锁
if (count > 0) {
System.out.println("卖票中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,剩票" + count + "张");
} else {
break;
}
l.unlock();//开锁
}
}
}
● 公平锁与非公平锁
公平锁:先来先到,排队
非公平锁:锁解开大家一块抢,上面3种都是不公平锁
显示锁Lock :fair参数为true 就表示公平锁
● 线程死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,要避免
儿子:我先玩手机,再写作业
妈妈:你先完成作业,再玩手机
在任何有可能导致锁产生的方法里,不要再调用另外一个方法,让另外一个锁产生
● 多线程通信问题
厨师做完菜得通知服务员上菜
厨师:生产者; 服务员:消费者;
当厨师做菜时,服务员等待
当厨师做完菜,把服务员唤醒上菜
上菜后,厨师等待,等盘子回来需要做菜时唤醒
生产者与消费者机制
确保生产者在生产时,消费者没有在消费,搭配机制,确保安全
● 线程的六种状态
NEW :尚未启动的线程处于此状态。
RUNNABLE :在Java虚拟机中执行的线程处于此状态。
BLOCKED :被阻塞等待监视器锁定的线程处于此状态。
WAITING :无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING :正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
TERMINATED :已退出的线程处于此状态
● 带返回值的线程Callable(java中第三种线程方式)
之前两种是新的执行路径,不影响主线程的使用,与主线程并发执行,一块走
Callable即可以实现之前的,也可以等子线程执行完毕给结果再执行主线程
● 线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处 :降低资源消耗、提高响应速度、提高线程的可管理性。
● Java中的四种线程池
1. 缓存线程池
(长度无限制)
执行流程: 1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在,则创建线程 并放入线程池, 然后使用
2. 定长线程池
(长度是指定的数值)
执行流程:1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
3. 单线程线程池
效果与定长线程池 创建时传入数值1 效果一致.
执行流程: 1. 判断线程池 的那个线程 是否空闲
2. 空闲则使用
3. 不空闲,则等待 池中的单个线程空闲后 使用
4. 周期性任务定长线程池
执行流程: 1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程