本文经过几个问题入手,主要经过案例讲解Thread类的使用,以及注意事项,最后站在源码的角度分析问题。java
多线程不必定快!多线程
多线程只是极大限度的利用CPU的空闲时间来处理其余的任务,这样才会缩短多个任务的执行时间。若是在没有CPU空闲的状况下,多线程之间的上下文切换(保存线程的运行环境,还原线程的运行环境)会产生开销,执行时间会慢于单线程。异步
单任务操做系统:同步执行,排队。只有当前任务执行完了才能够执行下一个任务。例如cmd中执行完一条指令以后才能够执行另外一条指令ide
多任务操做系统:异步执行,经过时间片的快速切换实现多个任务响应执行。源码分析
(1)继承Thread类,重写run( )方法性能
package com.feng.example; public class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("继承Thread类"); } }
启动线程:测试
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub MyThread myThread = new MyThread(); myThread.start(); } }
(2)建立一个Runnable接口的实现类A,将A的实例对象做为Thread类的参数传入this
package com.feng.example; public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub System.out.println("经过实现Runnable接口重写run"); } }
启动线程:spa
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable myRunnable = new MyRunnable(); Thread myThread = new Thread(myRunnable); myThread.start(); } }
因为在Thread类也是实现Runnable接口的,所以Thread的对象也能够做为第二种形式的参数操作系统
public class Thread implements Runnable {} //这里只是贴出源码中Thread类的定义
将第一种方式建立的MyThread类对象做为第二种方式的输入参数
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立MyThread类对象 Runnable myRunnable = new MyThread(); Thread myThread = new Thread(myRunnable); myThread.start(); } }
两种方式的区别:两种方式没有本质的区别,通常都会使用第二种形式,主要解决java中类不能多继承的缺陷。
好比Cat类继承Animal,同时又想将Cat定义为线程类,由于Cat不能同时继承Animal,Thread类,可是Cat能够继承Animal,实现Runnable接口
回顾一下操做系统课中线程的建立过程:线程的建立包括两部分
(1)建立线程,至关于MyThread myThread = new MyThread( );
(2)将线程放到就绪队列中,若是仅仅只是建立了线程却没有将线程放到就绪队列中,线程调度器是无法找到线程的。所以myThread.start( );将线程放到就绪队列中。等待分配cpu时间片,当获取了cpu以后就能够执行线程的run( )方法
经过start( )方法:是新开辟了一个线程,run( )方法是由线程规划器来调用的,是异步运行
直接调用run( )方法:调用者是Thread子类的一个对象,属于同步执行,没有开辟新的线程。
package com.feng.example; public class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("线程为:"+Thread.currentThread().getName()); } }
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立MyThread类对象 MyThread myThread = new MyThread(); myThread.start(); myThread.run(); //同步调用run方法 } }
程序运行结果:
首先查看Thread类源码中是如何重写Runnable接口中的run( )方法的。
public void run() { if (target != null) { target.run(); } }
接下来看一下这个target又是什么?
/* What will be run. */ private Runnable target;
由源码能够看出,在Thread类的内部,存在一个类型为Runnable的成员变量,此成员变量即是接收第二种建立线程类的方法中的输入参数的。再看Thread类的run方法,只是判断有此target存不存在,若是存在便执行target的run( )方法。
若是不存在怎么办?
若是不存在则是使用继承的方式来实现的线程类,那么run( )方法已经在Thread子类中进行了重写,覆盖了父类的run( )方法
下面经过一个题目来消化这个知识点:
Runnable接口的实现类以下:
package com.feng.example; public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub System.out.println("经过Runnable接口实现线程类"); } }
Thread的子类实现以下:
package com.feng.example; public class MyThread extends Thread { public MyThread(Runnable target) { super(target); } @Override public void run() { // TODO Auto-generated method stub System.out.println("经过继承实现线程类"); } }
测试代码以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立Runnable接口实现类 Runnable myRunnable = new MyRunnable(); //建立MyThread类对象 MyThread myThread = new MyThread(myRunnable); //到底输出的是谁的run方法???? myThread.start(); } }
分析: 首先start的是MyThread线程,由于会执行MyThread类的run( )方法。虽然传入了Runnable类型的参数,可是在MyThread类中的run( )方法中并无super.run( );所以程序只输出MyThread类中的run( )方法中的输出。
输出结果如图:
修改MyThread类中的run( )方法:
package com.feng.example; public class MyThread extends Thread { public MyThread(Runnable target) { super(target); } @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("经过继承实现线程类"); } }
分析:
首先start的是MyThread线程,由于会执行MyThread类的run( )方法。由于在构造MyThread时传入了Runnable类型的参数,所以在执行MyThread类中的run( )方法时,先执行super.run( ); 执行Thread类的run( ); 根据源代码先检查target存不存,这里target就是存入的myRunnable, 因此执行myRunnable里面的run( );执行完super.run( )方法后,在输出后面的语句。
输入结果以下图:
在有些地方可能会以匿名类的形式来进行考察:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub System.out.println("Runnable..."); } }){ public void run() { //super.run(); System.out.println("Thread..."); } }.start(); } }
看到此代码不要惊慌,其实与我最初定义的三个类文件的考察方式是同样的,只不过这里使用了匿名类,若是忘记了回去翻看匿名类的知识,此题注意注释的那一句super.run( ); 看一下注释不注释有什么区别。
只要理解Thread源码中的run( )方法,这个题目就显得很简单了。
1、在研究join的用法以前,先明确两件事情。
1.join方法定义在Thread类中,则调用者必须是一个线程,
例如:
Thread t = new CustomThread();//这里通常是自定义的线程类
t.start();//线程起动
t.join();//此处会抛出InterruptedException异常
2.上面的两行代码也是在一个线程里面执行的
以上出现了两个线程,一个是咱们自定义的线程类,咱们实现了run方法,作一些咱们须要的工做;另一个线程,生成咱们自定义线程类的对象,而后执行
customThread.start();
customThread.join();
在这种状况下,两个线程的关系是一个线程由另一个线程生成并起动,因此咱们暂且认为第一个线程叫作“子线程”,另一个线程叫作“主线程”。
2、为何要用join()方法
主线程生成并起动了子线程,而子线程里要进行大量的耗时的运算(这里能够借鉴下线程的做用),当主线程处理完其余的事务后,须要用到子线程的处理结果,这个时候就要用到join();方法了。
3、join方法的做用
在网上看到有人说“将两个线程合并”。这样解释我以为理解起来还更麻烦。不如就借鉴下API里的说法:
“等待该线程终止。”
解释一下,是主线程(我在“一”里已经命名过了)等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。(Waits for this thread to die.)
进入源码
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) { //进入这个分支
while (isAlive()) { //进入这个分支
wait(0);//阻塞
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
从实现角度来讲:
sychronied 关键字是JVM层实现的,由其提供内置的锁。而lock方式必须被显示的建立
从代码角度来讲
lock方式,代码缺少优雅性。sychronized代码更加简洁,代码量少。
从使用角度来讲
lock方式更加灵活,可是只有在解决特殊问题的时候才会使用。
用sychronized关键字不能尝试着获取锁且最终获取锁会失败,或者尝试着获取锁一段时间,而后放弃它,要实现这些必须使用lock方式。
使用sychronized关键字时,若是某些事务失败了,那么就会抛出异常,由JVM自动释放线程资源,可是你没法也没有机会去作任何的清理工做,以维护系统使其处于良好的状态。使用显示的Lock方式,你就可使用finally子句将系统维护在正确的状态了。
显示的Lock对象在加锁和释放锁方面,相对于内建的sychronized锁来讲,还赋予了你耕细粒度的控制力。
从性能角度来讲:
在资源竞争不是很激烈的状况下,Synchronized的性能要优于ReetrantLock,可是在资源竞争很激烈的状况下,Synchronized的性能会降低几十倍,可是ReetrantLock的性能能维持常态