第四章第五节Java核心类库_多线程

  • 多线程
    • 一.线程与进程
      • 1.线程与进程
      • 2.线程调度
    • 二.同步与异步&并发与并行
      • 1. 同步与异步
      • 2. 并发与并行
    • 三.继承Thread
      • 1.代码块
      • 2.运行结果
      • 3.时序图
    • 四.实现Runnable(推荐使用)
      • 1.使用方法
      • 2.代码块
      • 3.thread也有一定的好处
    • 五.Thread类
      • 1.常用构造方法
      • 2.其他常用方法
    • 六.设置和获取线程名称
    • 七.线程休眠sleep
    • 八.线程的中断
      • 1.代码
      • 2.运行结果
    • 九.守护线程
    • 十.线程安全
      • 解决方案1-同步代码块
      • 解决方案2-同步方法
      • 解决方案3-显式锁Lock
    • 十一.公平锁与非公平锁
    • 十二.死锁典例
    • 十三.多线程通信问题
    • 十四.生产者与消费者
      • Test one:
      • Test two:
      • Test three:
    • 十五.线程的六种状态
    • 十六.线程创建的第三种方式-带返回值的线程Callable
    • 十七.线程池 Executors
    • 十八.Lambda表达式

多线程

一.线程与进程

1.线程与进程

程序(program):

是为了完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。(程序是静态的)

进程(process):

是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。
进程是动态的,是一个动的过程,有它自身的产生、存在和消亡的过程。

线程(thread):

进程可以进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程。

2.线程调度

目的是为了更合理的利用CPU

分时调度:

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度(Java使用的调度方式):

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
但其实,多线程程序并不能提高程序的运行速度但能够提高程序运行效率,让CPU的使用率更高。

二.同步与异步&并发与并行

1. 同步与异步

同步: 排队执行 ,效率低但是安全

异步: 同时执行 ,效率高但是数据不安全

2. 并发与并行

并发:指两个或多个事件在同一个时间段内发生。

并行:指两个或多个事件在同一时刻发生(同时发生)。

三.继承Thread

1.代码块

Demo1

public class Demo1 {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();for(int i = 0; i < 10; i++){System.out.println("我要睡觉!"+i);}}

MyThread

