JAVA_SE_笔记整理(多线程)

多线程

1、多线程概述

进程:正在运行的程序,是系统进行资源分配和调用的独立单位;每一个进程都有它自己的内存空间和系统资源。

线程:是进程中的单个顺序控制流,是一条执行路径;一个进程如果只有一条执行路径,则称为单线程程序;一个进程如果有多条执行路径,则称为多线程程序。

线程是依赖于进程存在的。

多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。而多线程却给了我们一个错觉:让我们认为多个线程是并发执行的。其实不是。

大家注意两个词汇的区别:并行和并发。

前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。

后者是物理上同时发生,指在某一个时间点同时运行多个程序。

那么,我们能不能实现真正意义上的并发呢,是可以的,多个CPU就可以实现,不过你得知道如何调度和控制它们。

2java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

jvm虚拟机的启动是单线程的还是多线程的?

JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

3、多线程实现方式一

线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

构造方法:选用无参构造

成员方法:

run() // 多线程执行内容

start() // 启动多线程,由虚拟机调用run()方法

为什么重写run方法:

因为不是类中所有代码都需要被线程执行。所有Thread 类中run方法中的内容是需要被多线程执行的。

线程能不能多次启动:

多次启动会出现:IllegalThreadStateException - 如果线程已经启动。

run()方法单独调用为什么是单线程的:

因为run()方法直接调用其实就相当于普通的方法调用,所以就是单线程的

run()和start()方法的区别:

run(): 仅仅是封装被线程执行的方法,直接调用是普通方法

start(): 首先启动了线程,然后在由JVM去调用该线程的run()方法

成员方法:

public final String getName() // 获取线程名称

public final void setName(String name) // 设置线程名称

通过构造方法,设置线程名称

public static Thread currentThread() // 获取当前线程的引用

4、线程调度

假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

分时调度模型   所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

 

Java使用的是抢占式调度模型

演示如何设置和获取线程优先级

public final int getPriority()

public final void setPriority(int newPriority)

优先级设置1-10值越小,优先级越小

Thread.MAX_PRIORITY

Thread.NORM_PRIORITY
Thread.MIN_PRIORITY

5线程控制

我们已经知道了线程的调度,接下来我们就可以使用如下方法对象线程进行控制

成员方法:

线程休眠:public static void sleep(long millis)

线程加入,等待该线程终止:public final void join(),谁等待,当前线程!

线程礼让:public static void yield()

后台线程 守护线程:public final void setDaemon(booleanon)

守护线程:如果创建的线程没有显示调用此方法,这默认为用户线程。

线程划分为用户线程和后台(daemon)进程,setDaemon将线程设置为后台进程

setDaemon

需要在start方法调用之前使用

如果jvm中都是后台进程,当前jvm将exit。(随之而来的,所有的一切烟消云散,包括后台线程啦)

主线程结束后,用户线程将会继续运行;如果没有用户线程,都是后台进程的话,那么jvm结束

http://blog.csdn.net/m13666368773/article/details/7245570

中断线程:

public final void stop()

让线程停止,过时了,但是还可以使用。写程序运行十秒后停止,如果不停止强制停止

public void interrupt()

让线程停止,过时了,但是还可以使用。写程序运行十秒后停止,如果不停止强制停止

6、线程生命周期

 

新建 -- 就绪 -- 运行 -- 死亡

新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡

7、多线程实现方式

1、用类实现Runnable接口

2、将实现类的对象传入Thread的构造方法

实现接口方式的好处:

可以避免由于Java单继承带来的局限性。

适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

8、练习电影院问题

1、继承实现

某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

继承Thread类

继承Runnable类

出现负票原因:随机性和延迟导致的。线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

2、解决线程安全问题的基本思想:

问题出现原因:

是否是多线程环境

是否有共享数据

是否有多条语句操作共享数据

如何解决多线程安全问题呢?

基本思想:让程序的环境没有安全问题

怎么实现呢?

把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

同步代码块:

synchronized(对象){需要同步的代码;}

同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

访问同一个资源,多个代码使用同一把锁,有效。多个程序使用多把锁,锁不住。

