Java将线程封装为Thread类。每一个Thread类或extends Thread类的对象代表一个新的线程。通过该对象继承来的start方法启动线程,线程启动后因为多态性的原理会执行相应对象中的run方法体。
也可以直接创建一个带参数的Thread对象,参数对象必须是implements runnable接口的对象,而runnable接口内有abstrcat函数run,子类必须覆盖该方法,所以使用这种方式创建的线程在start()调用后,线程执行的是覆盖了runnable的run方法。
其实原理很简单,当Thread对象创建完成,调用start方法时,首先会去检测当前Thread对象内一个runnable类型的数据成员是否为空,如果为空,调用自身的run方法,如果不为空,那么调用runnable数据成员的run方法。这样的设置非常完整的体现出面向对象的多态性,也非常直观的体现出Java中接口的作用。
用句大白话说:我不管你是什么类型,只要你实现了我规定好的接口类型,那么你就必须要覆盖该接口类型中的抽象方法,我就可以通过多态性的原理调用你这些覆盖的方法。
说到多线程就不得不说一下线程同步。
Java中线程同步有两种方式
方法1:
synchronized可以设定一个代码块,如synchronized(对象){ 同步内容 } 也可以写在函数返回类型前面整个函数起同步效果。其中synchronized后跟的只要是任何对象都可以,仅用于标记锁,相当于C++中的互斥对象所用的对象。使用同一标记(也就是同一对象)的同步代码块享用同一把锁。比如,你有两个synchronized代码块。其中的标记对象用的是同一个,那么当一个synchronized区域内有线程存在时,不单该synchronized代码块不能被其它线程进入,同标记的另一个也不可以被其它线程进入。
函数使用的标记对象锁是函数的this对象,静态函数使用的标记对象锁是所在类的Class对象。即类名点class得到的一个类型为Class的对象,也就是该类所代表的一个对象。(Class对象为反射内容,反射是Java中的特性,其功能可以替代C++中的函数指针有过之而无不及)。
run方法类implements于Runnable还是extends自Thread在同步标记对象上有一点须要注意:
如果run方法类extends自Thread那么每启动一个新的线程也就会产生一个新的run方法。如果启动两个线程,此时有两个run方法,虽然都是同一类中的run方法,但并不是同一个对象。如果这个run方法内使用this标记对象,无法产生同步。因为两个this代表的同一个类的两个不同的实例对象。
而使用run方法类实现与Runnable则不同。只须要产生一个run方法所在类的对象,然后开启两个Thread线程,并将对象传递进去,所以两个线程使用的run方法是同一个对象的。
附最简单的死锁程序,说明以上问题:
class Demo{
public static void main(String[] args) {
//两个继承Thread类的对象
Deadlock deadlock1 = new Deadlock();
Deadlock deadlock2 = new Deadlock();
//分别将两个对象中的deadlock赋于另一个对象的成员
deadlock1.deadlock = deadlock2;
deadlock2.deadlock = deadlock1;
//开启两个线程
deadlock1.start();
deadlock2.start();
}
}
class Deadlock extends Thread {
//记录另一个线程的引用
Deadlock deadlock = null;
@Override
public void run() {
DeadLockMethod();
}
//同步函数,此函数使用this标记对象锁
private synchronized void DeadLockMethod() {
System.out.println("锁1:" + Thread.currentThread().getName());
//同步代码块,此代码块的标记对象为另一个对象的引用。
synchronized(deadlock){
System.out.println("锁2" + Thread.currentThread().getName());
}
}
}
程序打印:锁1:Thread-0 锁1:Thread-1 然后死锁。原因,当0线程进去同步函数后,1线程同时进入,因为此时的同步函数DeadLockMethod()使用的是自身this引用,而两个线程,分别使用的是两个Deadlock类不同的对象,所以this也不相同无法同步,进而两个线程都进入到同步函数中。分别输出自身线程名,而同步代码块中使用的标记对象是另一个线程对象,也就是另一个线程对象同步函数使用的那个锁,该锁已经被另一个对象持有,结果造成线程死锁。
方法2:
1.5版本新特性,增加了一个显示的同步机制Lock。类Lock完全模仿C++中的互斥对象。
interface Lock可代替synchronized使用,灵活性更高。
implements于Lock接口的ReentrantLock类可以直接创建一个同步对象,如:
Lock threadLock = new ReentrantLock(); //创建同步对象
threadLock.lock(); //同步开始
threadLock.uplock(); //同步结束。
使用同一个同步对象的同步区域内有互斥效果。即ReentrantLock的对象相当于synchronized所用的标记对象。
如果lock()与uplock()之间有可能出现异常,那么uplock()应当放在异常处理块的finally中,以便确保一定释放同步对象资源。