1:继承Thread类跟实现Runnable接口
实现Runnable接口的优点:
摆脱单继承的局限
可以实现资源共享
来看一下Thread类的部分源码:
[java]
public class Thread implements Runnable {
private Runnable target; // What will be run.
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0); //Initializes a Thread.
}
public void run() { //覆写了run方法
if (target != null) {
target.run();
}
}
public synchronized void start() {
if (threadStatus != 0) // 如果线程已经启动,就抛出异常
throw new IllegalThreadStateException();
group.add(this);
start0(); // 调用本地方法启动线程
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
private native void start0();
}
public class Thread implements Runnable {
private Runnable target; // What will be run.
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0); //Initializes a Thread.
}
public void run() { //覆写了run方法
if (target != null) {
target.run();
}
}
public synchronized void start() {
if (threadStatus != 0) // 如果线程已经启动,就抛出异常
throw new IllegalThreadStateException();
group.add(this);
start0(); // 调用本地方法启动线程
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
private native void start0();
}API中对Runnable接口的说明:设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,Thread 类实现了 Runnable。
多线程编程中,涉及到两个类((多线程代码封装类,Runnable接口实现类),Thread类(可以启动新线程)),
下面结合上面Thread类的源码来分析一下继承Thread类跟实现实现Runnable接口的小区别:
继承Thread类:我们的类会覆写Thread类的run方法,并把线程执行代码封装进run方法,然后调用Thread类的start方法启动线程。
要注意的是,Thread类也实现了Runnable接口,这就是为什么我们能把Thread类当成多线程代码封装类来使用的原因,因为它能去做这件事。
但这不是他的本职工作,顶多算扩展功能。他的本质工作是启动新线程。
实现Runnable接口:在线程对象初始化的时候通过构造方法传入Runnable接口实现类,然后Thread类的start方法会调用run方法,
run方法中判断target不等于null,于是执行的是我们Runnable接口实现类中的run方法。这也就是为什么能实现对象资源共享的原因,因为可以操作同一个对象。
有很多人说实现Runnable接口来创建线程对象,应用了代理模式。我们来看一下吧,
代理模式的定义:一个角色代表另一个角色来完成某些特定的功能
代理模式有三个角色: 1. 抽象主题角色 2. 代理主题角色 3. 实际被代理角色,如果应用了静态代理模式,那么对应的角色如下:
这里的Runnable接口作为抽象主题角色提供一个公共协议,Thread类作为主题角色实现了该协议,我们的Runnable实现类作为实际被代理角色也实现了该协议。
如果是代理模式,那么我们对被代理角色的访问应该可以是Runnable thread = new Thread(),然后thread.run方法执行被Thread类代理的方法。
但在这里,这样调用对于多线程来说是没有意义的,不会启动新的线程,仅仅就是普通方法调用,抛弃多线程,那么这里的确是静态代理模式。
这对于Thread类来说,他是通过Thread t = new Thread(); t.start(); start方法再去调用run方法。仅仅就是多态模式下的方法调用罢了。
Thread类之所以实现Runnable接口,是为了本身的功能扩展。上面已经分析过了。即使不实现Runnable接口,一样可以使用第二种方式来创建新线程对象,那这还属于代理模式吗?
2:多线程同步问题
问题的引出:通过实现Runnable接口的方式来创建新线程解决了多条线程数据共享问题。那么项目在实际运行的过程中,
由于操作过程可能不仅仅是对数据进行单纯的修改操作,还可能有对数据的判断操作,临时缓存,数据库操作等,
在这些操作过程中,因为网络延时,线程切换等就会触发数据覆盖,判断异常等问题。
例A<数据覆盖问题>:
[java]
class DoSomething {
private int a = 10;
public void add(int b) {
int temp = a; // 缓存现有值
temp += b; // 缓存累加后的值
try {
Thread.sleep(100); // 模拟处理数据的时间或者线程切换。
} catch (Exception e) { }
a = temp; // 模拟写入操作。
System.out.println(getA()); // 输出当前线程对a进行累加操作后的值
}
public int getA() {
return this.a;
}
}
public class ThreadDemo {
private static Thread[] threads = new Thread[10];
public static void main(String[] args) {
final DoSomething ds = new DoSomething();
for (int i=0; i<10; i++) { // 开启10条线程进行操作
threads[i] = new Thread(new Runnable(){
@Override
public void run() {