锁方法的锁对象是当前对象(this)

静态锁方法的锁对象是该当前类的class对象

多线程2

面试题:

1:多线程有几种实现方案,分别是哪几种?

两种。

 

继承Thread

实现Runnable接口

 

2:启动一个线程是run()还是start()?它们的区别?

start();

 

run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用

start():启动线程,并由JVM自动调用run()方法

 

3:sleep()wait()方法的区别

sleep():必须指时间;不释放锁。

wait():可以不指定时间,也可以指定时间;释放锁。

http://www.cnblogs.com/hongten/p/hongten_java_sleep_wait.html

 

4:为什么wait(),notify(),notifyAll()等方法都定义在Object类中

因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。

Object代码任意的对象,所以,定义在这里面。

 

5:线程的生命周期图

新建 -- 就绪 -- 运行 -- 死亡

新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡

课件:

多线程引入

把备注部分的代码通过画图解释一下调用流程。这个程序只有一个执行流程,所以这样的程序就是单线程程序。

假如一个程序有多条执行流程,那么,该程序就是多线程程序。

接下来我们来看看到底什么是多线程

多线程概述

进程:

正在运行的程序,是系统进行资源分配和调用的独立单位。

每一个进程都有它自己的内存空间和系统资源。

线程:

是进程中的单个顺序控制流,是一条执行路径

一个进程如果只有一条执行路径,则称为单线程程序。

一个进程如果有多条执行路径,则称为多线程程序。

举例

扫雷游戏,迅雷下载等

1:要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。

2:那么,什么是进程呢?通过任务管理器我们就可以看到进程的存在。

给出一个概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

3:多进程有什么意义呢?

单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。比如:WindowsMacLinux等,能在同一个时间段内执行多个任务。

对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。

因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。

多进程的作用不是提高执行速度,而是提高CPU的使用率。

 

4:那么什么又是线程呢?

在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。线程是程序中单个顺序的控制流,是程序使用CPU的基本单位。

 

5:多线程有什么意义呢?

多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。

而多线程却给了我们一个错觉:让我们认为多个线程是并发执行的。其实不是。

因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。所以他们仍然是在抢CPU的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。

 

6:那么什么又是并发呢?

大家注意两个词汇的区别:并行和并发。

前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。

后者是物理上同时发生,指在某一个时间点同时运行多个程序。

那么,我们能不能实现真正意义上的并发呢,是可以的,多个CPU就可以实现,不过你得知道如何调度和控制它们。

7:那么,我们来举例说说什么是进程,什么是线程。

扫雷游戏,迅雷下载等。

 

 

Java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

思考:

jvm虚拟机的启动是单线程的还是多线程的?

1JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

多线程的实现方案1

 

通过查看API来学习多线程程序的实现

参考Thread

继承Thread

步骤及代码演示

几个小问题:

为什么要重写run()方法

启动线程使用的是那个方法

线程能不能多次启动

run()start()方法的区别

1、为什么要重写run方法,因为不是类中所有代码都需要被线程执行。所有Thread 类中run方法中的内容是需要被多线程执行的。

2、 run()方法为什么是单线程的呢?

    因为run()方法直接调用其实就相当于普通的方法调用,所以就是单线程的

 

3run():仅仅是封装被线程执行的方法,直接调用是普通方法

     strat():首先启动了线程,然后在由JVM去调用该线程的run()方法

 

如何获取和设置线程名称

Thread类的基本获取和设置方法

public final String getName()

public final void setName(String name)

其实通过构造方法也可以给线程起名字

思考:

如何获取main方法所在的线程名称呢?

public static Thread currentThread()

这样就可以获取任意方法所在的线程名称

线程调度:

假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

分时调度模型   所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

Java使用的是抢占式调度模型。

演示如何设置和获取线程优先级

public final int getPriority()

public final void setPriority(int newPriority)

线程控制:

我们已经知道了线程的调度,接下来我们就可以使用如下方法对象线程进行控制

线程休眠

public static void sleep(long millis)

线程加入,等待该线程终止

