------<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()方法。
方法二:实现Runnable接口
步骤实现Runnable接口的好处
3、在定义线程时,建议使用实现方式。
三、线程安全问题
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方法结束,自然线程也就结束了。
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()方法可以暂停当前线程,让其他线程执行。