黑马程序员------毕老师视频笔记第十一天------多线程(1)

来源:互联网 发布:冷情阁app网络错误 编辑:程序博客网 时间:2024/06/05 04:14

---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

一.概述

要学习多线程,先要知道线程的概念,而学习线程,要先学习进程的概念。

什么是进程呢?正在执行的程序

直观感受一下


 

宏观上,进程是同时执行的,但是微观上,CPU一个时刻只能执行一个程序,CPU在做着快速的切换。

而在一个进程中,会有多条执行路径,例如迅雷

 

进程:是一个正在执行中的程序

         每一个进程执行,都有一个执行的顺序,该顺序就是一个执行路径,或叫做执行情景,或者叫一个控制单元

线程:就是进程中真正执行的部分,就是进程中一个独立的控制单元

         线程在控制着进程的执行

 

一个进程中至少有一个线程

 

JVM启动的时候会有一个进程java.exe,该进程中至少会有一个线程在负责者java程序的执行,而且这个线程运行的代码位于main方法中。

该线程称之为主线程。

 

扩展:其实更细节的说明JVM,JVM启动不只有一个线程,还有负责垃圾回收机制的线程

 

二.创建线程------继承Thread类

进程是系统创建的,那进程中的线程也是系统创建的,java只要调用系统的相关功能就能创建线程。java把相关的事物封装,成为Thread类。

(一个类形成为异常,继承Exception;想成为线程,继承Thread)

通过对API的查找,java已经提供了对线程这类事物的描述,就是Thread类,创建线程的第一种方式,就是继承Thread类,然后覆盖run方法。

为什么覆盖run方法呢?

public class Thread extends Objectimplements Runnable{}

run方法时实现Runnable接口中的方法

run方法中的内容就是新建的线程要执行的内容

 

步骤:

1.继承Thread类

2.覆盖run方法

3.调用start方法启动线程


class Demo extends Thread{//run必须是public的,它原来是接口Runnable中定义的public void run(){for (int i=0; i<100; i++){System.out.print("Demo ");}}}class ThreadDemo{public static void main (String [] args){//注意,不需要调用d.run();Demo d = new Demo();d.start();for (int i=0; i<100; i++){System.out.print("main ");}}}

发现运行结果每次都不同

因为多个线程都获取了CPU的执行权,CPU执行到谁,谁就运行

明确一点,在某一时刻,只能有一个程序运行(多核除外)

CPU在做着快速切换,以达到看上去是同时运行的效果

我们可以形象的把多线程的运行行为看做是多个线程在抢夺CPU的执行权

 

这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,CPU说了算

(其实执行多长时间是可以控制的,但是过程还是比较麻烦的)

 

为什么要覆盖run方法呢?总结一下,就是

Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。

也就是说,Thread类中的run方法是用于存储线程要运行的代码

主线程要运行的代码是存储在main方法中,这是JVM定义的

在主函数中写 d.run() 会怎么样呢?创建了线程,定义了要运行的功能,但是不会开启线程,这样就是一般的调用对象的方法,会在主函数中顺序执行

 

d.start(); 开启线程并执行run中的功能

start调用的是底层,开启了一个线程

 

三.线程运行状态

看一张老图

 

Thread类肯定调用了底层,java不会自己创建线程的,windows上跑的时候是windows系统帮忙创建的线程。

 

冻结又叫睡眠或等待,线程还活着,但是不动了

sleep(time),时间到了之后,线程就醒了(回到临时状态),继续执行,sleep就放弃了执行资格

wait(),线程活着,但是不唤醒的话,线程一直睡着,wait也会放弃执行资格

notify(),叫醒线程,wait之后notify,他才会醒,唤醒之后线程在临时状态

 

stop(),停止线程,线程消亡,每种状态都可以用stop把线程弄死

不用stop线程会消亡吗?会,执行完他就消亡了。

 

临时状态,阻塞状态,就绪状态,他们是一个状态

start一个线程之后,他也许不会马上执行,这时就处在这种状态,具备了运行资格,但没有执行权。

下面是毕老师的一张图



 

四.获取线程对象及名称

Thread;类中有setName方法和getName方法

用getName就可以得到线程名

线程都有自己默认的名称

Thread-0           Thread-1           等

这些名称可阅性不强,我们可以自己起setName(String name)

线程有一个可以传名称的构造函数Thread(String name)

所以子类中我们可以直接用 Demo(String name){super(name);}

 

Thread类中还有一个函数

static Thread currentThread()                   返回当前正在执行的线程对象的引用。

我们可以调用它 Thread.currentThread()得到线程对象的引用

可以用this验证一下,他们是相同的

class Demo extends Thread{private String name;public void run(){System.out.println(getName());System.out.println(this.getName());System.out.println(Thread.currentThread().getName());System.out.println(this==Thread.currentThread());}Demo(String name){this.name = name;}Demo(){}}class ThreadDemo{public static void main (String [] args){Demo d1 = new Demo();Demo d2 = new Demo("second Thread");d1.start();d2.start();//这里没有创建ThreadDemo的对象,不能用this System.out.println(Thread.currentThread().getName());}}


 

五.创建线程的另一种方法

实现Runnable接口

步骤:

1.定义类实现Runnable接口

2.覆盖Runnable接口中的run方法

         将线程要运行的代码存放在该方法中

3.在main方法中通过Thread类建立线程对象

4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数

         为什么要将Runnable接口的子类对象传递给Thread类的构造函数呢?

         因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属的对象

5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法

 

实现方式和继承方式有什么区别呢?

实现方式的好处:避免了单继承的局限性,一个类本身有父类,怎么让他独立运行呢

在定义线程的时候建议使用实现方式

两种方式的区别;

继承Thread:线程代码存放在Thread子类run方法中

实现Runnable:线程代码存放在接口子类的run方法中

 

Runnable接口就是在确立run方法存在的位置。

 

六.多线程的安全问题

问题的原因:

         当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法:

         对多条操作共享数据的语句,只能允许一个线程都执行完,在执行过程中,其他线程不可以参与执行。

java对于多线程的安全问题提供了专业的解决方式

同步代码块

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

对象是有标志位的,两位,一位允许线程进,一位阻止线程进,“锁旗标”

对象如同锁,持有锁的线程可以在同步代码块中执行,没有持有锁的线程即使获取了CPU的执行权,也进不去。

例举,火车上的卫生间

 

同步的前提:

1.必须要有两个或两个以上的线程

2.必须是多个线程使用同一个锁,也就是同一个对象

 

必须保证同步中只能有一个线程运行

 

可以建一个Object对象,当做锁,不用另外定义类

 

好处:解决了多线程的安全问题

弊端:多个线程都需要判断锁,较为消耗资源

 

同步函数

用synchronized关键字修饰函数

只能有一个线程进入函数,执行函数中的代码

同步函数和同步代码块是一样的机制,都有一个锁

非静态同步函数的锁是this

静态同步函数的锁Class对象(典型示例:单例设计模式的懒汉式)

 

死锁

一般出现在同步嵌套的情况,两个锁,持锁的线程之间都不放锁

死锁可能被“和谐”

 

(示例见下篇blog)

 ---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------


0 0
原创粉丝点击