public class MyThread extends Thread{/*** run方法就是线程要执行的任务方法*/@Overridepublic void run() {//这里的代码就是一条新的执行路径//这个执行路径的触发方式不是调用run方法,//而是通过thread对象的start()来启动任务for(int i = 0; i < 10; i++){System.out.println("我要吃饭!"+i);}}
}

2.运行结果

3.时序图


补充:每个线程都有自己的栈空间,共用一份堆内存。

四.实现Runnable(推荐使用)

1.使用方法

另一种实现多线程的方法之步骤:
①创建自定义类implements实现Runnable接口,并重写run方法;
②用自定义类创建一个对象r;
③用Thread类创建一个对象t,并将r作为t构造方法的参数;

2.代码块

实现Runnable与继承Thread相比有如下优势
1,通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况;
2,可以避免单继承所带来的局限性(Java允许实现多个接口,但不允许继承多个父类);
3,任务与线程是分离的,提高了程序的健壮性;
4,后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程;

Demo1

public class Demo1 {public static void main(String[] args) {//实现Runnable//1.    创建一个任务对象MyRunnable r = new MyRunnable();//2.    创建一个线程,并为其分配一个任务Thread t = new Thread(r);//3.    执行这个线程t.start();for(int i = 0; i < 10; i++){System.out.println("我要睡觉!"+i);}}
}

MyRunnable

/*** 用于给线程执行的任务*/
public class MyRunnable implements Runnable {@Overridepublic void run() {//线程的任务for (int i=0;i<10;i++){System.out.println("我要上厕所"+i);}}
}

3.thread也有一定的好处

public class Demo1 {public static void main(String[] args) {//若只调用一次,用thread可以通过匿名内部类的方式,使代码更加简洁new Thread(){@Overridepublic void run() {for (int i=0;i<10;i++){System.out.println("我要睡觉"+i);}}}.start();for (int i=0;i<10;i++){System.out.println("我要上厕所"+i);}}
}

五.Thread类

1.常用构造方法

2.其他常用方法


用于设置优先级的字段


六.设置和获取线程名称

MyRunnable

/*** 用于给线程执行的任务*/
public class MyRunnable implements Runnable {@Overridepublic void run() {//线程的任务//静态方法currentThread可以获得当前线程,调用getName/setName// 可以获得/设置线程的名称System.out.println(Thread.currentThread().getName());}
}

Demo1

public class Demo1 {public static void main(String[] args) {System.out.println(Thread.currentThread().getName());new Thread(new MyRunnable(),"喜羊羊").start();new Thread(new MyRunnable(),"美羊羊").start();new Thread(new MyRunnable(),"懒羊羊").start();}
}

运行结果

七.线程休眠sleep

public class Demo1 {public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++){System.out.println(i);Thread.sleep(1000);}}
}

八.线程的中断

过时的stop方法可以直接中断线程,但是如果线程来不及释放资源,会造成一部分垃圾无法回收
这里采用添加中断标记的方法:调用interrupt方法,子线程执行时捕获中断异常,并在catch块中,添加处理释放资源的代码。

1.代码

public class Demo1 {public static void main(String[] args) throws InterruptedException {//线程的中断//一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定Thread t = new Thread(new MyRunnable(), "子线程");t.start();for (int i =0;i < 5;i++){System.out.println(Thread.currentThread().getName()+":"+i);Thread.sleep(1000);}t.interrupt();//主线程for循环执行完毕后,将子线程中断}static class MyRunnable implements Runnable {@Override//这里的run方法不能将异常抛出,因为这里父接口Runnable没有声明异常的抛出//子接口不能声明比父接口范围更大的异常//所以只能进行try-catchpublic void run() {for (int i =1;i <= 10;i++){System.out.println(Thread.currentThread().getName()+":"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("我挂机了嘤嘤嘤~");return;}}}}}

2.运行结果

九.守护线程

概述:
线程分为守护线程和用户线程;
- 用户线程:当一个进程不包含任何存活的用户线程时,进程结束;
- 守护线程:守护用户线程,当最后一个用户线程结束后,所有守护线程自动死亡;
直接创建的都是用户线程;
设置守护线程:线程对象.setDaemon(true),而且一定要在启动之前设置。

十.线程安全

线程不安全的原因:
多个线程争抢同一个数据,使得数据在判断和使用时出现不一致的情况。

解决方案1-同步代码块

public class Demo1 {public static void main(String[] args) throws InterruptedException {Object o = new Object();//线程不安全//解决方案1  同步代码块//格式:synchronized(锁对象){}Runnable run = new Ticket();new Thread(run).start();new Thread(run).start();new Thread(run).start();}static class Ticket implements Runnable{//总票数private int count = 10;private Object o = new Object();@Overridepublic void run() {//Object o = new Object();    //这里不是同一把锁,所以锁不住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;}}//synchronized结束}//while结束}//run结束}//Ticket结束
}//Demo1结束

解决方案2-同步方法

public class Demo1 {public static void main(String[] args) throws InterruptedException {Object o = new Object();//线程不安全//解决方案2  同步方法Runnable run = new Ticket();new Thread(run).start();new Thread(run).start();new Thread(run).start();}static class Ticket implements Runnable{//总票数private int count = 10;@Overridepublic void run() {while (true) {boolean flag = sale();if(!flag){break;}}}public synchronized boolean sale(){if (count > 0) {//卖票System.out.println("正在准备卖票");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);return true;}return false;}}
}

给方法上锁,对应的锁对象就是this, 如果是静态修饰方法的话,锁对象为类名.class;
比如这里sale方法若被修饰为静态方法的话,锁对象为Ticket.class,也就是字节码文件对象

解决方案3-显式锁Lock

同步方法和同步代码块都属于隐式锁显式锁则是程序员手动加锁、解锁

public class Demo{public static void main(String[] args) {Object o = new Object();//线程不安全//解决方案3   显示锁  Lock  子类 ReentrantLockRunnable run = new Ticket();new Thread(run).start();new Thread(run).start();new Thread(run).start();}static class Ticket implements Runnable{//总票数private int count = 10;//参数为true表示公平锁    默认是false 不是公平锁private Lock l = new ReentrantLock(true);@Overridepublic 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();}}}
}

十一.公平锁与非公平锁

-区别
公平锁:先来先得,遵循排队;
非公平锁:大家一起抢(同步代码块,同步方法,显式锁都属于非公平锁);

-实现方法
在显式锁实例化时,传入参数true()

//参数为true表示公平锁    默认是false 不是公平锁
private Lock l = new ReentrantLock(true);

十二.死锁典例

public class Demo {/*** 死锁典例:两个线程互相申请对方的锁,但是对方都不释放锁。* @param args*/public static void main(String[] args) {//线程死锁Culprit culprit = new Culprit();Police police = new Police();//新建一个线程,警察对罪犯说话,等待罪犯回应//警察的say方法里面打印完自己说的话之后,便等待罪犯的回应//而另一边罪犯的say方法也在等待警察的回应,且罪犯的say是带锁的//故罪犯的reaction方法一直到不了,警察的say就一直在等罪犯的reactionnew MyThread(culprit,police).start();//main主线程,罪犯对警察说话,等待警察回应//罪犯的say方法里面打印完自己说的话之后,便等待警察的回应//而另一边警察的say方法也在等待罪犯的回应,且警察的say是带锁的//故警察的reaction方法一直到不了,罪犯的say就一直在等警察的reactionculprit.say(police);}static class MyThread extends Thread{//私有两个属性,一个警察p一个罪犯cprivate Culprit c;private Police p;//两参的构造方法public MyThread(Culprit c, Police p) {this.c = c;this.p = p;}@Overridepublic void run() {p.say(c);//警察对罪犯说话,等待罪犯回应}}static class Culprit{public synchronized void say(Police p){System.out.println("罪犯说:你放了我,我放了人质");p.reaction();}public synchronized void reaction(){System.out.println("罪犯说:好的,我放了人质,你放过我");}}static class Police{public synchronized void say(Culprit c){System.out.println("警察说:你放了人质,我放过你");c.reaction();}public synchronized void reaction(){System.out.println("警察说:好的,我放了你,你把人质给我");}}}

解决方案:
在任何有可能导致锁产生的方法里,不要再调用另外一个方法让另外一个锁产生;
一个方法已经产生一个锁了,就不要再去找其他有可能产生锁的方法了。

十三.多线程通信问题

主要借助于wait和notify函数实现

十四.生产者与消费者

思路:

厨师cook为生产者线程,服务员waiter为消费者线程,食物为生产与消费的物品;

假设目前只有一个厨师,一个服务员,一个盘子。理想状态是:厨师生产一份饭菜,服务员端走一份,且饭菜的属性未发生错乱;

厨师可以制作两种口味的饭菜,制作100次;

服务员可以端走饭菜100次。

Test one:

public class TestOne{public static void main(String[] args) {//多线程通信    生产者与消费者问题Food f = new Food();new Cook(f).start();new Waiter(f).start();}//厨师static class Cook extends Thread{private Food f;//构造方法public Cook(Food f) {this.f = f;}//做五十份老干妈小米粥和五十份煎饼果子@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i%2==0){// 设计两种菜色f.setNameAndTaste("老干妈小米粥","香辣味");}else {f.setNameAndTaste("煎饼果子","甜辣味");}//else结束}//for结束}//run结束}//Cook结束//服务员static class Waiter extends Thread{private Food f;//构造方法public Waiter(Food f) {this.f = f;}//上100次菜@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {Thread.sleep(100);//端完一次休息一下:因为厨师在setNameAndTaste中有休眠100毫秒} catch (InterruptedException e) {e.printStackTrace();}f.get();//端菜}//for结束}//run结束}//Waiter结束//食物static class Food{private String name;private String taste;// 生产public void setNameAndTaste(String name,String taste){this.name = name;//设置名称和味道之间加入休眠,为了演示:在此期间时间片可能发生丢失,因而菜色属性发生错乱的情况。try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.taste = taste;}// 消费public void get(){System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);}//get结束}//Food结束}//TestOne结束

运行结果

原因分析:在厨师进行setNameAndTaste的时候,先设置了name,此时休眠了0.1秒,而taste还未来得及切换,服务员此时就把菜端出去了,出现了多线程协同合作的不匹配问题。

Test two:

给get、setNameAndTaste两个方法都加上同步标记synchronized,让厨师在进行setNameAndTaste时候服务员别进来瞎掺和进行get,服务员get的时候厨师别进来瞎掺和进行setNameAndTaste。

错误原因分析:synchronized只是确保了方法内部不会发生线程切换,但并不能保证生产一个消费一个的逻辑关系。

Test three:

终极解决方案:
厨师做完饭后喊醒服务员,自己睡着;服务员送完饭后喊醒厨师,自己睡着。主要修改的部分为setNameAndTaste与get方法。

public synchronized void setNameAndTaste(String name,String taste) {if (flag) {this.name = name;//设置名称和味道之间加入休眠,为了演示:在此期间时间片可能发生丢失,因而菜色属性发生错乱的情况。try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.taste = taste;//做完了把标记改为falseflag = false;//唤醒在当前this对象下所有睡着的线程(即唤醒服务员)this.notifyAll();//厨师自己睡过去try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}
}//setNameAndTaste结束// 消费
public synchronized void get(){if (!flag){System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);//端完了把标记改为trueflag = true;//唤醒在当前this对象下所有睡着的线程(即唤醒厨师)this.notifyAll();//服务员自己睡过去try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}
}//get结束

运行结果

十五.线程的六种状态

十六.线程创建的第三种方式-带返回值的线程Callable

新的创建线程的方式。之前的创建线程方式:实现Thread的子类、实现Runnable接口,可以看成是和主线程并发执行的;这里要讲的线程更像是主线程指派的一个任务,主线程可以获得其返回值。
后续开发使用较少,仅作了解,不再赘述。

十七.线程池 Executors

为什么需要线程池?

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

后续开发中使用到线程池的地方很少,不是重点,不再赘述,详情看老师发的pdf。

十八.Lambda表达式

为什么要用lambda表达式?
答: 对于某些应用场景,我们更注重于结果,如果能用一个方法解决, 那么通过创建对象、调用方法的方式可能会更加繁琐。

1)不使用lambda

public class Demo1 {/*** lambda表达式* 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)* @param args*/public static void main(String[] args) {print(new MyMath() {@Overridepublic int sum(int x, int y) {return x + y;}}, 100, 200);}public static void print(MyMath m, int x, int y){int num = m.sum(x, y);System.out.println(num);}static  interface MyMath{int sum(int x, int y);}
}

2)使用lambda
不需要实现接口、实例化对象;

public class Demo1 {/*** lambda表达式* 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)* @param args*/public static void main(String[] args) {print((int x, int y) -> {return x + y;}, 100, 200);}public static void print(MyMath m, int x, int y){int num = m.sum(x, y);System.out.println(num);}static  interface MyMath{int sum(int x, int y);}
}

参考链接:https://blog.csdn.net/qq_41528502/article/details/108052873

第四章Java核心类库_多线程相关推荐

  1. jre包括jvm和java核心类库_包含JVM标准实现及Java核心类库

    包含JVM标准实现及Java核心类库 点击次数:1533  更新日期:2013-03-24 "青花瓷Java版"为北京师范大学教育学部蔡苏作词原创,覆盖教育技术学院专业选修课< ...

  2. Java核心类库篇7——多线程

    Java核心类库篇7--多线程 1.程序.进程和线程 程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件 进程 - 主要指运行在内存中的可执行文件 线程就是进程内部的程序流 操作系统内部支持 ...

  3. Java核心类库(下)

    文章目录 Java核心类库(下) 异常机制(重点) 基本概念 异常的分类 异常的避免 异常的捕获 异常的抛出 自定义异常 异常机制总结 File类(重点) 基本概念 常用的方法 IO流 IO流的概念 ...

  4. 【JVM】第四章 Java内存模型

    第四章 Java内存模型 文章目录 第四章 Java内存模型 一.物理机的并发问题 1.硬件的效率问题 2.缓存一致性问题 3.代码乱序执行优化问题 二.Java 内存模型 1.概念 2.Java 内 ...

  5. Java核心知识点学习----多线程中的阻塞队列,ArrayBlockingQueue介绍

    1.什么是阻塞队列? 所谓队列,遵循的是先进先出原则(FIFO),阻塞队列,即是数据共享时,A在写数据时,B想读同一数据,那么就将发生阻塞了. 看一下线程的四种状态,首先是新创建一个线程,然后,通过s ...

  6. JAVA学习笔记—JAVA SE(四)JAVA核心库类(下)

    文章目录 四.JAVA核心库类(下) 1. 异常机制和File类 1.1 异常机制 1.1.1 基本概念 1.1.2 异常的分类 1.1.3 异常的避免 1.1.4 异常的捕获 1.1.5 异常的抛出 ...

  7. Java 核心类库一览

    作者:白色蜗牛 来源:蜗牛互联网 阅读本文你将收获: 类库与 JAR 文件 什么是类库 我们知道,在面向对象的程序设计里,一个类是可以调用另外一个类的方法,只要把被调用的那个类引入到 classpat ...

  8. 第十四章 Linux核心资源

    Table of Contents, Show Frames, No Frames 第十四章 Linux核心资源 本章主要描叙寻找某个特殊核心函数时用到的Linux核心资源. 本书并不要求读者具有C编 ...

  9. Java核心类库篇8——网络编程

    Java核心类库篇8--网络编程 1.七层网络模型 OSI(Open System Interconnect),即开放式系统互联,是ISO(国际标准化组织)组织在1985 年研究的网络互连模型. 当发 ...

最新文章

  1. python笔记2(函数 面向对象 文件编程 上下文管理器)
  2. EdgeGallery — AIO 离线部署 v1.5 版本
  3. mysql用的cap中哪两个_分布式事务CAP定理和BASE理论
  4. 混合样本数据增强(Mixed Sample Data Augmentation,MSDA)
  5. 控制服务器系统设计,基于 DNS 技术的顶管机远程控制系统设计与实现
  6. linq where的应用
  7. EasyUI基础入门之Parser(解析器)
  8. 用CSS3制作50个超棒动画效果教程
  9. Python实现爬取下载百度图片
  10. 关于TikTok环境伪装度检测,whoer和上网大师app的对比
  11. 同一局域网建立ftp服务器实现文件共享
  12. 万维网联盟W3C发布HTML5新logo
  13. android 动态修改logo,关于app动态修改logo的问题
  14. HTTP gzip压缩
  15. 网络代理【1】什么是网络代理
  16. pscc改变图片字体大小
  17. java 通过 冰蓝 word 转pdf ,最大程度包装pdf 样式和word接近
  18. Linux Socket 两个客户端通信,服务端作为中转
  19. java 设置背景色_背景颜色的设置
  20. 单片机入门学习笔记6:新唐单片机N76E003

热门文章

  1. 2022年全国职业技能大赛网络安全竞赛试题B模块自己解析思路(7)
  2. ffi Error:Dynamic Linking Error:Win32 error 126
  3. 一带一路中国出口马来西亚主要商品及货源地
  4. win7计算机广告更改,win7系统屏蔽营销广告的设置办法
  5. sql语句中where 1=1的作用
  6. java基于SpringBoot+vue 的简历模板分享系统 elementui前后端分离
  7. 物联网下的RFID门禁,图书防盗新变革
  8. angular中安装ng-alain 插件
  9. 【贼好理解!!】C++ list链表常用成员函数讲解
  10. STM32 CAN过滤器详解