一、创建线程
下面两种方式相信一定非常的熟悉,这是我们常用的方式,下面就以它开头,对线程的创建和启动做一个详细的分析。也是希望通过分析这个过程来理解多线程。
1、继承Thread的方式创建线程
public class ThreadTest extends Thread {
public void run(){
System.out.println("重写run方法");
}
public static void main(String[] args) {
//构造Thread子类对象并启动
new ThreadTest().start();
}
}
2、实现Runnable接口的方式创建线程
public class ThreadRunnableTest implements Runnable{
@Override
public void run() {
System.out.println("do something here!");
}
public static void main(String[] args) {
//创建一个类对象
ThreadRunnableTest runnableTest = new ThreadRunnableTest();
//创建一个线程对象
Thread thread = new Thread(runnableTest);
//启动线程
thread.start();
}
}
二、Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
用@FunctionalInterface注解标注,表示可以用Lambda表达式来创建接口实例,该接口只有一个抽象的run方法。
三、构造方法
1、创建一个Thread类实例,并调用他的start方法,要创建一个Thread类的实例自然要通过构造函数,因此下面来看下具体这些构造函数
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
2、通过上面的构造函数不难发现,每个构造函数都有一个init方法,而且这个init方法里面有四个参数,他们分别是:
- ThreadGroup g(线程组)
- Runnable target (Runnable 对象)
- String name (线程的名字)
- long stackSize (为线程分配的栈的大小,若为0则表示忽略这个参数)
3、而这个init方法又调用了另一个init方法,设置关于线程组和访问控制上下文的
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
初始化一个线程。
参数:
g – 线程组
target – 调用 run() 方法的对象
name - 新线程的名称
stackSize – 新线程所需的堆栈大小,或为零表示将忽略此参数。
acc – 要继承的 AccessControlContext,如果为 null,则为 AccessController.getContext()
推断注释:
参数g : @org.jetbrains.annotations.Nullable
参数name : @org.jetbrains.annotations.NotNull
参数acc : @org.jetbrains.annotations.Nullable
4、综合上面来看,其他不做详细描述,下面两个参数是我们常用到的。
- target – 调用 run() 方法的对象
- name - 新线程的名称
5、线程的名字其默认值为"Thread-" + nextThreadNum()
nextThreadNum()
:
就是一个简单的递增计数器,所以如果创建线程时没有指定线程名,那线程名就会是:Thread-0, Thread-1, Thread-2, Thread-3, ...
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
6、重要参数:
Runnable target (Runnable 对象)
:通过这里可以发现创建一个线程实例最重要的是传入一个Runnable
类型的对象,既然是Runnable
类型的,那他一定是实现了Runnable
接口,也就是说该对象一定重写了run
方法,Thread
类本身也实现了Runnable
接口,所以它必然也覆写了run
方法,我们先来看看它的run
方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
这个run方法仅仅是调用了target对象的run方法,如果我们在线程构造时没有传入target
(例如调用了无参构造函数),那么这个run方法就什么也不会做。
四、启动线程
当我们的线程创建完成后,下面就是启动线程,在Java中启动一个线程必须调用start方法,通过下面的代码可以看出,这个方法本质是调用了native的start0方法,这个方法使得线程开始执行,并由JVM来执行这个线程的run方法,结果就是有两个线程在并发执行,一个是当前线程,也就是调用了Thread#start
方法的线程,另一个线程就是当前thread对象代表的线程,它执行了run方法。也就是说,这个Thread类实例代表的线程最终会执行它的run
方法,而上面的分析中我们知道,它的run
做的事就是调用Runnable对象的run方法,如果Runnable对象为null, 就啥也不做。
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();
我们知道,Thread类从定义上看就是个普通的java类,是什么魔法让它从一个普通的java类晋升为一个可以代表线程的类呢?是native方法!
如果我们直接调用target对象的run方法,或者Thread类的run方法,那就是一个普通调用,因为run方法就是普普通通的类方法,与我们平时调用的其他类方法没有什么不同,这并不会产生多线程。
但是,如果我们调用了start方法,由于它内部使用了native方法来启动线程,它将导致一个新的线程被创建出来, 而我们的Thread实例, 就代表了这个新创建出来的线程, 并且由这个新创建出来的线程来执行Thread实例的run方法。
实例一:
public class CustomizedThread extends Thread {
public static void main(String[] args) {
System.out.println("{"+Thread.currentThread().getName()+"线程}:"+"我是定义main方法里的...");
CustomizedThread thread = new CustomizedThread();
thread.run();
}
public void run(){
System.out.println("{"+Thread.currentThread().getName()+"线程}:"+"我是定义在CustomizedThread类中的run方法。");
}
}
执行结果:
{main线程}:我是定义main方法里的...
{main线程}:我是定义在CustomizedThread类中的run方法。
Process finished with exit code 0
通过上面的执行结果可以看出,这个只有一个main线程,虽然执行了我们自己定义的run方法,但是他并没有产生新的线程,这个时候这里的run方法就是一个普通的方法。
public class CustomizedThread extends Thread {
public static void main(String[] args) {
System.out.println("{"+Thread.currentThread().getName()+"线程}:"+"我是定义main方法里的...");
CustomizedThread thread = new CustomizedThread();
thread.start();
}
public void run(){
System.out.println("{"+Thread.currentThread().getName()+"线程}:"+"我是定义在CustomizedThread类中的run方法。");
}
}
执行结果:
{main线程}:我是定义main方法里的...
{Thread-0线程}:我是定义在CustomizedThread类中的run方法。
Process finished with exit code 0
通过上面的执行结果可以看出这里非常明显就是两个线程,一个是main的线程,他执行了main方法,一个是Thread-0线程,他是我们自定义的线程,他执行了run方法。这里我们调用的是start方法,他的底层调用了native来启动线程。
疑问
不是说:创建一个线程最重要的是要传入一个Runnable对象吗?上面的实例为什么没有传入Runnable对象呢?
public class CustomizedThread extends Thread {
...
}
很明显,这里我的CustomizedThread类继承了Thread类,由于上面的实例并没有传入参数,他就默认调用了父类Thread的无参构造方法,通过下面的代码可以看出来这里的target是null,然后,我们使用了myThread.start()
,因为我们在子类中没有定义start方法,所以,这个方法来自父类,而Thread类的start方法的作用我们已经讲过,它将新建一个线程,并调用它的run方法,这个新建的线程的抽象代表就是我们的CustomizedThread
,所以它的(CustomizedThread的)run方法将会被调用。如果这里我们没有写run方法,那么当然就是调用父类的run方法了。而Thread类的run方法调用的又是target对象的run方法,而target对象现在为null, 所以这个方法啥也不做。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
父类的run方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
小结:
创建一个线程最重要的是定义一个run方法,这个run方法要么通过继承Thread类的子类覆写,要么通过直接构造Thread类时传入一个Runnable的target对象。无论它由子类覆写提供还是由target对象提供,start方法最终都会新建一个线程来执行这个run方法。
实例二:
public class CustomizedThread2 implements Runnable {
@Override
public void run() {
System.out.println("{" + Thread.currentThread().getName() + "线程}: " + "我是传递给Thread类的Runnable对象的run方法");
}
public static void main(String[] args) {
System.out.println("{" + Thread.currentThread().getName() + "线程}: " + "我是main方法");
Thread thread = new Thread(new CustomizedThread2());
thread.start();
}
}
执行结果:
{main线程}: 我是main方法
{Thread-0线程}: 我是传递给Thread类的Runnable对象的run方法
Process finished with exit code 0
上面的实例,通过新建Thread类的对象来创建线程,他的本质是传递一个Runnable对象给Thread的构造函数,通过myThread.start()
来启动这个线程,start方法会调用run方法,而thread类的run方法最终会调用target对象的run方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
总结
在Java中创建一个线程有且仅有一种方式:就是通过创建一个Thread类的实例,并调用它的start方法,当然更重要的是还要定义一个run方法,说明这个线程具体要做什么。继承Thread类,覆写run方法或者实现Runnale接口,将它作为target参数传递给Thread类构造函数,启动一个线程一定要调用该线程的start方法,否则,并不会创建出新的线程来。