黑马程序员_Java_多线程
8.多线程
8.1、多线程概述
- 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
- 线程(例:FlashGet):就是进程中一个独立的控制单元。线程在控制着进程的执行。一个进程中至少有一个线程。
- Java VM启动的时候,会有一个进程java.exe。该进程中至少一个线程负责Java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
- 扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
- 多线程存在的意义
- 一个采用了多线程技术的应用程序可以更好地利用系统资源。其主要优势在于充分利用了CPU的空闲时间片,可以用尽可能少的时间来对用户的要求做出响应,使得进程的整体运行效率得到较大提高,同时增强了应用程序的灵活性。更为重要的是,由于同一进程的所有线程是共享同一内存,所以不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。
8.2、创建线程一(继承Thread类)
- 线程的创建方式(如何在自定义的代码中,自定义一个线程呢?)
- 通过对api的查找,Java已经提供了对线程这类事物的描述,就是Thread类。
- 创建新执行线程有两种方法。
- 方法一:一种方法是将类声明为
Thread
的子类。该子类应重写Thread
类的run
方法。并调用start方法。//该方法两个作用:启动线程,调用run方法。建立好一个对象就是创建好一个线程。
- 方法一:一种方法是将类声明为
start:使该线程开始执行;Java 虚拟机调用该线程的 run
方法。
发现运行结果每一次都不同,因为多个线程都获取cpu的执行权。CPU执行到谁,谁就执行。明确一点,在某一时刻只能有一个程序在运行。(多核除外)
CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象地把多线程的运行行为在互相抢夺CPU的执行权。
为什么要覆盖run方法呢?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码,该储存功能就是run方法。(创建线程的目的就是为了让线程去执行一些代码,那么线程在描述过程中需要定义代码所存放的位置,这个存储空间就是run方法,也就是说Thread类中的run方法,用于存储线程要运行的代码。)
- 多线程的特性
多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,CPU说了算。
8.3、线程的运行状态
1、被创建
2、临时状态(阻塞):线程被start以后,不一定运行。当开启了多个线程时,CPU在某一时刻只能执行一个线程,在等待CPU的执行权。(具备执行资格,但没有执行权。)
3、运行(既有资格又有执行权):创建后通过调用start方法运行。
4、冻结(放弃了执行资格):一、在运行状态下通过sleep(time)方法进行冻结。当自定义的time过后,线程复活了,从冻结状态转为临时状态,即具备执行资格,当争取到执行权时便运行。
二、在运行状态下通过wait()方法将线程进行冻结,通过notify()方法将线程复活,,从冻结状态转为临时状态,即具备执行资格,当争取到执行权时便运行。
三、线程在冻结状态时进程并没有结束。
5、消亡:通过stop()方法将线程结束,run方法结束。
8.4、获取线程对象以及名称
1、线程都有自己默认的名称:Thread-编号 该编号从0开始。
2、调用getName() 方法获取。
3、调用setName()方法改变线程名称。但通常用父类的构造方法进行自定义名称。(即子类的构造方法调用父类已经定义好的构造方法,super(name);)
4、currentThread():返回对当前正在执行的线程对象的引用。
5、每一个线程的创建都有自己独立的运行空间。
8.5、创建线程二(实现Runnable接口)最为常用
通过售票的例子,继承Thread类,创建多个Thread类子类对象是不行的,因为ticket数据不共享。可以通过static进行共享,但一般不建议这么做,因为生命周期太长。接着创建一个对象进行多次start,出现了无效线程状态异常,只有Thread-0在跑。因为线程已经从创建状态到运行状态,再次开启start是没有意义的。所以,该怎么解决这个问题呢?第一种创建方式不行了。
方法二:创建线程的另一种方法是声明实现 Runnable
接口的类。该类然后实现 run
方法。然后可以分配该类的实例,在创建 Thread
时作为一个参数来传递并启动。(此时ticket被共享了)
步骤:
1、定义类实现Runnable接口。
2、覆盖Runnable接口中的run方法。(将线程要运行的代码存放在该run方法中)
3、通过Thread类建立线程对象。
4、将Runnable的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread类的构造函数呢?因为自定义的run方法所属的对象是Runnable接口的子类对象。所以要线程去执行制定对象的run方法。就必须明确该run方法所属的对象。
5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
实现方式和继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性。
在定义线程时,建议使用实现方式。
两种方式区别:
继续Thread:线程代码存放在Thread子类的run方法中。
实现Runnable:线程代码存在接口子类的run方法中。
8.6、多线程的安全问题
sleep(long millils):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
该方法抛出InterruptedException中断异常:如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。
通过分析发现,打印出0、-1、-2等错票。多线程的运行出现了安全问题。
问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
8.7、多线程同步代码块
Java对于多线程的安全问题提供了专业的解决方式。就是同步代码块。synchronized(对象){需要被同步的代码;}
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使过去CPU的执行权,也进不去,因为没有获取锁。(火车上的卫生间——经典。)
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。(通常把资源对象作为锁。)
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
8.8、多线程同步函数
如何在代码中找问题?
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
把synchronized作为修饰符放在函数上,即为同步函数。
不能把run方法随便的加同步,因为要哪些代码需要被同步,而哪些不需要。把需要同步的代码用函数封装起来,再调用此方法即可。
同步函数用的是哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
8.9、静态同步函数
如果同步函数被静态修饰后,使用的锁是什么呢?通过验证,发现不再是this,因为静态方法中不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是Class。
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。
8.10、多线程-单力设计模式-懒汉式
懒汉式的特点在于实例的延迟加载。当多线程访问时会出现安全问题。可以加同步来解决。用同步函数会比较低效,用同步代码块和双重判断的形式会更加高效,解决效率的问题。加同步时使用的锁是该类所属字节码文件对象。
8.11、多线程-死锁
死锁:同步中嵌套同步。
发生死锁的原因一般是两个对象的锁相互等待造成的。
那么为什么会产生死锁呢?
1.因为系统资源不足。
2.进程运行推进的顺序不合适。
3.资源分配不当。
学过操作系统的朋友都知道:产生死锁的条件有四个:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
8.12、多线程间的通信问题
思考 1、wait(),notify(),notifyAll()用来操作线程为什么定义在了Object中?
- 这些方法存在于同步中。
- 使用这些方法时必须要标识所属的同步得到锁。
- 锁可以是任意对象,所以任意对象锁调用的方法一定定义Object类中。
思考2、wait(),sleep()有什么区别呢?
- wait():释放资源,释放锁。
- sleep():释放资源,不释放锁。
线程间通信:其实就是多个线程操作同一个资源。但是操作的动作不同。
解决安全问题:
将run方法中操作同一资源的代码加上同步,并使用同一个锁。通常将资源对象作为锁。因为资源是唯一的。
等待唤醒机制:
使用原因:当输入线程跳出同步时,输入线程和输出线程都可能抢到CPU的执行权,所以输入线程有可能再次抢到执行权,再次进行输入信息,把前面的覆盖掉。当输出线程抢到执行权时也不可能只输出一个。这是CPU的切换造成的。
为了能输入一个就取出一个,在资源上加入一个标记(boolean flag )。当flag为false时,输入线程往资源里面存值,并将标记修改,反之,把值输出。但当输入线程再次抢到cup的执行权时,不能再存了。这时就让输入线程先等着,由于时间不确定,所以不能用sleep,这时就要用wait。所以当输出线程把值输出后,在输出线程冻结前将其他线程唤醒notify。同时注意要标识出wait、notify、notifyAll所属的锁。
生产者和消费者
在实际开发中,代码时由多个线程去运行的,多个线程负责生产多个线程负责消费。由于生产线程在生产前没有再次判断标记,所以会出现生产两个消费一个的情况。if只判断一次,while会判断多次,所以将if改为while。当生产线程被唤醒后,会再次判断标记,就不会出现再次生产的情况。但是线程全都锁死了,全都挂着(全都等待,非死锁)。原因是notify唤醒的时线程池中最早等待的那一个,不一定是对方线程。所以将notify改为notifyAll,将线程池中的线程全部唤醒。
注意:当出现了多个生产者和消费者时,必须要用while循环和notifyAll。而if和notify只能用于生产者一个消费者一个的情况。
笔记:对于多个生产者和消费者,为什么要定义while判断标记?
愿意:让被唤醒的线程再次判断标记。
为什么定义notifyAll?因为需要唤醒对方线程,因为只用notify,容易出现只唤醒对方线程的情况,导致程序中的所有线程都等待。
8.13、生产者和消费者jdk5.0升级版
我们希望只唤醒对方,不唤醒本方,该怎么做呢?Java提供了接口Lock。
Lock
实现提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition
对象。
以前的加锁和解锁都是隐式的。用Lock后,代码中就为显式的。
Condition
将 Object
监视器方法(wait
、notify
和 notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意 Lock
实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock
替代了 synchronized
方法和语句的使用,Condition
替代了 Object 监视器方法的使用。
- lock():加锁。
- unlock():解锁。
- await():替代了wait()。
- signal():替代了notify()、
- signalAll():替代了notifyAll()。
当线程拿到锁后,加锁的代码中可能导致程序跳转,下面的解锁代码执行不到,但是解锁的代码是必须执行的,所以用finally()。
Lock支持多个相关的Condition对象,意思是锁里面能创建多个Condition对象,Condition_con.wait()只能被Condition_con.signal或Condition_con.signalAll唤醒。这就能起到只唤醒对方的作用。指定唤醒等待中的线程。
JDK1.5中提供了多线程升级解决方案。将同步Synchronnized替换成现实Lock操作。将Object的wait,norify,notifyAll,替换了Condition对象。该对象可以通过Lock锁的newCondition()方法进行获取。在该实例中实现了只唤醒对方的操作。
8.14、停止线程
1、定义循环结束标记。因为线程运行代码一般都是循环,只要控制了循环即可。
2、使用interrupt(中断)方法。该方法是结束线程的冻结状态,使线程回到运行状态上来。
注意:stop方法已经过时不在使用。如何停止线程?只有一种方法就是,run方法结束。
开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊状态:当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程回复到运行状态是时,这时需要对冻结进行清除。强制让线程回复到运行状态上来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();
8.15、join方法
join():等待该线程终止。意思是调用该方法的线程在抢夺CPU的执行权。当A线程执行到B线程的join方法时,A就会等待,等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。
8.16、优先级&yield方法
线程的优先级一共是有10级,默认是5。
setPriority():更改线程的优先级。
MAX_PRIORITY:线程可以具有的最高优先级。(10)
MIM_PRIORITY:线程可以具有的最低优先级。(5)
NORM_PRIORITY:分配给线程的默认优先级。(1)
yield():暂停当前正在执行的线程对象,并执行其他线程。强制放弃执行权。
黑马程序员_Java_多线程的更多相关文章
- 黑马程序员_java08_多线程
转载于:http://www.itxuexiwang.com/plus/view.php?aid=148 线程是程序中可以并行执行的任务. java运行系统总是选当前优先级最高的处于就绪状态的线程来执 ...
- 黑马程序员:多线程Socket
---------------------- ASP.Net+Android+IOS开发..Net培训.期待与您交流! ----------------------- 一.Socket一般应用模式(服 ...
- 黑马程序员:Java基础总结----网络编程
黑马程序员:Java基础总结 网络编程 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 网络编程 网络通讯要素 . IP地址 . 网络中设备的标识 . 不易记忆,可用 ...
- 黑马程序员:轻松精通Java学习路线连载1-基础篇!
编程语言Java,已经21岁了.从1995年诞生以来,就一直活跃于企业中,名企应用天猫,百度,知乎......都是Java语言编写,就连现在使用广泛的XMind也是Java编写的.Java应用的广泛已 ...
- 【黑马18期Java毕业生】黑马程序员Java全套资料+视频+工具
Java学习路线图引言: 黑马程序员:深知广大爱好Java的人学习是多么困难,没视频没资源,上网花钱还老被骗. 为此我们历时一个月整理这套Java学习路线图,不管你是不懂电脑的小 ...
- 黑马程序员面试宝典(Java)Beta6.0免费下载
场景 JavaSE基础 面向对象特征以及理解 访问权限修饰符区别 理解clone对象 JavaSE语法 java有没有goto语句 &和&&的区别 如何跳出当前的多重嵌套循环? ...
- 黑马程序员——【Java高新技术】——代理
---------- android培训.java培训.期待与您交流! ---------- 一.“代理概述”及“AOP概念” (一)代理概述 1.问题:要为已存在的多个具有相同接口的目标类的各个方法 ...
- 黑马程序员+SQL基础(上)
黑马程序员+SQL基础 ---------------<a href="http://edu.csdn.net"target="blank">ASP ...
- 黑马程序员+Winform基础(上)
黑马程序员+Winform基础 ---------------<a href="http://edu.csdn.net"target="blank"> ...
随机推荐
- java泛型编程
一般的类和方法都是针对特定数据类型的,当写一个对多种数据类型都适用的类和方法时就需要使用泛型编程,java的泛型编程类似于C++中的模板,即一种参数化类型的编程方法,具体地说就是将和数据类型相关的信息 ...
- javascript 中 nodeValue 、value 、text 的区别
nodeValue: 属性设置或者返回某节点的值: 也可以改变某个文本节点的值, node.nodeValue eg: 如何获取p元素里面的文本内容 <p id="demo" ...
- Java里的接口
Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现. Java接口和Java抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现.OOP面向对象的编程,如 ...
- ACdream 1417 Numbers
pid=1417">题目链接~~> 做题感悟:比赛的时候用的广搜,然后高高兴兴的写完果断TLE .做题的时候不管什么题都要用笔画一下,模拟几组数据,这样或许就AC了(做题经验,有 ...
- opencv和javacv版本不一致
Exception in thread "main" java.lang.UnsatisfiedLinkError: no jniopencv_highgui in java.li ...
- linux修改主机名-IP
1.查看当前主机名 hostname 2. ifconfig 显示所有网络接口的信息 ifconfig eth0 显示网卡eth0的信息 3.临时修改主机名 hostname rusky. ...
- redisi配置安装
一.单机配置 http://www.codeceo.com/article/centos-redis-setup.html 二.测试安装情况 http://blog.sina.com.cn/s/blo ...
- box-shadow全面解析
一.box-shadow语法: box-shadow: none | inset(可选值,不设置,为外投影,设置,为内投影) x-offset(阴影水平偏移量,正方向为right) y-offset( ...
- pd的django个人博客教程----1:效果展示等
开发环境同to do list 1:首页:localhost/pd/ 2:导航栏测试或者生活点入: 测试:localhost/category/?cid=1 3:点击文章后进入文章显示页面 e.g:进 ...
- (转)介绍几个C#正则表达式工具
推荐三个C#正则表达式工具,理由如下 第一个C#正则表达式工具,REGEX 这个C#正则表达式工具优点是中文的,提供了一些示例 第二个C#正则表达式工具,REGEXBUDDY 这是一个真正专业的REG ...