多线程--7
Runnable接口的来历
Thread类和Runnable接口的关系
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
1. Runnable接口的来历
1). 直接继承Thread子类创建多线程存在的缺点
(1). Thread类或者其子类中静态成员变量的缺点
[1]. 缺点1 ----生命周期过长
{1}.类的静态变量缺点就是静态变量的生命周期和这个类的Class对象的生命周期一样长
{2}. 当多线程需要共享的数据是非常大或者数量非常多的时候,如果这样的共享数据采用线程类的静态成员变量的话,很可能出现当所有的线程实例都运行完成之后,对应的共享数据还长时间占用内存,消耗资源。
[2]. 缺点2 ----是所有类的实例的共享数据
很可能有这样的需求:几个线程之间操作不同的共享数据。此时如果这些不同的共享数据都以静态变量的形式存在于线程类或者其子类中,这样对所有的线程类对象都是可见的。本来应该对某些线程隐藏的数据却可以直接通过类进行访问,数据不安全。
(2). 改进Thread类及其子类的目标
把线程类或者其子类共享的静态数据转移到其他的类中的非静态数据。这样既缩短生命周期又达到不同的多线程之间的共享不同的数据。
2). 提取共享数据到自定子类 -----改进
(1). 改进Thread类或者子类设计思路
[1]. 抽取Thread子类中的共享数据
Thread类或者其子类中的静态共享数据有两种用途
{1}. 供多线程实例之间进行操作
{2}. 用作多线程实例之间的同步代码块的锁对象
[2]. 自定义用于存储原Thread类或者其子类中的静态共享数据的类
将Thread类或者子类的这些静态共享数据抽取到一个新的自定义类,同时去掉static的修饰,以非静态成员变量的形式存在于自定义的类中。
[3]. 为Thread类或者子类增加一个构造函数+一个非静态成员变量
{1}. 增加以自定义类为类型的非静态成员变量
{1}1. 用一个非静态自定义类的成员变量存贮原来Thread类或者其子类中的多个共享数据
{1}2. 原有的多个静态共享数据被封装成了一个新的自定义类的类对象成员
{2}. 增加以自定义类类型为参数的重载构造函数
在这个构造函数中通过外面传来的参数初始化{1}中为Thread类或者其子类中增加的相应类型的非静态成员变量。
{3}. 如何实现自定义类的实例对象的共享?
{3}1. 在主线程中实例化一个自定义类的共享数据
{3}2. 通过{2}中为Thread或者Thread子类中增加的相应的构造方法传入{3}1中定义的共享数据。哪些线程需要这个共享数据,就向哪几个线程实例的构造方法传参。这样该类型的一个数据可以传给多个不同的线程实例,达到共享的目的
{4}. 数据共享的传递性
自定义类的对象以共享数据的身份被相应的多线程对象进行操作,那么这个自定义对象自身的成员属性也属于多线程对象的共享数据。
(2). 图例给出抽取共享变量的过程
还是以多线程卖票的程序为例进行说明
[1]. 图1演示了如何将Thread子类程序抽取共享数据并且完成对Thread子类的修改过程
代码修改到这种程度之后,基本上就可以实现了初衷。
3). Thread子类的run方法抽取----优化I
(1). 2)中对Thread类或者其子类修正之后存在的缺点
[1]. 缺点I:增加了Thread类或者其子类对锁对象的访问的难度
由于共享数据 (线程需要共同操作的对象和锁对象) 已经被封装到自定义的SynClassI中,所以如果想在Thread 类或者其子类中使用锁对象进行代码块的同步需要通过Thread子类的SynClassI类型的成员变量synClassObj进行调用。由于此时锁对象objLock2在SynClassI中是以私有成员的形式存在的,所以要为这个私有成员增加Setter和Getter以方便外部的run方法对这个锁对象进行访问。
[2]. 缺点II:增加了Thread类或者其子类对共享操作数据的访问的难度
同样道理:Thread类或者子类想访问被封装到SynClassI类中的需要被多个线程同时操作的ticketNum的时候,也是不能直接在run方法中直接进行访问。
[3]. 通过图的形式表现出第一次修正存在缺点
(2). Thread子类+ 自定义SynClassI类的优化 ----抽取run()方法
[1]. 优化I:将Thread类或者子类中的run方法也移植到自定义类SynClassI类中
这样在SynClassI中不用提供共享数据的Setter和Getter,直接在SynClass中的run方法中直接访问需要的共享数据。
[2]. 优化II:在Thread类或者子类中直接通过自定义类SynClass类的synClassObj对象直接调用SynClassObj封装好的run方法即可。这样无论是多线程运行的时候需要什么种类的共享数据,都直接在自定义类SynClass中的run方法进行操作,Thread类或者子类只管调用自定义类的run方法就可以了。
【优点】为自定义SynClass类增加了run方法实际上是格式化了Thread类的代码
(3). 取run()方法后的示例代码 ----优化I示例代码
[1]. 优化之后的自定义类的代码SynClassII
class SynClassII{
private int tickNum =10;
private Object objLock2 =new Object();
//自定义类SynClassII增加了run方法 ----格式化了Thread类或者子类中的run的代码
public void run(){
while(true){
synchronized(objLock2){
if(tickNum <=0)
break;
try {
Thread.sleep(10);
}catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread()+". sale: ticket"+ tickNum--);
}
}
}
}
[2]. 优化之后的Thread子类的代码 TicketV
class TicketV extends Thread{
private SynClassII synClassObj;
public TicketV(){}
public TicketV(SynClassII synClassObj){
super();
this.synClassObj =synClassObj;
}
public void run(){
//防止synClassObj没有被赋值而抛出空指针异常
//----无论用户自定义的代码是什么样的,都是调用自定义类对象的run方法
//---- 非常固定 --- 格式化
if(this.synClassObj !=null){
this.synClassObj.run();
}
}
}
【 注意】
这里进行if判断的原因就是保证在synClassObj是空的情况下不会抛出控制异常。
[3]. 优化之后的测试代码
public class TickeDemoCompareVI{
public static void main(String[] args){
SynClassIIsynClassObj =new SynClassII();
Threadt1 =new TicketV(synClassObj);
Threadt2 =new TicketV(synClassObj);
Threadt3 =new TicketV(synClassObj);
Threadt4 =new TicketV(synClassObj);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
[4]. 优化之后的运行结果
4). 自定义类向上抽取成公共接口----优化II
(1). 自定义SynClass类的进一步优化 ---- 向上抽取成通用接口
[1]. 从 (4) 中的优化可以看出来,为了使得Thread类或者其子类代码能够格式化书写并且更加方便地操作共享对象,这就要求自定义类中就必须包含run方法。
[2]. 所以对自定义的SynClassII类进行向上抽取,抽取成自定义通用的接口RunnableII,这个RunnableII接口中仅仅声明一个空参的run方法即可。
【注意】抽取成接口而不是抽象类的原因是:系统不知道默认的多线程应该运行什么代码,所以没有默认的多线程运行代码存在。因此向上抽取成接口要比抽象类更加合理。
[3]. 为了Thread类或者子类的通用性,将涉及到自定义类的SynClassII地方全部由其父级接口RunnableII类型替代【多态扩展】
(2). 向上抽取之后的代码 ---- 优化II代码示例
[1]. 向上抽取的SynClassII的公共接口Runnable的示例代码
interface RunnableII{ public void run();}
[2]. 实现了公共接口Runnable的子类SynClassII的示例代码
class SynClassII implements RunnableII{
private int tickNum =10;
private Object objLock2 =new Object();
//自定义类SynClassII增加了run方法 ----格式化了Thread类或者子类中的run的代码
public void run(){
while(true){
synchronized(objLock2){
if(tickNum <=0)
break;
try {
Thread.sleep(10);
}catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread()+". sale: ticket"+ tickNum--);
}
}
}
}
[3]. 优化之后Thread子类的 示例代码
class TicketV extends Thread{
private RunnableII target;
public TicketV(){}
public TicketV(RunnableII target){
super();
this.target =target;
}
public void run(){
//防止target没有被赋值而抛出空指针异常
//----无论用户自定义的代码是什么样的,都是调用自定义类对象的run方法
//---- 非常固定 --- 格式化
if(this.target !=null){
this.target.run();
}
}
}
[4]. 优化之后的测试类代码
public class TickeDemoCompareVII{
public static void main(String[] args){
RunnableIIrunnableII =new SynClassII();
Threadt1 =new TicketV(runnableII);
Threadt2 =new TicketV(runnableII);
Threadt3 =new TicketV(runnableII);
Threadt4 =new TicketV(runnableII);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
[5]. 运行结果
2. Thread类和Runnable接口的关系
综述:Java设计者在设计Thread类和Runnable的时候就遵循了在1中提到的优化过程。下面以图的形式类比一下在1中优化完成的代码和Thread类和Runnable接口的源码
1). 源码类比
(1). 自定义接口和java.lang.Runnable接口对比
(2). 自定义RunnableII实现子类和java.lang.Runnable实现子类的对比
(3). 自定义Thread实现子类和Thread类的对比
所以java.lang.Thread同样也有以java.lang.Runnable为参数的接口
2). Thread类和Runnable接口的run的关系
假设Thread类中的成员变量的Runnable位置传入了非null的值。这样Thread类中的Runnable类型的成员变量target的指向了实际的Runnable实现子类的对象,有确定的run方法实现体。这个时候,Thread类中的run方法中的if判断成立,执行的是target.run();也就是执行的是Runnable实现子类的对象中的run方法。
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------