------- android培训、java培训、期待与您交流! ----------
一、多线程
1.1 多线程的概念
进程:是一个正在执行中的程序,每一个进程执行都有一个执行的顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程,就是进程中的一个控制单元。该线程在控制着进程的执行。多线程就是同时运行一个以上的线程。
java VM启动时会有一个进程java.exe,该进程至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称为主线程。其实更为细节说明,jvm,jvm启动不止一个线程,还有负责垃圾回收的线程。
1.2 多线程存在的意义
多线程的出现,可以提高程序运行的效率。
1.3 多线程的创建方式
1.3.1 创建线程的第一种方式
继承Thread类,覆盖run方法。
如下
这个程序中有两个线程,运行结果如下,由于输出语句过于多,只展示部分
可以看出两个线程进行了交互执行。
发现运行结果每一次都不相同,是因为多个线程都获取CPU执行权,CPU执行到谁,谁就运行。明确一点,在某一时刻,只能有一个程序在运行(1核的情况),CPU在做着快速的切换,以达到看上去是同时运行的效果。
可以形象的把多线程运行行为理解为互相抢夺CPU执行权。这就是一个多线程的特性,随机性。谁抢到执行权就谁执行,至于执行多长,由CPU决定。
具体创建步骤:
1、 定义类继承Thread。
2、 复写Thread类中的run方法。目的是将自定义的代码存储在run方法中,让线程运行。
3、 调用线程的start方法。该方法两个作用,启动线程,调用run方法。
为什么要覆盖run方法?
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,也就是说Thread类中的run方法,用于存储线程要运行的代码。
但是,直接调用run方法,会先运行完run方法中的执行语句再执行主线程,必须用start开启run方法才能达到多线程的效果。
1.4 线程的五种状态
1、 被创建:通过new得到的线程。
2、 运行状态:通过start()方法调用而运行的线程。
3、 阻塞状态:运行中,失去执行权,但是具有执行资格的状态。
4、 冻结状态:失去执行资格的状态。
5、 消亡:线程运行结束的状态。
1.5 获取线程名称的方法
Thread.currentThread():获取当前线程对象。
getName:获取线程的名称。
设置线程的名称:setName或者构造函数。
1.6 线程创建的第二种方式
实现Runnable接口 覆盖run方法
步骤:
1、 定义类实现Runnable接口
2、 覆盖Runnable接口中的run方法,将线程要运行的代码存放在run方法中。
3、 通过Thread类建立线程对象
4、 通过Runnable接口的子类对象作为实际参数传递给Thread类的构造函数,因为自定义的run方法所属对象是Runnable接口的子类对象,所以要让线程去执行制定对象的run方法,就必须明确该run方法所属的对象。
5、 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
实现方法和继承方式有什么区别?
实现方式的好处,避免了单继承的局限性,在定义线程时,建议使用实现方式。继承Thread,线程代码存放在Thread的子类方法中。实现Runnable,线程代码存放在接口的子类run方法中。
1.7 同步
问题的原因:当多条语句在操作同一个线程的共享数据时,一个线程对多条语句,只执行了一部分,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多个操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行,就是同步。
1.7.1 同步代码块
格式:
synchronized(对象)
{
需要被同步的代码;
}
对象如同锁,持有锁的线程可以在同步中执行,没有锁的线程即使获得CPU的执行权,也进不去,因为没有获取锁。
同步的前提:
1、 必须要有两个或者两个以上的线程。
2、 必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行就可以了。
好处:同步解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
1.7.2 同步函数
将同步作为修饰符放函数上。同步函数使用的锁是this。如果函数是静态,那么同步函数使用的锁是该类的字节码:类名.class。
如下,就是一个通过实现Runnable,使用同步代码块的例子。
运行结果:
可以看出,通过使用同步代码块,运行中没有出现0的情况。
1.8 懒汉式的解决方案
单例设计模式中的懒汉式,也称为延迟加载,在多线程运行时,会有安全隐患。解决方案如下:
通过使用同步代码块和双重判断的方式,可以解决懒汉式在多线程运行时的安全问题。
1.9 死锁
死锁产生是原因:
两个使用不同锁,确相互嵌套的同步,就容易出现死锁。如下代码,就会出现死锁:
运行结果:
由右上角是红色可以看出代码还在运行,只是现在却不打印了,是因为两个代码块的锁相互嵌套形成死锁,导致程序无法继续运行下去导致的。
二、线程间的通信
2.1 线程间的通信
其实就是多个线程在操作同一个资源,但是操作的动作缺不同。
2.2 等待唤醒机制
wait()
notify()
notifyAll()
都是用在同步中,因为要对持有监听器的线程操作,所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object中?因为这些方法在操作同步线程时,都必须要标识他们所操作线程的线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁的线程进行唤醒。
也就是说等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义在object中。
2.3 JDK5.0新特性
Lock:是java5.0出现的新工具,它提供了显示加锁和解锁的方法,并且可以用一个锁对应多个Condition。
如下,就是一个用Lock和Conditon方法的例子。
2.4 生产者和消费者
生产者消费者这个例子充分体系了多线程中同步的使用,其运行结果如下:
可以看出,是四个线程在交替运行。
2.5 停止线程
如何停止线程?
在多线程中,停止线程的方法只有一种,就是让run 方法结束,代码也通常是循环结束,只要控制住循环,就可以让run方法结束,也就是线程结束。
当线程处于冻结状态时,哪怕改变标记,线程也无法重冻结状态读取标记,这是就有一个特殊的方法,interrupt()。interrupt()若将冻结状态的线程强制唤醒,线程会发生异常InterruptException。
当没有指定的方式让冻结的线程恢复到运行状态时,这是就需要对冻结状态进行清除,强制让线程恢复到运行状态来,这样就可以操作标记让线程结束。即interrupt。
2.6 守护线程
setDaemo();
将线程设为守护线程。必须在线程启动前调用。
守护线程也是后台线程,倘若程序中没有前台线程运行,后台线程会自动结束。
2.7 join方法
join方法可以将线程加入运行,当执行到这个语句时,主线程会放弃执行权,让执行join的线程运行完,主线程才会继续运行。该语句在线程启动后调用。
2.8 优先级
优先级代表线程抢CPU的频率,其中优先级高的CPU执行的次数会更多。
线程中优先级有10级,其中1最低,10最高。
yield()方法:线程释放执行权,可以得到线程交互运行的效果。