并发:CPU同一时间段执行多种不同的进程
并行:CPU同一时刻同时执行不同进程
进程:进入到内存的程序。
打开Windows任务管理器可以查看合结束当前内存中的进程。【从内存中清除】
线程:进程中的一个执行单元。一个进程中至少有一个线程。
举例: Inter Core i7 8866 4核心8线程
8线程:同时执行8个任务
【单核心单线程】cpu会在多个线程之间做高速切换,轮流执行多个线程,效率低,切换速度(1/n 毫秒)
【4核心8线程】cpu可同时执行8个线程,8个线程在多个任务之间做高速切换,速度是单线程的8倍
多线程的优势:
1.效率高
2.多个线程之间互不影响
分时调度:所有线程轮流使用CPU,平均分配时间
优先级调度(抢占式调度):优先级高的优先使用,同优先级随机选择【java】
主线程:执行main方法的线程
单线程程序:java程序中只有一个线程
执行从main方法开始,从上到下顺序执行
【java程序过程分析】
JVM执行main方法,main方法会依次进入到栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径有一个名字,叫主线程
JVM允许应用程序【并发】执行多线程程序
创建多线程程序方式一:创建Thread类的子类
实现步骤:
【开辟新的栈空间】用于执行thread的子类执行run方法
当存在多个栈空间时,cpu就有个选择的权利,可以选择执行其它线程(其它栈空间)
public final String getName()//返回该线程的名称
public static Thread currentThread()//返回当前正在执行线程对象的引用
public final void setName(String name) //设置线程名称
【设置线程名称】除了使用setName来改名以外,还可以在创建线程对象时,用重写后的带参构造方法来取名。
public static void sleep(long millis)//暂停millis毫秒
【应用】可以用于模拟时钟
public Thread(Runnable target)
public Thread(Runnable target,String name)
创建多线程程序方式二:实现Runnable接口
Runnable
接口应该由打算通过某一线程执行其实例的类来实现。
实现步骤:
匿名:没有名字
内部类:写在其他类内部的类
【作用】简化代码
匿名对象的最终产物:子类/实现类对象,该对象的类没有名字
格式:
new 父类/接口(){
//重写父类/接口中方法
}
调用格式:
new 父类/接口(){
//重写父类/接口中方法
}.start
【引例】电影院买票问题
若有一场电影,一个窗口卖所有的票。【单线程】无安全问题
若有一场电影,共100张票,售票窗口1: 1-33号票,售票窗口2: 34-67号票,售票窗口3: 68-100号票。【多线程】没有共享数据,无安全问题
若有一场电影,共100张票,三个售票窗口,都可以卖所有的票。【多线程】访问了共享数据,会产生线程安全问题。
代码论证实现如下:
package cn.itcast.day13.demo03;
public class RunnableImpl implements Runnable {
//定义一个多线程共享票源
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠一下
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断有票
System.out.println(Thread.currentThread().getName() + "--> 正在卖剩余第" + ticket + "张票");
ticket--;
}
}
}
}
package cn.itcast.day13.demo03;
import javax.swing.plaf.TableHeaderUI;
/*
模拟卖票
创建3 个进程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建接口实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//开启多线程
t0.start();
t1.start();
t2.start();
//产生了线程安全问题,卖出了重复的票,甚至卖出了第-1张票
}
}
【产生的安全问题】
解决线程安全问题的第一种方案是同步代码块
格式
synchronized(锁对象){
//访问共享数据的代码块
}
【注意】
加入锁对象后的卖票修正代码:
package cn.itcast.day13.demo04.demo03;
import java.util.Objects;
public class RunnableImpl implements Runnable {
//定义一个多线程共享票源
private int ticket = 100;
//创建一个锁对象,就是一个普通对象就行
Object object = new Object();
@Override
public void run() {
while (true) {
//同步代码块
synchronized (object){
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠一下
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断有票
System.out.println(Thread.currentThread().getName() + "--> 正在卖剩余第" + ticket + "张票");
ticket--;
}
}
//同步代码块
}
}
}
使用了一个锁对象,这个锁对象叫【同步锁】,也叫对象锁,或对象监视器
【简而言之】
同步中的线程,没有执行完毕,不会释放同步锁。
同步外的线程,没有同步锁,不会进入同步。
同步保证了只有一个线程在同步中对共享数据进行操作,保证了安全,但降低了程序效率。
解决线程安全问题的第二种方案:使用同步方法
使用步骤:
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
//访问共享数据的代码块
}
利用同步方法执行的代码块:
public synchronized void payTicket(){
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠一下
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断有票
System.out.println(Thread.currentThread().getName() + "--> 正在卖剩余第" + ticket + "张票");
ticket--;
}
}
【本质】与同步代码块思想一致,不过是将线程实现类对象作为了锁对象
修饰符 static synchronized 返回值类型 方法名(参数列表){
//访问共享数据的代码块
}
【锁对象】本类的class文件对象,不是this,this是创建对象之后产生的
Lock
实现提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 [Condition
] 对象。
void lock()
获取锁
void unlock()
释放锁
线程状态。线程可以处于下列状态之一:
NEW
]RUNNABLE
]BLOCKED
]WAITING
]TIMED_WAITING
]TERMINATED
]定义:多个线程处理同一资源,但线程任务却各不相同。
【为什么要处理线程间通信】
多线程并发执行时,若希望他们有规律地执行,需要协调一下工作,以帮助达到多线程工作目的。
【如何保证有效利用资源】
等待与唤醒机制
【引例】卖包子
顾客消费线程和包子铺卖包子线程之间的通信
顾客线程:告知包子铺老板要包子的种类和数量,然后调用wait方法进入无限等待
包子铺线程:花时间做包子,做好之后调用notify方法唤醒顾客,完成包子交易
注意事项:
进入到TimeWaiting(计时等待)状态有两种方式
public final void notifyAll()
唤醒当前所有正在等待状态的线程
又称为线程间通信。【重点】有效利用资源
在多线程任务中,若多个线程进入等待状态,将会存在一个等待调度队列。notify唤醒时,优先唤醒队列中等待时间最长的线程。
【注意】即线程被唤醒,也不一定直接进入Runnable状态。若此时锁还没有被上一线程释放,就获取不到锁对象。于是线程会先进入Blocked状态堵塞,等待获取到锁对象。
【资源类】:包子类
设置包子的属性:皮,馅
设置包子的状态:有 true 没有false
【生产者类】:包子铺类 (是一个线程类,继承Thread)
设置线程任务(run):生产包子
生产流程:
对包子状态进行判断
true:有包子 包子铺进入等待状态
false:没有包子 包子铺生产包子,交替生产两种包子
有两种状态(i%20) A包子 (i%21) B包子
包子铺生产好了包子,修改状态为true
唤醒消费者类(吃货类)
【消费者类】:吃货类,线程类
设置线程任务(run):吃包子
对包子的状态进行判断
false:没有包子 吃货线程调用wait方法进入等待状态
true:有包子 吃货吃包子,吃完包子修改包子状态为false,唤醒包子铺线程生产包子
【测试类】:包含main方法的类,启动程序
创建包子对象,创建包子铺线程,开始营业。
创建吃货线程,开始交易。
为了避免频繁地创建线程和销毁线程,有一种办法可以实现线程复用,那就是线程池。
线程池:一个容纳多个线程的容器,其中的线程可以反复使用。
【本质】一种容器:集合(ArrayList , HashSet,LinkedList,HashMap)
推荐使用LinkedList 集合
在JDK 5之后,java内置有线程池,不需要手动创建集合
【好处】
java.util.concurrent 类 Executors
public static ExecutorService newCachedThreadPool()
创建线程池对象
java.util.concurrent 接口 ExecutorService
Future<?> submit(Runnable task)
【线程池接口】用来从线程池中调用线程,执行start方法执行线程任务
用于关闭/销毁线程池
面向对象的思想:做一件事情,找一个能解决这件事的对象,调用对象的方法,完成这件事。【找对象】
函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视结果不重视过程。【找结果】
实现Runnable接口的方式,实现多线程程序
步骤:
匿名内部类
java 8 发布了新特性:Lambda 表达式
制定了一种做事情的方案(函数):
无参数,无返回值,只有代码块
同样的语义体现在Lambda语法中,更加简单:
() -> System.out.println("多线程任务执行!");
(参数列表) -> {一些重写方法的代码};
三要素:
【格式解释】
给定一个厨子cook接口,内部唯一抽象方法makeFood()
原则:【可推导,可省略】
凡是根据上下文推导出来的内容,都可以省略书写。
可以省略的内容包括:
(参数列表) :括号中参数列表的数据类型可以省略
(参数列表):括号中的参数如果只有一个,那么数据类型和括号都可以省略
{代码}:若{}中代码只有一行,无论是否有返回值,都可以省略大括号{},return,分号
注意:{} return ; 分号 这三个要省略,必须一起省略
【注意】有且仅有一个抽象方法的接口,成为"函数式接口"