public final void join()

线程礼让

public static void yield()

后台线程 守护线程

public final void setDaemon(booleanon)

中断线程

public final void stop()

public void interrupt()

备注:

1sleep() 休眠单位毫秒

2join()线程加入A.join,在API中的解释是,堵塞当前线程B,直到A执行完毕并死掉,再执行B

3yield()礼让A.yieldA让出位置,给B执行,B执行结束A再执行。跟join意思正好相反!

4setDaemon(boolean) 守护线程、后台进程

如果创建的线程没有显示调用此方法,这默认为用户线程。

setDaemon需要在start方法调用之前使用

线程划分为用户线程和后台(daemon)进程,setDaemon将线程设置为后台进程

如果jvm中都是后台进程,当前jvmexit。(随之而来的,所有的一切烟消云散,包括后台线程啦)

主线程结束后,用户线程将会继续运行;如果没有用户线程,都是后台进程的话,那么jvm结束

http://blog.csdn.net/m13666368773/article/details/7245570

5stop()让线程停止,过时了,但是还可以使用。写程序运行十秒后停止,如果不停止强制停止

     interrupt()中断线程。 把线程的状态终止,并抛出一个InterruptedException

 

线程运行周期图:

 

 

备注:

    新建 -- 就绪 -- 运行 -- 死亡

新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡

多线程的实现方案2

 

实现Runnable接口

如何获取线程名称

如何给线程设置名称

实现接口方式的好处

可以避免由于Java单继承带来的局限性。

适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

 

线程练习:电影院买票

需求:

某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

继承Thread

备注:、1票数定义到方法内

2、票数定义到成员变量

3、票数定义到静态成员变量

关于电影院卖票程序的思考

我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟

改实现接口方式的卖票程序

每次卖票延迟100毫秒

改进后的电影院售票出现问题

 

问题

相同的票出现多次

CPU的一次操作必须是原子性的(这里的原子操作指那些内容?)

还出现了负数的票

随机性和延迟导致的

 

注意

线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

 

解决线程安全问题的基本思想

 

首先想为什么出现问题?(也是我们判断是否有问题的标准)

是否是多线程环境

是否有共享数据

是否有多条语句操作共享数据

如何解决多线程安全问题呢?

基本思想:让程序没有安全问题的环境。

怎么实现呢?

把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

 

解决线程安全问题实现

 

同步代码块

格式:

synchronized(对象){需要同步的代码;}

同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

同步代码块的对象可以是哪些呢?

 

备注:同步代码块的锁对象是谁呢?

任意对象。

同步方法的格式及锁对象问题?

把同步关键字加在方法上。

同步方法是谁呢?

 this

 C:静态方法及锁对象问题?

 静态方法的锁对象是谁呢?

 类的字节码文件对象。(反射会讲)

同步的特点

同步的前提

多个线程

多个线程使用的是同一个锁对象

同步的好处

同步的出现解决了多线程的安全问题。

同步的弊端

当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

 

 

 

死锁问题

同步弊端

效率低

如果出现了同步嵌套,就容易产生死锁问题

死锁问题及其代码

是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

车辆问题:

1、第一辆车南、北各位同一把锁。

将南加锁,走南,

将北加锁,走北

2、第二辆车

将北加锁,走北

将南加锁,走南,

线程间通信

针对同一个资源的操作有不同种类的线程

举例:包子,一边做,一边吃。

 

通过设置线程(生产者)和获取线程(消费者)针对同一个吃包子事件阐述

 

 

1、准备第一个类笼,里面用来存放包子,需要两个成员属性,包子名字,以及有没有包子

2、做包子类:

死循环(true// 往死了做

先判断有没有包子?

如果没有包子,加锁。做包子

3、吃包子类:

死循环(true// 往死了吃

先判断有没有包子?

如果有包子,加锁,吃包子。

4、测试类:

定义一个笼对象

一个做包子的,

一个吃包子的

同时启动,

一定时间后,同时停止

 

线程的状态转换图

原文地址:https://www.cnblogs.com/songliuzhan/p/12624147.html