您的位置:首页 > 其它

创建线程的方法

2016-09-18 16:32 190 查看
从操作系统的角度来看,线程是CPU调度的最小单位——可以理解为一种资源、权利,即能够获得CPU的使用权

网上很多文章中说java创建线程的方法有两种(Thread Runnable)或三种(外加Callable Future),这种说法不能说是错误的,但是很容易误导人,实际上,创建线程(拥有CPU使用权的线程)只有一种方法,那就是继承Thread类。实现Runnable(包括Callable Future)准确地说,不是线程,而是任务,站在操作系统的角度来看,一个类实现类Runnable,并没有立刻获得能够使用CPU的权利,来看一下Runnable和Thread的源码

@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
*
* @see     java.lang.Thread#run()
*/
public abstract void run();
}


public class Thread implements Runnable {
private ThreadGroup group;

public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();

group.add(this);

boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}

private native void start0();

/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* @see     #start()
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}


注释我去掉了一些,从Thread.run()方法来看,Runnable要是能运行的话——获得CPU的使用权,那么要放在Thread的构造函数中,否则不执行,然后看Thread.start()方法,其中调用了start0()方法,该方法是个本地方法,本地方法的实现要去看源码,openjdk8的源码下载地址,因为最终是要看cpp的,而我不会cpp,引用另外两篇博文:调用Thread.start()方法,JVM(Hotspot)会调用操作系统的API来创建一个操作系统线程,此时一个Java线程对象就与一个操作系统线程一一对应。

那么为什么需要Runnable接口呢?只有Thread不就够类么,重写run()方法就行了,网上很多都说Runnable是接口,而Thread是类,实现Runnable能够避免Java单根继承带来的局限,且实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享

我认为都没说到点子上,重点是:调用Thread.start()时,是通过操作系统来创建线程的,而创建线程必然是运行在内核态,那么就一定有上下文切换,从而带来额外的时间和空间开销。如果将线程(Thread对象)看作插槽,它代表了资源——能够使用CPU(其实还有寄存器等存储资源),Runnable对象看作插头——要运行的任务,因为创建、撤销插槽开销很大,那么可以先创建一组插槽,什么任务需要运行,就插入相应的插头,就能够避免频繁的创建、撤销,避免上下文切换,从而提高性能,没错,就是线程池的思想

很多地方的线程和任务并没有分清楚,比如这里,若是站在java程序员的角度,Runnable就是一个线程,为啥?我要在执行多个任务,实现Runnable接口,不就是线程么

1.1实现Runnable接口

public class MyRunnable{

public static void main(String[] args) {
Thread[] ths = new Thread[3];
for (int i = 0; i < 3; i++) {
ths[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++)
System.out.print(j);
System.out.print(" ");
}
});
}
for (Thread th: ths)
th.start();
}
}


注意Runnable使用了匿名内部类,

1.2继承自Thread类

public class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 5; i++)
System.out.print(i);
System.out.print(" ");
}

public static void main(String[] args){
MyThread[] thx = {
new MyThread(),
new MyThread(),
new MyThread(),
};
for (MyThread th: thx){
th.start();
}
}
}


需要注意的是,以上两个程序中,线程的启动顺序是确定的,但是执行顺序是不确定的,因为是并发执行而不是顺序执行的,JVM和操作系统一起决定了线程的执行顺序,下图分别为Runnable在windown7+jdk1.7.0_67和ubuntu16.04+jdk1.8.0_101上运行的结果





参考资料:

1. java编程思想

2. http://www.ticmy.com/?p=230

3. http://blog.csdn.net/jeffhtlee/article/details/12751825
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: