【JAVA】 04-Java中的多线程
链接:
笔记目录:毕向东Java基础视频教程-笔记
GitHub库:JavaBXD33
目录:
<>
- <>
内容待整理:
多线程引入
概述
多线程:
- 进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间。
- 线程:进程中的一个执行单元,负责进程中的程序的运行,一个进程中至少要有一个线程。
- 多线程程序:一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
- 程序启动了多线程,有什么应用呢?可以实现多部分程序同时执行,专业术语:“并发”
多线程运行原理
- 多线程的使用需要合理使用CPU资源,线程过多会导致降低性能。
- CPU处理程序时是通过快速切换完成的,在我们看来好像随机一样(其实有自己的规律)
多线程的创建
主线程的运行方式
主线程:
- 在之前的代码中,JVM启动后,必然有一个执行路径(线程)从main方法开始,一直执行到main方法结束。
- 这个线程在java中称为主线程。
问题分析:
- 当主线程在这个程序中执行时,如果遇到了循环而导致在指定位置停留时间过长,无法执行下面的程序。
- 可不可以实现一个主线程负责执行其中一个循环,由另一个线程负责其他代码的执行,实现多部分的代码同时执行。
- 这就是多线程技术可以解决的问题。
多线程的创建:
- 通过API中的英文Thread的搜索(实在搜不到先去百度搜中文),查到了Thread类
- 通过阅读Thread类中的描述
多线程的创建一:继承Thread类
步骤:
- 1.1 定义一个类继承Thread。
- 1.2 重写run方法。
- 1.3 创建子类对象,就是创建线程对象。
- 1.4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
面试:线程对象调用run方法和调用start方法的区别?
- 调用run方法不开启线程,并让JVM调用run方法,在开启的线程中执行
- 调用start方法开启线程,并让JVM调用run方法在开启的线程中执行。
原理:继承Thread类的原理
继承Thread类:
- 因为Thread类描述线程事物,具备线程应有的功能。
- 那为什么不直接创建Thread类的对象呢?
Thread t1 = new Thread();
t1.start();
- 这么做没有错,但该start调用的是Thread类中的run方法
- 而这个run方法没有做什么事情,更重要的是,这个run方法中没有定义我们需要让线程执行的代码。
创建线程的目的是什么?
- 为了建立单独的执行路径,让多部分代码实现同时执行。
- 也就是说,线程创建并执行需要给定的代码(专业术语:线程的任务)。
- 对于之前所讲的主线程,它的任务定义在了main函数中。
- 自定义的线程需要执行的任务都定义在run方法中。
- Thread类中的run方法内部的任务并不是我们所需要的,只要重写run方法,
- 既然Thread类已经定义了线程任务的位置,只要在位置中定义任务代码即可
- 所以进行了重写run方法的动作。
内存与线程名称获取
内存:
- 多线程执行时,在栈内存中,每一个执行线程都有一片自己所属的栈内存空间,进行方法的压栈和弹栈。
- 当执行线程的任务结束了,线程自动就在栈内存中释放了。
- 当所有的执行线程都结束时,进程就结束了。
获取线程名称:
- 获取当前线程对象:
Thread: currentThread()
- 获取名称:
getName();
- 故:
Thread.currentThread().getName();
- 主线程的名称:main
- 自定义的线程:Thread-1 线程多个时,数字顺延。Thread-n,n从0开始
- 获取当前线程对象:
多线程的创建二:实现Runnable接口
步骤与理解:
- 1、定义类,实现Runnable接口:
- 避免了继承Thread类的单继承局限性
- 2、覆盖接口中的run方法,将线程任务代码定义到run方法中
- 3、创建Thread类的对象:
- 只有创建Thread类的对象才可以创建线程。
- 4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数
- 因为线程已被封装到Runnable接口的run方法中,而这个方法所属于Runnable接口的子类对象,
- 所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时,就可以明确要运行的线程的任务。
- 5、调用Thread类的start方法开启线程
- 1、定义类,实现Runnable接口:
第二种方式实现Runnable接口:
- 避免了单继承的局限性,所以较为常用。
- 实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分叫线程对象,一部分是线程任务
- 继承Thread类:线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,又有线程任务。
- 实现Runnable接口:
- 将线程任务单独分离出来,封装成对象,类型就是Runnable接口类型。- Runnable接口对线程对象和线程任务进行解耦。
- 用Runnable来标明线程任务,用Thread来明确线程对象。
线程的状态图
多线程的安全问题
发生:
- 用sleep模拟临时阻塞
问题产生的原因:
- 1、线程任务中在操作共享的数据
- 2、线程任务操作共享数据的代码有多条(运算有多个)
解决思路:
- 只要让一个线程在执行线程任务时将多条操作共享数据的代码执行完,
- 在执行过程中,不要让其他线程参与运算,就可以了。
代码体现:同步代码块synchronized
- 格式:
synchronized(对象){ //需要被同步的代码。}
//对象:相当于锁。火车上的卫生间-->同步锁
同步代码块的好处:
- 解决了多线程的安全问题。
同步的弊端:
- 降低了程序的性能。
但是为了安全,可以损失点性能。
同步的前提:
- 必须保证多个线程在同步中使用的是同一个锁
synchronized(new Object())
时有几个线程有几把锁- 同步区分:用锁 -- 不同的对象,不同的锁,就是不同的同步
- 解决了什么问题呢:
- 当多线程安全发生时,加入了同步问题依旧时,就要通过这个同步的前提来判断同步是否写正确。
同步函数:同步的另一种体现形式
- 简化同步代码块为同步函数(例如:public synchronized void sale())
- 同步函数的锁:this(因为函数必须被对象调用)
- 验证:
- 写一个同步代码块,写一个同步函数
- 如果同步代码块中的锁对象和同步函数中的锁对象是同一个,就同步了,不会有错误数据
- 如果不是同一个锁对象,就不同步,就会出现错误数据。
- 设置两个线程:一个线程在同步代码块中执行,另一个在同步函数中执行。
- 总结:同步函数使用的锁是this
同步函数和同步代码块的区别:
- 同步函数使用的锁是固定的this。当线程任务只需要一个同步时完全可以使用同步函数。
- 同步代码块使用的锁可以是任意对象。当线程任务中需要多个同步时,必须通过锁来区分,这时必须使用不同代码块。
- 同步代码块较为常用。
static同步函数使用的锁
- 不是this,而是字节码文件对象
类名.class
单例懒汉式的并发访问(高频考点)
- 并发访问有安全隐患,所以加入同步机制解决安全问题
- 但是,同步的出现却降低了效率。
- 提高效率:减少判断锁的次数,可以通过双重判断的方式。
同步的另一个弊端:死锁
- 情况之一:
- 当线程任务出现了多个同步时(多个锁)时,如果同步中嵌套了其他同步,这时容易引发一种现象:死锁
- 这种情况能避免就避免
- 注意:要会写一个死锁程序
//可能死锁,也可能大和谐了
//Thread-0 synchronized(obj1){ synchronized(obj2){} }
//Thread-1 synchronized(obj2){ synchronized(obj1){} }
多线程间通信
生产者消费者问题
- 面试会考,划重点
- 生产和消费同时执行,需要多线程。
- 但是执行的任务不同,处理的资源却相同 -- 线程间的通信。
- 代码框架:
- 1、描述资源
- 2、描述生产者,因为具备着自己的任务
- 3、描述消费者,因为具备着自己的任务
- 问题1:数据错误,已经被生产很早期的商品,才被消费到,出现了线程安全问题。加入同步解决。
- 使用同步函数。问题已解决,不会再消费到之前很早期的商品
- 问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
希望的结果应该是:生产一个商品,就被消费掉,再生产下一个商品。 - 搞清楚几个问题:
- 生产者什么时候生产?消费者什么时候消费?
- 当盘子中没有面包时,就生产;如果有了面包,就不要生产。
- 当盘子中已有面包时,就消费;如果没有面包,就不要消费。
- 思考:
- 生产者生产了商品后,应该告诉消费者来消费。这时的生产者应该处于等待状态。
- 消费者消费了商品后,应该告诉生产者来生产。这时的消费者应该处于等待状态。
- 等待:wait(); ---- 需要InterruptedException捕获异常
- 告诉:notify(); -- 唤醒
- 问题解决:实现了生产一个消费一个。
等待/唤醒机制
- 注:对象监视器:锁
- wait(); -- 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
- notify(); -- 会唤醒线程池中任意一个等待的线程。立刻有执行资格,但不一定立刻有执行权
- notifyAll(); -- 会唤醒线程池中所有的等待线程。
- 记住:
- 这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁(对象监视器)
- 同一个锁上的notify,只能唤醒该锁上的被wait的线程。
- 为什么这些方法定义在Object类中呢?
- 因为这些方法必须标识所属的锁,而锁可以是任意对象。
- 任意对象可以调用的方法必然是Object类。
- 举例:小朋友抓人游戏
多生产多消费:
- 问题1:生成了商品没有被消费,同一个商品被消费多次。
- 被唤醒的线程没有判断标记,造成问题1产生
- 解决:只要让被唤醒的线程必须判断标记就可以了。
将if判断标记的方式改为while判断标记 - 记住:只要是多生产多消费,必须while判断
- 问题2:while判断后,死锁了。
- 原因:生产方唤醒了线程池中的生产方的线程。本方唤醒了本方。
- 解决:希望本方要唤醒对方。没有对应方法,所以,唤醒所有。
- 仍然有一些小遗憾:效率低了。唤醒所有会有点多余。
JDK1.5Lock接口
- 提供了多生产多消费的解决方案
- 在java.util.concurrent.locks软件包中提供相应的解决方案
- Lock接口:比同步更厉害,有更多的操作。lock(),-- 获取锁 unlock();-- 释放锁
- 提供了一个更加面向对象的锁,在该锁中,提供了更多的显式的锁操作。
- 可以替代同步。 - 先把同步改为lock:unlock要放在try-finally的finally中确保一定会执行到
JDK1.5Condition方法
- 图示:
- 已经将旧锁替换成新锁,那么锁上的监视器方法(wait,notify,notifyAll)也应该替换成新锁的监视器方法
- 而jdk1.5中将这些原有的监视器方法封装到了一个Condition对象中。
- 想要获取监视器方法,需要先获取Condition对象。
- Condition对象的出现其实就是替换了Object中的监视器方法。
- await();
- signal();
- signalAll();
- 将所有的监视器方法替换成了Condition。但是效率低的问题仍然存在。
解决多生成多消费的效率问题
- 图示
- 希望本方可以唤醒对方中的一个。
- 老程序中可以通过两个锁嵌套完成,但是容易引发死锁。
- 新程序中可以解决这个问题:只用一个锁。
- 可以在一个锁上加上多个监视器对象。
Condition示例
- API文档的“java.util.concurrent.locks 接口 Condition”下的示例,多生产多消费问题
eclipse
总结见另一篇博文:
END
【JAVA】 04-Java中的多线程的更多相关文章
- 黑马程序员_ JAVA中的多线程
------- android培训.java培训.期待与您交流! ---------- 尽管线程对象的常用方法可以通过API文档来了解,但是有很多方法仅仅从API说明是无法详细了解的. 本来打算用一节 ...
- Java 中传统多线程
目录 Java 中传统多线程 线程初识 线程的概念 实现线程 线程的生命周期 常用API 线程同步 多线程共享数据的问题 线程同步及实现机制 线程间通讯 线程间通讯模型 线程中通讯的实现 @(目录) ...
- 第35节:Java面向对象中的多线程
Java面向对象中的多线程 多线程 在Java面向对象中的多线程中,要理解多线程的知识点,首先要掌握什么是进程,什么是线程?为什么有多线程呢?多线程存在的意义有什么什么呢?线程的创建方式又有哪些?以及 ...
- Java中使用多线程、curl及代理IP模拟post提交和get访问
Java中使用多线程.curl及代理IP模拟post提交和get访问 菜鸟,多线程好玩就写着玩,大神可以路过指教,小弟在这受教,谢谢! 更多分享请关注微信公众号:lvxing1788 ~~~~~~ 分 ...
- Java的并发编程中的多线程问题到底是怎么回事儿?
在我之前的一篇<再有人问你Java内存模型是什么,就把这篇文章发给他.>文章中,介绍了Java内存模型,通过这篇文章,大家应该都知道了Java内存模型的概念以及作用,这篇文章中谈到,在Ja ...
- 【转】Java中的多线程学习大总结
多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程 ...
- Java中的 多线程编程
Java 中的多线程编程 一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序 ...
- java中的多线程 // 基础
java 中的多线程 简介 进程 : 指正在运行的程序,并具有一定的独立能力,即 当硬盘中的程序进入到内存中运行时,就变成了一个进程 线程 : 是进程中的一个执行单元,负责当前程序的执行.线程就是CP ...
- Java中的多线程=你只要看这一篇就够了
如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...
随机推荐
- oracle 使用escape转义%与_匹配字符为本来含义
举例: 查找姓名为M%的员工. select * from employee where staff_name like 'M\%' escape '\';
- Codeforces 750E 线段树DP
题意:给你一个字符串,有两种操作:1:把某个位置的字符改变.2:询问l到r的子串最少需要删除多少个字符,使得这个子串含有2017子序列,并且没有2016子序列? 思路:线段树上DP,我们设状态0, 1 ...
- 利用docker制作一个带有redis软件的镜像,供其他人使用
1. 宿主机在etc/apt/下创建一个haha的文件夹 2.宿主机将haha文件夹映射到容器的虚拟系统中etc/apt/ 3. 此时,可以在宿主机和容器虚拟机中同步创建和删除文件 4. 将宿主机中的 ...
- macOS gcc g++ c++ cc
安装完Xcode之后,系统中默认的编译器不再是Gcc系列,编译一些库的时候经常产生问题. 在PATH变量中设置symbol link,把gcc,g++,c++,cc全链接到Gcc系列.
- CentOS下安装Chrome浏览器
1. 下载安装脚本, 在下载目录中,执行以下命令,将安装脚本下载到本地 wget https://intoli.com/install-google-chrome.sh 2.然后授予可执行权限 chm ...
- 基于MaxCompute InformationSchema进行冷门表热门表访问分析
一.需求场景分析 在实际的数据平台运营管理过程中,数据表的规模往往随着更多业务数据的接入以及数据应用的建设而逐渐增长到非常大的规模,数据管理人员往往希望能够利用元数据的分析来更好地掌握不同数据表的使用 ...
- 记录一下LEETheme库主题切换的使用
LEETheme库的位置:https://github.com/lixiang1994/LEETheme 从https://github.com/gsdios的SDAutolayoutDemo 中的白 ...
- kafka消费组、消费者
consumer group consumer instance 一个消费组可能有一个或者多个消费者.同一个消费组可以订阅一个或者多个主题.主题的某一个分区只能被消费组的某一个消费者消费.那么分区和消 ...
- PHPcms编辑器如何粘贴带格式的word文档
在之前在工作中遇到在富文本编辑器中粘贴图片不能展示的问题,于是各种网上扒拉,终于找到解决方案,在这里感谢一下知乎中众大神以及TheViper. 通过知乎提供的思路找到粘贴的原理,通过TheViper找 ...
- [luogu]P1315 观光公交[贪心]
[luogu]P1315 [NOIP2011]观光公交 ——!x^n+y^n=z^n 题目描述 风景迷人的小城Y 市,拥有n 个美丽的景点.由于慕名而来的游客越来越多,Y 市特意安排了一辆观光公交车, ...