Day 009 2021.3.10
多线程
快速对齐代码: Ctrl + Alt + L
4个空格: Tab
多线程第一种创建方式(extends Thread)
package com.hong.Day009.Demo01;
//1.创建一个Thread子类
public class MyThread extends Thread{
//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run" + i);
}
}
}
package com.hong.Day009.Demo01;
public class Demo02 {
public static void main(String[] args) {
//3.创建Thread类的子类对象
MyThread mt = new MyThread();
//4.调用Thread类中的方法start方法,开启新的线程,执行run方法
mt.start();
//主线程会继续执行方法中的代码
for (int i = 0; i < 20; i++) {
System.out.println("main" + i);
}
}
}
多线程第二种创建方式(implements Runnable)
package com.hong.Day009.Demo02;
/*
创建多线程程序的第二种方法:实现Runnable接口
java.lang.Runnable
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现,类必须定义一个称为run的无参数方法
Thread(Runnable target)
分配一个新的 Thread对象。
Thread(Runnable target, String name)
分配一个新的 Thread对象。
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程
*/
//1.创建一个Runnable接口的实现类
public class MyThread implements Runnable{
//2.在实现类中重写Runnable接口的run方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
package com.hong.Day009.Demo02;
public class Demo01 {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
MyThread mt = new MyThread();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(mt);
//5.调用Thread类中的start方法,开启新的线程
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
Thread与Runnable的区别
/*
实现Runnable接口的创建多线程程序的好处:
1.避免了单继承的局限性
2.增强了程序的扩展性,降低了程序的满合性
*/
匿名内部类实现多线程创建
package com.hong.Day009.Demo02;
/*
匿名内部类方式实现多线程的创建
作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现接口,重写接口中的方法,创建实现类对象合一步完成
格式:
new 父类/接口(){
重复父类/接口中的方法
};
*/
public class Demo02 {
public static void main(String[] args) {
//线程父类Thread
//new MyThread().start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i + "黑马");
}
}
}.start();
//线程接口Runnable
//Runnable r =new RunnableImpl();//多态
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i + "程序员");
}
}
};
new Thread(r).start();
//简化Runnable
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i + "www");
}
}
}).start();
}
}
多线程运行原理
并发与并行
并发:两个或多个事件在同一个时间段内发生
并行:两个或多个事件在同一时刻内发生(同时发生)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQo4amID-1615379510653)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310093504125.png)]
进程与线程
进程:是一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程
线程:线程是进程中的一个执行单位,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以由多个线程的,这个应用程序也可以称之为多线程程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yMeV4Y4U-1615379510655)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310094057401.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5PpwPuu6-1615379510657)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310094650673.png)]
线程调度
-
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
-
抢占式调度
有限让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度
主线程(mian线程)
java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例
执行主(main)方法,从上到下依次执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6BXuuIGz-1615379510660)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310095858773.png)]
运行原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-91MF2G21-1615379510661)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310082854726.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wp8gUsd0-1615379510663)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310083402738.png)]
Thread类的常用方法
获取和设置线程的名称
package com.hong.Day009.Demo01;
/*
获取线程的名称:
1.使用Thread类中的方法getName()
String getName() 返回此线程的名称。
2.可以先获取到当前正在执行的线程,使用线程中的getName()获取线程名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
*/
//1.定义一个Thread类的子类
public class MyThread1 extends Thread{
//2.重写run方法,设置线程任务
@Override
public void run() {
//获取线程的名称
// 方法1
// String name = getName();
// System.out.println(name);
// 方法2
// Thread thread = Thread.currentThread();
// System.out.println(thread);
// 方法3
System.out.println(Thread.currentThread().getName());
}
}
package com.hong.Day009.Demo01;
/*
设置线程的名称:
1.使用Thread类中的setName(名字) 方法
void setName(String name) 将此线程的名称更改为等于参数 name 。
2.创建一个带参数的构造方法,参数传递线程的名称,通过父类的带参数构造方法,把线程名称传递给父类,让父类(Thread)给子线程取一个名字
Thread(String name) 分配一个新的 Thread对象。
*/
public class Demo03 {
public static void main(String[] args) {
//创建Thread类的子类对象
MyThread1 mt = new MyThread1();
//调用start方法,开启新线程
mt.setName("小强");
mt.start();//Thread-0 //Thread[Thread-0,5,main] //Thread-0
new MyThread1().start();//Thread-1 //Thread[Thread-1,5,main] //Thread-1
}
}
暂定线程(Sleep)
package com.hong.Day009.Demo01;
/*
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
*/
public class Demo01 {
public static void main(String[] args) {
//模拟秒表
for (int i = 1; i <= 60; i++) {
System.out.println(i);
//使用Thread类的sleep方法让程序睡眠i秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程安全问题
概述
安全问题可能出现的前提:多线程访问了共享数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G62rugUo-1615379510664)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310100829778.png)]
线程安全问题代码实现(卖票案例)
package com.hong.Day009.Demo02;
/*
模拟卖票案例
创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象
Thread t0 = new Thread(run,"票口1");
Thread t1 = new Thread(run,"票口2");
Thread t2 = new Thread(run,"票口3");
//调用start方法
t0.start();
t1.start();
t2.start();
}
}
package com.hong.Day009.Demo02;
/*
实现卖票案例
*/
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--;
}
}
}
}
线程安全问题产生的原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsOvw4Nv-1615379510666)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310102548335.png)]
同步技术原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ap43cFYR-1615379510667)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310105255477.png)]
解决线程安全问题(同步代码块)
package com.hong.Day009.Demo02;
/*
实现买票案例
出现了线程安全问题:卖出了不存在的票和重复的票
解决线程安全问题的第一种方法:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的资源
private int ticket = 1000;
//创建一个锁对象(唯一)
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--;
}
}
}
}
}
package com.hong.Day009.Demo02;
/*
模拟买票案例
创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法
t0.start();
t1.start();
t2.start();
}
}
解决线程安全问题(同步方法)
package com.hong.Day009.Demo02;
/*
实现买票案例
出现了线程安全问题:卖出了不存在的票和重复的票
解决线程安全问题的第二种方法:使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.同步方法也会把方法内部锁住只让一个线程执行
2.同步方法的锁住对象是 new RunnableImpl() 也就是this
3.静态同步方法锁住对象是本类的class属性-->class文件对象(反射)
*/
public class RunnableImpl implements Runnable {
//定义一个多个线程共享的资源
private int ticket = 1000;
//静态同步方法
/*private static int ticket = 1000;*/
//设置线程任务:买票
@Override
public void run() {
//先判断票是否存在
//使用死循环,让买票行为重复执行
while (true) {
//创建一个同步代码块
payTicket();
}
}
/*
定义一个同步方法
*/
public /*static*/ synchronized void payTicket(){
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
package com.hong.Day009.Demo02;
/*
模拟买票案例
创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法
t0.start();
t1.start();
t2.start();
}
}
解决线程安全问题(Lock锁)
package com.hong.Day009.Demo02;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
实现买票案例
出现了线程安全问题:卖出了不存在的票和重复的票
解决线程安全问题的第三种方法:使用Lock锁
java.util.concurrent.locks
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
Lock接口中的方法:
void lock() 获得锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements locks
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的void lock()获得锁
3.在可能会出现安全问题的代码后调用Lock接口中的void unlock()释放锁
格式:
注意:
*/
public class RunnableImpl implements Runnable {
//定义一个多个线程共享的资源
private int ticket = 1000;
//在成员位置创建一个ReentrantLock对象
Lock lock1 = new ReentrantLock();
//设置线程任务:买票
@Override
public void run() {
//先判断票是否存在
//使用死循环,让买票行为重复执行
/* while (true) {
//2.在可能会出现安全问题的代码前调用Lock接口中的void lock()获得锁
lock1.lock();
//创建一个同步代码块
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
//3.在可能会出现安全问题的代码后调用Lock接口中的void unlock()释放锁
lock1.unlock();
}*/
//更好的写法
while (true) {
//2.在可能会出现安全问题的代码前调用Lock接口中的void lock()获得锁
lock1.lock();
//创建一个同步代码块
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
//票存在
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能会出现安全问题的代码后调用Lock接口中的void unlock()释放锁
lock1.unlock();
}
}
}
}
}
package com.hong.Day009.Demo02;
/*
模拟买票案例
创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法
t0.start();
t1.start();
t2.start();
}
}
等待唤醒机制
概述
线程间通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
等待唤醒机制(线程之间的通信):
- wait:线程不再活动,不再参与调度,进入wait set中,因此不会浪费CPU资源,也不会去竞争锁,这是的线程状态是WAITING,它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set中释放处理,重新进入到调度队列(ready queue)中
- notify:则选取所通知对象的wait set 中的一个线程释放:例如,餐馆有空位置后,等待就餐最久的顾客最先入座
- notifyAll:则释放所通知对象的wait set上的全部线程
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此时它已经不再持有锁了,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能再当初调用wait方法之后的地方恢复执行。
总结如下:
- 如果能获取锁,线程就从WAITING 状态变成 RUNNABLE 状态;
- 否则,从wait set出来,又进入entry set,线程就从WAITING 状态编程BLOCKED 状态
调用的注意细节:
- wait方法与notify方法必须由同一个锁对象调用。因为,对应的锁对象可以通过notify唤醒同一个锁对象调用的wait方法后的线程。
- wait方法与notify方法是属于Object类的方法。因为,锁对象可以是任意的对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者同步函数中使用。因为,必须要通过锁对象调用这两个方法
生产者与消费者案例
package com.hong.Day009.Demo03;
/*
分析:
资源类包子类
属性:
皮
馅
包子的状态:true false
生产者(包子铺)类:
一个线程类,可以继承Thread
重写run方法生产包子:
对包子的状态进行判断:(交替生产两种包子 i%2)
true --> 包子铺进入等待状态 -->唤醒吃货线程吃包子
false --> 包子铺进入生产包子状态 -->修改包子状态为true
吃货(消费者)类:
一个线程类,可以继承Thread
重写run方法吃包子:
对包子的状态进行判断:
true --> 吃货进行吃包子状态 -->吃完包子修改包子的状态为false
false --> 吃货进入wait状态
测试类:
包含main方法类
创建包子对象,创建线程,创建吃货线程
*/
public class Demo01 {
public static void main(String[] args) {
//创建包子对象
Baozi bz = new Baozi();
//创建包子铺线程
new BaoZiPu(bz).start();
//创建吃货线程
new ChiHuo(bz).start();
}
}
package com.hong.Day009.Demo03;
/*生产者(包子铺)类:
一个线程类,可以继承Thread
重写run方法生产包子:
对包子的状态进行判断:(交替生产两种包子 i%2)
true --> 包子铺进入等待状态 -->唤醒吃货线程吃包子
false --> 包子铺进入生产包子状态 -->修改包子状态为true
注意:
包子铺线程和包子线程关系-->(通信)互斥
必须使用同步技术保证两个线程只能有一个在执行
锁对象必须要保证唯一,可以使用包子对象作为锁对象
1.需要在成员位置创建一个包子变量
2.使用带参数构造方法,为这个包子变量赋值
*/
public class BaoZiPu extends Thread {
//1.需要在成员位置创建一个包子变量
private Baozi bz;
//2.使用带参数构造方法,为这个包子变量赋值
public BaoZiPu(Baozi bz) {
this.bz = bz;
}
@Override
public void run() {
//必须使用同步技术保证两个线程只能有一个在执行
int count = 2;
//让包子铺一直生产包子
while (true) {
synchronized (bz) {
//对包子的状态进行判断
if (bz.flag == true) {
//包子铺进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行,包子铺生产包子(交替生产两种包子)
if (count % 2 == 0) {
//生产 薄皮肉馅包子
bz.pier = "薄皮";
bz.xianer = "肉馅";
} else {
//生产 薄皮青菜馅包子
bz.pier = "薄皮";
bz.xianer = "青菜";
}
count++;
System.out.println("包子铺正在生产" + bz.pier + bz.xianer + "包子");
//生产包子需要3秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改包子状态为true
bz.flag = true;
//唤醒吃货线程吃包子
bz.notify();
System.out.println("包子铺已经生产好了" + bz.pier + bz.xianer + "包子,吃货已经可以开始吃了");
}
}
}
}
package com.hong.Day009.Demo03;
/*吃货(消费者)类:
一个线程类,可以继承Thread
重写run方法吃包子:
对包子的状态进行判断:
true --> 吃货进行吃包子状态 -->吃完包子修改包子的状态为false
false --> 吃货进入wait状态*/
public class ChiHuo extends Thread {
//1.需要在成员位置创建一个包子变量
private Baozi bz;
//2.使用带参数构造方法,为这个包子变量赋值
public ChiHuo(Baozi bz) {
this.bz = bz;
}
//吃包子
@Override
public void run() {
//必须使用同步技术保证两个线程只能有一个在执行
//让吃货一直吃包子
while (true) {
synchronized (bz) {
//对包子的状态进行判断
if (bz.flag == false) {
//吃货进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行,吃货开始吃包子
System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包子");
//吃包子需要1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改包子状态为false
bz.flag = false;
//唤醒包子铺线程吃包子
bz.notify();
System.out.println("吃货已经把" + bz.pier + bz.xianer + "的包子吃完了");
System.out.println("=====================================================");
}
}
}
}
package com.hong.Day009.Demo03;
/*资源类包子类
属性:
皮
馅
包子的状态:true false*/
public class Baozi {
String pier;
String xianer;
boolean flag = false;//包子资源是否存在 包子资源状态
}
线程状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQgA3M9a-1615379510668)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310153445370.png)]
/*
java.lang.Thread.State
线程状态:
1.NEW 尚未启动的线程处于此状态。
2.RUNNABLE 在Java虚拟机中执行的线程处于此状态。
3.BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
4.WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
5.TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
6.TERMINATED 已退出的线程处于此状态。
*/
等待唤醒案例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcNNvtn0-1615379510668)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310154033226.png)]
package com.hong.Day009.Demo04;
/*
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子种类和数量,调用wait方法,放弃CPU的执行,进入到WAITING状态
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
如果要一直循环可以加while(true)作为死循环
*/
public class Demo01 {
public static void main(String[] args) {
//创建一个锁对象,保证唯一
Object obj = new Object();
//创建一个顾客线程(消费者) (匿名内部类)
new Thread(){
@Override
public void run() {
//保证等待和唤醒只能有一个在执行,需要使用同步技术
synchronized (obj){
System.out.println("告诉老板要的包子的种类和数量");
//调用wait方法
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,开吃");
}
}
}.start();
//创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
//花5秒做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//保证等待和唤醒只能有一个在执行,需要使用同步技术
synchronized (obj){
System.out.println("告知顾客,可以吃包子了");
//唤醒顾客吃包子
obj.notify();
}
}
}.start();
}
Object类(wait、notifyAll)
/*
进入到TimeWating(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果再毫秒值结束后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
notify()只能唤醒一个
notifyAll()能唤醒全部
*/
线程池
概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rJ9oPWlh-1615379510669)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310160858906.png)]
代码实现
package com.hong.Day009.Demo04;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/* 线程池:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
返回的是ExecutorService接口的实现类对象
1.Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
2.void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
线程池的使用步骤:
1.使用线程池的工厂类 Executors里边提供的静态方法newFixedThreadPool生产一个指定线程池数量的线程池
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit,传递线程任务(实现类)
4.调用ExecutorService中的方法shutdown销毁线程池(不推荐)
*/
public class Demo02 {
public static void main(String[] args) {
//1.使用线程池的工厂类 Executors里边提供的静态方法newFixedThreadPool生产一个指定线程池数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//3.调用ExecutorService中的方法submit,传递线程任务(实现类)
es.submit(new ThreadRunnable());//pool-1-thread-2创建了一个新的线程执行
es.submit(new ThreadRunnable());//pool-1-thread-1创建了一个新的线程执行
es.submit(new ThreadRunnable());//pool-1-thread-2创建了一个新的线程执行
//4.调用ExecutorService中的方法shutdown销毁线程池(不推荐)
es.shutdown();
}
}
package com.hong.Day009.Demo04;
/*
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
*/
public class ThreadRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "创建了一个新的线程执行");
}
}
Lambda表达式
在数学中,函数就是有输入量,输出量的一套计算方法,也就是“拿什么东西做什么事情”,相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法–强调做什么,而不是以什么形式做。
冗余的Runnable代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDoHjnlr-1615379510670)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310163910619.png)]
package com.hong.Day009;
/*
实现Runnable接口的方式(可使用匿名内部类):
1.创建Runnable接口的实现类
2.覆盖重写Runnable接口的run方法
*/
public class Demo05 {
public static void main(String[] args) {
//匿名内部类
Runnable task = new Runnable() {
@Override
public void run() {//覆盖重写run方法
System.out.println(Thread.currentThread().getName() + "多线程任务执行");
}
};
new Thread(task).start();//启动线程
//再次简化
new Thread(new Runnable() {
@Override
public void run() {//覆盖重写run方法
System.out.println(Thread.currentThread().getName() + "多线程任务执行");
}
}).start();
}
}
Lambda的更优写法
package com.hong.Day009.Demo04;
public class Demo06 {
public static void main(String[] args) {
//使用匿名内部类的方式实现多线程
new Thread(new Runnable() {
@Override
public void run() {//覆盖重写run方法
System.out.println(Thread.currentThread().getName() + "多线程任务执行");
}
}).start();
//使用Lambda表达式,实现多线程
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "多线程任务执行");
}
).start();
}
}
Lambda标准格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zv9QdemM-1615379510671)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310164734177.png)]
/*
Lambda表达式的标准格式:
由三部分组成:
a.一些参数
b.一个箭头
c.一段代码
格式:
(参数列表) -> {一些重写方法的代码};
*/
练习:使用Lambda标准格式(无参无返回)
给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数,无返回值,如下:
package com.hong.Day009.Demo05;
public interface Cook {
void makeFood();
}
package com.hong.Day009.Demo05;
/*
需求:
给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数,无返回值
使用Lambda的标准格式调用invokeCook方法,打印输出“吃饭啦!”字样
*/
public class Demo01 {
public static void main(String[] args) {
//调用invokeCook方法
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭啦!");
}
});
//使用Lambda表达式
invokeCook(()-> {
System.out.println("吃饭啦!");
});
}
//
private static void invokeCook(Cook cook){
cook.makeFood();
}
}
练习:使用Lambda标准格式(有参数有返回)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Q4zTEK8-1615379510672)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310191525874.png)]
package com.hong.Day009.Demo06;
import java.util.Arrays;
public class Demo01 {
public static void main(String[] args) {
//使用数组存储多个Person对象
Person[] arr = {
new Person("柳岩",17),
new Person("迪丽热巴",18),
new Person("古力娜扎",19)
};
//对数组中的Person对象使用Arrays的sort方法通过年龄进行升序(前边-后边)排列
/* Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});*/
//使用Lambda方法,简化匿名类
Arrays.sort(arr, (Person o1,Person o2)-> {
return o1.getAge() - o2.getAge();
});
//遍历数组
for (Person p : arr) {
System.out.println(p);
}
}
}
package com.hong.Day009.Demo06;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
练习:给定计算机接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWz4m286-1615379510672)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310192743807.png)]
package com.hong.Day009.Demo06;
public class Demo02 {
public static void main(String[] args) {
//调用invokeCalc方法,可以使用匿名内部类
invokeCalc(10, 20, new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
});
//使用Lambda表达式简化匿名内部类
invokeCalc(10, 20, (int a,int b)-> {
return a + b;
});
}
/*
定义一个方法
参数传递两个int类型的整数
参数传递Calculator接口
方法内部调用Calculator中的方法calc计算两个整数的和
*/
private static void invokeCalc(int a,int b,Calculator calculator){
int result = calculator.calc(a,b);
System.out.println("结果是" + result);
}
}
package com.hong.Day009.Demo06;
public interface Calculator {
int calc(int a ,int b);
}
Lambda表达式可以省略的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJCRZGsB-1615379510673)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310193943614.png)]
File类
基本构造方法
package com.hong.Day009.Demo07;
import java.io.File;
/*
java.io.File
文件和目录路径名的抽象表示。
构造方法:
1.File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
2.File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
3.File(String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例。
重点:
file 文件
directory 文件夹
path 路径
*/
public class Demo02 {
public static void main(String[] args) {
show01();
}
/*
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
参数:
String pathname:字符串的路径名称
路径可以是以文件结尾,也可以是以文件夹结尾
路径可以是相对路径,也可以是绝对路径
路径可以是存在的,也可以是不存在的
创建File对象,只是把字符串路径封装为File对象,不考虑路径的真实情况
*/
private static void show01() {
File f1 = new File("C:\\Users\\asus\\Desktop\\java博客");
System.out.println(f1);//重写了Object中的toString方法
}
/*
File(String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例。
参数:把参数分成了两部分
String parent : 父路径
String child :子路径
好处:
父路径和子路径,可以单独书写,使用起来非常灵活,父路径和子路径都可以改变
*/
/*
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
参数:把参数分成了两部分
File parent : 父路径
String child :子路径
好处:
父路径和子路径,可以单独书写,使用起来非常灵活,父路径和子路径都可以改变
父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象
*/
}
静态成员变量
/*
java.io.File
文件和目录路径名的抽象表示。
静态成员变量:
1.static String pathSeparator(路径分隔符 Windows:分号 Linux:冒号)
与系统相关的路径分隔符字符,为方便起见,表示为字符串。
2.static char pathSeparatorChar
与系统相关的路径分隔符。
3.static String separator(文件名称分隔符 Windows:反斜杠 Linux:正斜杠)
与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串。
4.static char separatorChar
与系统相关的默认名称分隔符。
操作路径:路径不能写死了
C:\deveiop\a\a.txt Windows
C:/deveiop/a/a.txt Linux
C:"+File.separator+"deveiop"+File.separator+"a"+File.separator+"a.txt
*/
绝对路径和相对路径
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xAty6Ot0-1615379510675)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310195443994.png)]
获取功能方法
package com.hong.Day009.Demo07;
/*
1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
3.String getPath() 将此抽象路径名转换为路径名字符串。
4.long length() 返回由此抽象路径名表示的文件的长度。
*/
public class Demo03 {
public static void main(String[] args) {
}
/*
1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
获取的构造方法中的传递路径
无论路径是绝对的还是相对的,返回都是绝对路径
*/
/*
3.String getPath() 将此抽象路径名转换为路径名字符串。
获取的构造方法中的传递路径
传递的路径是什么类型返回的路径就是什么类型
*/
/*
2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
返回路径的结尾,可以是文件也可以是文件夹
*/
/*
4.long length() 返回由此抽象路径名表示的文件的长度。
获取的是构造方法指定的文件的大小,以字节为单位
文件夹没有大小的概念
*/
}
判断功能方法
/*
1.boolean isDirectory() 测试此抽象路径名表示的文件是否为目录。
2.boolean isFile() 测试此抽象路径名表示的文件是否为普通文件。
3.boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
*/
创建删除功能方法
/*
1.boolean createNewFile() 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。
2.boolean delete() 删除由此抽象路径名表示的文件或目录。
3.boolean mkdir() 创建由此抽象路径名命名的目录。
4.boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
*/
遍历文件夹目录功能
/*
1.String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
2.File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
*/
File.separator+"a"+File.separator+"a.txt
*/
绝对路径和相对路径
[外链图片转存中…(img-xAty6Ot0-1615379510675)]
获取功能方法
package com.hong.Day009.Demo07;
/*
1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
3.String getPath() 将此抽象路径名转换为路径名字符串。
4.long length() 返回由此抽象路径名表示的文件的长度。
*/
public class Demo03 {
public static void main(String[] args) {
}
/*
1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
获取的构造方法中的传递路径
无论路径是绝对的还是相对的,返回都是绝对路径
*/
/*
3.String getPath() 将此抽象路径名转换为路径名字符串。
获取的构造方法中的传递路径
传递的路径是什么类型返回的路径就是什么类型
*/
/*
2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
返回路径的结尾,可以是文件也可以是文件夹
*/
/*
4.long length() 返回由此抽象路径名表示的文件的长度。
获取的是构造方法指定的文件的大小,以字节为单位
文件夹没有大小的概念
*/
}
判断功能方法
/*
1.boolean isDirectory() 测试此抽象路径名表示的文件是否为目录。
2.boolean isFile() 测试此抽象路径名表示的文件是否为普通文件。
3.boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
*/
创建删除功能方法
/*
1.boolean createNewFile() 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。
2.boolean delete() 删除由此抽象路径名表示的文件或目录。
3.boolean mkdir() 创建由此抽象路径名命名的目录。
4.boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
*/
遍历文件夹目录功能
/*
1.String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
2.File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
*/