线程创建的基础方式
线程创建主要分为两大类,一类是实现Runnable、Callable或继承Thread类后直接创建,另一种是通过线程池创建
继承Thread类创建线程
Thread类实际上是实现了Runnable类并实现了run()方法,我们在继承Thread类后,也需要重写run()方法,在run()方法中实现我们的业务逻辑,并调用子类的start()方法,开启一个线程并执行业务;
// 继承Thread类,重写run()方法
public class ThreadDemo extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + ": " + System.currentTimeMillis());
}
}
public static void main(String[] args) {
// 1.实例化子类
ThreadDemo threadDemo = new ThreadDemo();
// 2.启动线程
threadDemo.start();
}
}
//我们可以看到运行结果,每1s打印一次
Thread[Thread-0,5,main]: 1597114115408
Thread[Thread-0,5,main]: 1597114116408
Thread[Thread-0,5,main]: 1597114117409
Thread[Thread-0,5,main]: 1597114118409
这里有一个问题:我们为什么不直接调用run方法,而是调用了start()方法呢?
当我们new Thread()时,就会新建一个线程,但是并没有启动,若此时直接调用run()方法,便会被当成当前线程的一个普通方法执行,并没有新开启一个线程,而start()方法则会启动一个新的线程并进入就绪态,一旦得到分配的时间片后便执行run()方法中的业务代码了。
实现Runnable创建线程
在继承Thread类创建线程的方法中,我们提到过Thread类实际上也是实现了Runnable接口,然后在创建并启动线程后执行了run()方法。
我们来看下Runnable接口的代码
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
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
发现Runnable接口只定义了一个run()方法,因此我们在实现Runnable接口后,还需要进一步的操作才能创建一个线程,请看代码
//实现Runnable接口
public class RunnableDemo implements Runnable {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + ": " + System.currentTimeMillis());
}
}
public static void main(String[] args) {
// 1.实例化实现Runnable接口的类
RunnableDemo runnableDemo = new RunnableDemo();
// 2.新建一个线程,并将实例化的runnableDemo作为参数传入thread
Thread thread = new Thread(runnableDemo);
// 3.调用thread类的start()方法启动线程,执行RunnableDemo类中实现的run()方法
thread.start();
}
}
//执行结果
Thread[Thread-0,5,main]: 1597115827464
Thread[Thread-0,5,main]: 1597115828465
Thread[Thread-0,5,main]: 1597115829465
Thread[Thread-0,5,main]: 1597115830465
从上面代码我们可以看出,把在new Thread()时把实现了Runnable接口的RunnableDemo实例作为参数传入了Thread的构造方法,再调用Thread的start()方法,进而执行了RunnableDemo中重写的run()方法,完成了一次线程从创建、启动、执行的过程
了解Thread类的构造方法
在上文中,我们了解到,实现了Runnable接口的类的示例是可以作为参数传入Thread类的构造方法的,接下来看一下Thread类的public构造方法
// 1
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
// 2
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
// 3
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
// 4
public Thread(String name) {
init(null, null, name, 0);
}
// 5
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
// 6
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
// 7
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
// 8
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
从所有的构造方法我们可以看到,最终都是调用了init()方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
//调用了重载的init()方法
init(g, target, name, stackSize, null, true);
}
//重载的init()方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
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 (inheritThreadLocals && 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();
}
现在让我们来依次看一下几常用参数的意义
-
ThreadGroup g
线程组,指定创建后的线程属于哪个线程组,init()方法中可以看到,如果不指定g,则默认归属于父线程的线程组
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(); } }
-
Target target
实现了Runnable接口或继承了Thread的类的实例对象,在Thread类中的run()方法可以看到,若target为null,则没有任何执行结果
@Override public void run() { if (target != null) { target.run(); } }
-
String name,线程名称,以一个不传该参数的重载方法来看,默认是Thread-线程号
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
-
long stackSize
/* * The requested stack size for this thread, or 0 if the creator did * not specify a stack size. It is up to the VM to do whatever it * likes with this number; some VMs will ignore it. */ private long stackSize;
新开线程是需要栈空间的,这里的stackSize则是指定新开的栈空间大小,而这个参数默认是0的,注意最后一句:It is up to the VM to do whatever it likes with this number; some VMs will ignore it。也就是说这个主要是依据jvm的设定,即便给定值了,在过高或过低甚至有的jvm直接就会忽略掉这个参数,所以不建议动这个参数的值。