09、多线程(一) -- 基本概念
1.1、多线程基本使用
1、线程的创建方式
多线程的创建有两种方式,分别如下:
继承
- 继承Thread类,并重写run方法,将需要多线程的代码放入run方法中。
- 通过Thread的子类的引用调用start()方法来开启线程。
实现
- 定义类实现Runnable接口,覆盖Runnable接口中的run方法。
- 通过Thread类建立线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
- 调用Thread类的star方法开启线程并调用Runnable接口子类的run方法。
继承的方式:
public class ThreadDemo {public static void main(String[] args) {new MyThread().start();} }class MyThread extends Thread{@Overridepublic void run() {// 多线程代码System.out.println("多线程开启了");} }
实现的方式:
public class ThreadDemo {public static void main(String[] args) {// 第一种写法 /* MyThread t1 = new MyThread();Thread thread = new Thread(t1);thread.start();*/// 开发中写法new Thread(){@Overridepublic void run() {System.out.println("线程开启了");}}.start();} }class MyThread implements Runnable{public void run() {System.out.println("线程开启了");} }
线程都有自己默认的名称,通过getName和setName来进行获取和设置。同时,还可以通过Thread.currentThread()来获取当前线程对象。
2、 线程的运行状态
被创建-------------------->运行-------------------------->消亡(stop(已过时)或run方法结束)
|
阻塞-->(阻塞状态:具备运行资格,但不具备cpu执行权。)
|
冻结(sleep和wait):不具备运行资格,也不具备cpu执行权。
睡眠状态和等待状态:不具备运行资格,也没有执行权。
- 睡眠状态:sleep(time),当线程遇到sleep会进入睡眠状态,当睡眠时间到达后可能是运行状态,也可能进入临时状态(阻塞状态)。
- 等待状态:wait(),当线程遇到wait会进入等待状态,这时需要notify()来唤醒,唤醒后可能是运行状态,也可能是临时状态(阻塞状态)。
消亡的两种方式:通过stop()命令强行结束线程或run()方法执行结束。
3、 多线程售票实例
火车站有100张票,分别在t1、t2、t3、t4等4个窗口进行出售。
由此首先我们考虑到的是将100张票定义为静态共享变量,并开启四个线程来出售,那么代码如下:
public class ThreadDemo {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket);// 售票窗1Thread t2 = new Thread(ticket);// 售票窗2Thread t3 = new Thread(ticket);// 售票窗3Thread t4 = new Thread(ticket);// 售票窗4 t1.start();t2.start();t3.start();t4.start();} }// 售票厅 class Ticket extends Thread {private static int ticks = 100;// 出售票的方法public void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {if (ticks > 0) {sale();}}} }
输出结果:
Thread-0sale:3Thread-0sale:2Thread-0sale:1Thread-2sale:93Thread-1sale:89Thread-3sale:91
从结果中我们可以看出,售卖过程中我们发现出售的顺序已经错乱,这是因为线程太快导致,我们可以让线程在输出时进行睡眠20毫秒:
// 售票厅 class Ticket extends Thread{private static int ticks = 100;// 出售票的方法public void sale(){System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while(true){if(ticks > 0){try{Thread.sleep(20);}catch(Exception e){}sale();} }} }
输出结果:
Thread-2sale:77 Thread-3sale:77 Thread-0sale:77 Thread-1sale:77
从结果中,我们看到出现售卖相同的票,那么线程的安全隐患就暴露出来了,那么我们如何解决这个问题呢?
我们只能通过线程同步来解决该问题,来保住每次被并发执行的代码中只能有一个线程在操作,以此解决线程的安全问题。
4、 多线程同步
由上面代码的示例我们知道,直接开启四个线程进行售卖是存在安全隐患的,我们必须通过同步来解决线程安全问题。
同步的前提:
- 必须要有两个或者两个以上的线程。
- 必须是多个线程使用同一个锁。必须保证同步中只能有一个线程在运行。
线程的同步解决了多线程的安全问题,但是多个线程都需要判断锁,较为耗费资源。
a) 如果需要同步的代码只是部分,则可以使用同步代码块,同步代码块的锁对象可以是任意的对象:
// 售票厅 class Ticket extends Thread {private static int ticks = 100;Object obj = new Object();// 出售票的方法public void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {synchronized(obj){if (ticks > 0) {try{Thread.sleep(20);}catch(Exception e){}sale();}}}} }
输出结果:
Thread-1sale:100 Thread-1sale:99 Thread-1sale:98 ... Thread-2sale:3 Thread-2sale:2 Thread-2sale:1
b) 函数需要被对象调用。那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this。
// 售票厅 class Ticket extends Thread {private static int ticks = 100;Object obj = new Object();// 出售票的方法public void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {synSal();}}// 同步函数public synchronized void synSal(){if (ticks > 0) {try{Thread.sleep(20);}catch(Exception e){}sale();}} }
输出结果:
Thread-1sale:100 Thread-1sale:99 Thread-1sale:98 ... Thread-2sale:3 Thread-2sale:2 Thread-2sale:1
c) 当同步函数被static(静态)所修饰的时候,使用的锁是所在类的字节码文件(类名.class)。
// 售票厅 class Ticket extends Thread {private static int ticks = 100;Object obj = new Object();// 出售票的方法public static void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {synSal();}}// 同步函数public static synchronized void synSal(){if (ticks > 0) {try{Thread.sleep(20);}catch(Exception e){}sale();}} }
输出结果:
Thread-1sale:100 Thread-1sale:99 Thread-1sale:98 ... Thread-2sale:3 Thread-2sale:2 Thread-2sale:1
d) 多线程--死锁,同步中嵌套同步,而锁却不同就会造成死锁,从而导致程序无法继续向下运行。
class DeadLock implements Runnable{//定义一个标记private boolean flag;DeadLock(boolean flag){this.flag = flag;}//重写run方法public void run(){if(flag){synchronized(MyLock.locka){synchronized(MyLock.lockb){System.out.println("if locka");}}}else{synchronized(MyLock.lockb){synchronized(MyLock.locka){System.out.println("else lockb");}}}} } //定义两个锁 class MyLock{static Object locka = new Object();static Object lockb = new Object(); } public class DeadLockDemo {public static void main(String[] args) {new Thread(new DeadLock(true)).start();new Thread(new DeadLock(false)).start();} }
5、多线程之间的通信
a) 等待唤醒机制
- notify():激活线程池中 wait的线程。(谁先进去就唤醒谁)
- notifyAll():激活线程池中所有wait的线程。
- notify,notifyAll和wait等方法必须用在同步中,也就是说同步是前提。
- 而且这几种方法都是继承自Object类,出现异常,只能try而不能抛。
- 等待和唤醒必须是同一个锁,锁可以是任意对象,所以方法定义在Object类中。
- 都使用在同步中,因为要持有监视器(锁)的线程操作。
- 所以要使用在同步中,因为只有同步才具有锁。
b) 生产者和消费者实例
我们来看一个生产者和消费者的实例,在生产商品的同时将商品销售或消费掉。
class ProducerConsumerDemo {public static void main(String[] args) {Resource res = new Resource(); Producer pro = new Producer(res);Consumer con = new Consumer(res);Thread t1 = new Thread(pro);Thread t2 = new Thread(pro);Thread t3 = new Thread(con);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();} }class Resource {private String name;private int count = 1;private boolean flag = false;public synchronized void set(String name) {while (flag)try {this.wait();} catch (Exception e) {}this.name = name + "--" + count++;System.out.println(Thread.currentThread().getName() + "...生产者.." + this.name);flag = true;this.notifyAll();}public synchronized void out() {while (!flag)try {wait();} catch (Exception e) {}System.out.println(Thread.currentThread().getName() + "...消费者........." + this.name);flag = false;this.notifyAll();} } class Producer implements Runnable {private Resource res;Producer(Resource res) {this.res = res;}public void run() {while (true) {res.set("+商品+");}} } class Consumer implements Runnable {private Resource res;Consumer(Resource res) {this.res = res;}public void run() {while (true) {res.out();}} }
为什么要定义while判断标记?
对于多个生产者和消费者需要用while判断标记,来让被唤醒的线程再一次判断标记。
为什么要定义notifyAll()?
因为在本线程进入冻结或睡眠状态时,需要唤醒对方线程,如果用notify(),容易出现只唤醒本方线程的情况,导致程序中所有线程都进入等待状态。
我们可以看下简单的总结:
一个生产线程对应一个消费线程,我们可以用if(flag)来判断标记,唤醒必须用notify()。
if(flag) --> notify()
多个生产线程对应多个消费线程,我们必须用while(flag)来判断标记,唤醒必须用notifyAll()。(while循环下唤醒是notify()的话会导致全部等待)
while(flag) --> notifyAll();
死锁和全部等待:死锁是争抢执行权而导致程序停止,全部等待是所有线程都进入冻结状态。
针对唤醒本方的同时也唤醒对方的问题,Jdk1.5对该问题进行了针对性的处理,请参考下面的新特性。
转载于:https://www.cnblogs.com/pengjingya/p/5529078.html
09、多线程(一) -- 基本概念相关推荐
- 【java多线程学习】多线程的基本概念
今天开始系统的学习了java多线程有关的基础知识,大致先分为三个步骤:多线程的基本概念,多线程的两种使用方法(继承Thread类.实现Runable接口),线程的同步.这里先记录下下多线程的基本概念. ...
- 尚硅谷大数据技术Spark教程-笔记09【SparkStreaming(概念、入门、DStream入门、案例实操、总结)】
尚硅谷大数据技术-教程-学习路线-笔记汇总表[课程资料下载] 视频地址:尚硅谷大数据Spark教程从入门到精通_哔哩哔哩_bilibili 尚硅谷大数据技术Spark教程-笔记01[SparkCore ...
- Java多线程——多线程的基本概念和使用
一.进程和线程的基础知识 1.进程和线程的概念 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) 线程:进程中的一段代码,一个进程中可以有多段代码.本身不拥有资源(共享所在进程的资源) 在 ...
- java 线程的基本概念_Java多线程——多线程的基本概念和使用
一.进程和线程的基础知识 1.进程和线程的概念 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) 线程:进程中的一段代码,一个进程中可以有多段代码.本身不拥有资源(共享所在进程的资源) 在 ...
- Java多线程上——基本概念及操作
目录 多线程定义: 多线程编程 创建线程方法 Thread 类及常见方法 启动问题(start() 与 run()) 区别 中断线程 等待一个线程-join() 线程的状态 线程安全 线程安全定义 线 ...
- iOS开发 - 多线程相关的概念
1.进程 概念 在系统中正在运行的程序 特点 进程之间相互独立,每个进程运行在自己的内存空间内 实例 同时打开QQ.迅雷,系统会启动两个不同的进程 2.线程 概念 线城是进程的基本执行单元,即进程想要 ...
- Java多线程-线程的概念和创建
前言 声明:该文章中所有测试都是在JDK1.8的环境下. 该文章是我在学习Java中的多线程这方面知识时,做的一些总结和记录. 如果有不正确的地方请大家多多包涵并作出指点,谢谢! 一.基础概念 我们知 ...
- Java Web 实战 09 - 多线程基础之定时器
多线程当中的定时器 定时器 1. 官方的定时器 2. 自己实现的计时器 2.1 使用优先级阻塞队列 2.2 定义比较规则 2.3 解决 CPU 空转 : wait 2.4 防止其他线程插入到入队列和线 ...
- 多线程(线程概念、代码示例)
进程和线程 说起进程,就必须提一下程序,程序是指令和数据的有序集合,是一个静态的概念 进程是执行程序的一次执行过程,第一个动态的概念,是系统资源分配的单位 在一个进程中包含若干个线程,线程是独立的执行 ...
- [转载] 羽毛球——学打羽毛球 09 步法的基本概念
转载于:https://www.cnblogs.com/6DAN_HUST/archive/2012/09/23/2698807.html
最新文章
- Linux下Boost编译安装
- mysql调试索引_10 分钟让你明白 MySQL 是如何利用索引的?
- 使用Docker迁移与备份
- 诊断SQLSERVER问题常用的日志
- 普通高中段计算机学科知识,高中计算机学科的特点及教学内容浅探
- C语言-结构体内存对齐
- 23种设计模式之门面模式
- request.getRequestDispatcher().forward(request,response)和response.sendRedirect()的区别
- Free Syslog Forwarder–免费的Syslog转发工具
- greenplum定期清理日志脚本-分割线后更新简单方法
- Redis commands 官方
- Git可视化工具-小乌龟
- 火山图——直观的特征差异可视化
- 2022年计算机二级考试WPS Office高级应用与设计考前冲刺题及答案
- 机器人搭建记录 HoshinoBot
- 服务器电话销售话术,电话销售必看:让客户无法拒绝的13个经典话术
- ThinkPHP拼团拼购h5单商户商城[可对接公众号]非常棒的一款h5拼团商城源码
- vim中进行复制粘贴
- 互联网项目团队成员及能力组成的金字塔和倒金字塔模型
- MATLAB教室人数统计开源代码(包含 GUI 注释 课题分析)
热门文章
- scrapy报错:ModuleNotFoundError
- nginx php 慢,Nginx+PHP-FPM时快时慢的解决
- vue.js2.0 新手开发_vue.js2.0实战(1):搭建开发环境及构建项目
- npm 编译慢_如何有效提升快应用(Webpack)编译速度
- refprop用matlab,Matlab 调用 REFPROP(64位)下载即可用
- linux用户个人的环境变量,linux下的变量以及系统和个人环境变量的配置文件
- 贴花纸怎么贴_地砖保护膜怎么贴—怎么贴地砖保护膜
- 13、Math类简介
- swagger-ui多端口自动切换优化
- Virtualbox中Ubuntu与windows共享文件夹设置