黑马程序员——java基础---多线程

来源:互联网 发布:nba2016全明星数据 编辑:程序博客网 时间:2024/05/19 08:40

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流-----

一、多线程

1、概述

进程:是一个正在执行中的程序。每一个进程都有一个执行顺序。该顺序是一个执行路径或者称为一个控制单元。
线程:就是进程中一个负责程序执行的独立控制单元(执行路径),线程控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束
一个进程中至少要有一个线程

2、多线程的优点和缺点

好处:解决了多部分同时运行的问题,提高程序执行效率。
弊端:线程太多会导致效率的降低

3、jvm中的多线程 

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

其实更细节说明JVM多线程,JVM启动不止一个线程,还有负责垃圾回收机制的线程。

二、创建多线程的两种方法

方法一:继承Thread类

步骤

1、定义继承Thread类

2、复写Thread类中的run()方法,将自定义代码存储在run方法中,让线程运行。

3、调用线程中的start()方法,该方法一方面启动线程,一方面调用了run()方法。

注:1、如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
2、为什么要覆盖run()方法呢?Thread类用于描述线程。Thread类中的run方法,用于存储线程要运行的代码。
3、继承方式有一个弊端,如果该类已经继承了其他父类,则无法继承Thread类创建线程了,这样就有了第二种创建线程的方式。
代码示例:

运行结果为:
如图,执行是随机、交替执行的,每一次运行的结果都会不同

方法二:实现Runnable接口

步骤
1、定义类实现Runnable接口
2、覆盖Runnable接口中的run()方法,将线程要运行的代码存放在该run方法中。
3、通过Thread类建立线程对象
4、将Runnable接口的子类对象作为实际参数传递给Thread的构造函数。
5、调用Thread类的start()方法开启线程并调用Runnable接口子类的run()方法。
注:1、为什么要将Runnable接口的子类对象传递给给Tread的构造函数呢?
自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象
实现Runnable接口的好处
1、将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2、避免了java单继承的局限性。
3、在定义线程时,建议使用实现方式。
两种方式的区别
继承Thread:线程代码存放在Thread子类run()方法中。
实现Runnable:线程代码存在接口的子类的run()方法中。
代码实例:

运行结果为:

三、线程安全问题

1、产生的原因

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

即:

1、多个线程访问出现延迟。

2、线程随机性。

2、解决方法

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

在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)

方式一:同步代码块

synchronized(线程锁)

 {需要被同步的代码}

持有锁的线程可以在同步中执行相应的代码,没有持有锁的线程即使获取了cpu的执行权,也无法执行相应的同步代码。

方式二:同步函数

同步函数的格式是在函数上加上synchronized修饰符即可。

同步的前提

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

必须是多个线程使用同一个锁

同步的好处和弊端
好处:解决了线程安全问题
弊端:多个线程需要判断锁,较为消耗资源。

如何寻找多线程中的安全问题

1、明确哪些代码是多线程运行代码。

2、明确共享数据。

3、明确多线程运行代码中哪些语句是操作共享数据的。

同步函数的锁是:this

代码示例:



运行结果为:



静态函数的锁是:该类对应的字节码文件对象。应用格式:类名.Class

代码示例:



运行结果为:


3、线程状态

线程拥有如下六种运行状态

被创建:等待启动,调用start启动。

运行状态:具有执行资格和执行权。

临时状态(阻塞):有执行资格,但是没有执行权。

冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

消忙状态:stop()方法,或者run方法结束。

注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。

图解:


4、线程间通信——等待唤醒机制

概念:多个线程在操作同一个资源,但是操作的动作不同。

等待唤醒机制

a、涉及的方法:

wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。

notify():随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。

notifyAll():接触那些在该对象上调用wait方法的线程的阻塞状态。

wait() notify() notifyAll全都使用在同步中!因为要对持有监视器(锁)的线程进行操作。所以要使用在同步中,因为只有同步才有锁。

b、为什么这些方法要定义在Object类中呢?

因为这些方法在操作同步中线程时,都必须要标识他们所操作线程持有的锁。只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。

c、等待唤醒机制代码实例:

d、在JDK1.5以后,出现了新的lock接口,将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

该接口中的方法摘要如下:

lock():获取锁

lockInterruptibly():如果当前线程未被中断,则获取锁

newCondition():返回绑定到此 Lock 实例的新Condition 实例。

tryLock():仅在调用时锁为空闲状态才获取该锁。

tryLock(long time,TimeUnit unit):如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁

unlock():释放锁

小知识点:

1、wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

a,这些方法存在与同步中。

b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。

c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

2、wait(),sleep()有什么区别?

wait():释放cpu执行权,释放锁。

sleep():释放cpu执行权,不释放锁。

3、为甚么要定义notifyAll?

因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

代码示例:






运行结果为:


5、死锁

当同步中嵌套同步时,就有可能出现死锁现象。

代码示例 :




运行结果为:


从运行结果可以看到只运行一遍就已经锁死了。

6、停止线程

线程的停止有以下两个原因:

因为run方法正常退出而自然死亡。

因为一个没有捕获的异常终止了run方法而意外死亡。

特别是,可以调用线程中的stop方法杀死一个线程,该方法抛出ThreadDeath错误对象,由此杀死线程,但是,stop方法已经过时,不要在自己的代码中调用它。
那么,现在我们如何来停止线程呢?就是让run方法结束。
方案一:多线程运行通常是循环结构,只要控制了循环,就可以让run方法结束。如下列代码所示,我们在首先设置一个标记flag,在该线程执行一段时间之后,将标志设置为false,则该run方法结束,自然线程也就结束了。
方案二:在方案一中的解决方案可以解决一般情况下线程的停止,但是有一种特殊情况,即当线程处于冻结状态时,方法读取不到该标记,那么线程也就不会结束,此时,我们就需要使用Thread类中的innerrupt()方法对冻结状态进行清除,让线程恢复到运行状态。

7、守护线程(Daemon Thread)

守护线程和普通线程在写法上没什么区别,我们可以通过调用t.setDaemon(true)将线程转为守护线程。守护线程唯一的用途是为其它线程提供服务,当只剩下守护线程时,虚拟机就退出了,如果只剩下守护线程,那么就没有必要继续运行程序了。

8、什么时候用多线程

当某些代码需要同时被执行时,就用单独的线程进行封装。

9、扩展小知识

1、join方法

当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。

2、setPriority()方法用来设置优先级

MAX_PRIORITY 最高优先级10

MIN_PRIORITY   最低优先级1

NORM_PRIORITY 分配给线程的默认优先级

3、yield()方法可以暂停当前线程,让其他线程执行。

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流-----



0 0