线程Thread
如何创建线程
创建线程的两种方式:这里其实需要注意,说是说创建线程可以通过继承Thread和实现Runnable接口来创建,我们先看看一下。
1.继承Thread类,并重写run方法
class IThread extends Thread {
@Override
public void run() {
super.run();
}
}
IThread thread = new IThread();
thread.start();
2.实现Runnable接口,并实现run方法
class IRunnable implements Runnable {
@Override
public void run() {
}
}
Thread thread = new Thread(new IRunnable());
thread.start();
可以看到,最后通过Thread类创建线程。所以,准确来说,只能通过Thread类来创建线程。Runnable只是一个执行器。
Thread 类构造方法
Thread()
分配新的 Thread 对象。
Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
Thread(String name)
分配新的 Thread 对象。
Thread(ThreadGroup group, Runnable target)
分配新的 Thread 对象。
Thread(ThreadGroup group, Runnable target, String name)
分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈尺寸。
Thread(ThreadGroup group, String name)
分配新的 Thread 对象。
上面是Thread类所有的构造方法,仅是列举。下面我们看几个比较常用的。
常见构造方法解析
创建线程默认有一个线程名,以Thread-
为前缀,从数字0开始计数。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
也可以自定义线程名。
public Thread(String name) {
init(null, null, name, 0);
}
继承Thread或是实现Runnable接口方式,都可以指定线程的名字。
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
Thread线程执行原理
如果在构造Thread的时候没有传递Runnable或者没有重写Thread的run方法,该Thread将不会调用任何东西,如果传递了Runnable接口实例,或者重写了Thread的run方法,则会执行该方法的逻辑单元(逻辑代码)。
@Override
public void run() {
if (target != null) {
target.run();
}
}
线程组只是稍微说明一下,这里如果在不传入的情况下,获取的是哪个线程组。具体的线程组的方法后面会单独看。
线程组
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
...
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
...
}
我们看一下init方法,可以看到如果g为null。
则使用
if (g == null) {
g = parent.getThreadGroup();
}
获取的是parent.getThreadGroup。 parent是
Thread parent = currentThread();
而currentThread() 是
public static native Thread currentThread();
这里获取到的就是当前线程。那为什么当前线程就是要创建线程的父线程呢?这是因为在 new Thread()
的时候,其实还是在main方法(即main线程)中执行的, 此时还没有start出一个新的线程,所以传入的就是调用者的线程组。
此时子线程和父线程在同一个线程组。
总结:如果构造线程对象时未传入ThreadGroup, Thread 会默认获取父线程的ThreadGroup作为该线程的ThreadGroup。
stackSize
构建Thread的时候,传入stackSize代表这该线程占用的stack大小,如果没有指定stackSize的大小,默认是0.0代表这会忽略该参数,该参数会被JNI(虚拟机函数)函数使用。
需要注意:该参数在一些平台有效,一些平台无效。
Thread 类的方法
下面我们列举一些比较常见的Thread类中的方法。
currentThread()
第一个,必须要说的就是 获取线程的方法
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
一般通过 Thread.currentThread()
来获取当前对象。
start() 和 run()
接下来就是如果启动线程,以及执行业务逻辑的两个方法。
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
这里需要注意一下调用Thread类的start方法和run方法的区别:
run() 方法和 start() 方法的区别:
- 使用Thread对象去调用run方法,相当于普通方法的调用,不会开启一个新的线程去执行run方法,它只会在当前线程中串行执行run方法中的代码。
- 如果需要在子线程中执行run方法的业务逻辑,则需要使用start方法来启动一个新的线程。start() 方法底层调用 start0() 方法,而start0()方法是一个native方法,底层实现会去调用run方法。
常用方法
long getId()
返回该线程的标识符。
String getName()
返回该线程的名称。
int getPriority()
返回线程的优先级。
static boolean interrupted()
测试当前线程是否已经中断。
boolean isAlive()
测试线程是否处于活动状态。
boolean isDaemon()
测试该线程是否为守护线程。
boolean isInterrupted()
测试线程是否已经中断。
static int activeCount()
返回当前线程的线程组中活动线程的数目。
Thread.State getState()
返回该线程的状态。
ThreadGroup getThreadGroup()
返回该线程所属的线程组。
static int enumerate(Thread[] tarray)
将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
void setName(String name)
改变线程名称,使之与参数 name 相同。
上面这些方法都基本可以通过看中文解释就能知道其用法了,就不过多的解释了。
守护线程
设置线程为守护线程,需要在start()方法前设置。
void setDaemon(boolean on);
如果线程都是守护线程,则程序会自动结束。守护线程主要是辅助作用,如果没有了非守护线程,则程序自动结束,即使守护线程还存在。
线程优先级
void setPriority(int newPriority)
设置线程的优先级。线程的优先级默认为5. 最小为1,最大为10.
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
yield()
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
yield方法就是暂停当前线程,并执行其他线程。
sleep()
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
static void sleep(long millis, int nanos)
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)。
sleep 方法,顾明思义就是睡眠。就是将当前线程睡眠多少ms。需要注意的是,sleep方法是只会释放资源,不会释放锁的,与wait方法是不同的,当调用wait方法时,其占用的锁资源和锁是会释放的。
join()
void join()
等待该线程终止。
void join(long millis)
等待该线程终止的时间最长为 millis 毫秒。
void join(long millis, int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
join 翻译为加入。比如下面这段代码:
Thread t = new Thread(){
@Override
public void run(){}
};
t.start();
t.join();
System.out.println("running here.");
上面这段代码表示启动了线程t。当执行到t.join()
时,需要等待t线程结束,相当于把线程t加入(join)到主线程中,然后先后执行。所以join方法就是等待该线程。
interrupt()
void interrupt()
中断线程。
中断线程。interrupt在api文档的描述是:如果线程在调用Object类的wait()、wait(long) 或 wait(long,int)方法,或者该类的join()、join(long)、join(long,int)、 sleep(long) 或者 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException。
interrupt for sleep
下面演示一下,如果使用interrupt来中断程序:
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(100_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("running here.");
}
};
thread.start();
thread.interrupt();
这是使用interrupt方法中断正在sleep的线程。当执行到 thread.interrupt();
这一行时,Thread.sleep(100_000);
会抛出异常,然后线程被中断,继续向后执行。输出running here.
。
interrupt for wait
Thread thread = new Thread(){
@Override
public void run() {
synchronized (this){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("running here.");
}
}
};
thread.start();
thread.interrupt();
这里是中断wait线程。同样当执行到thread.interrupt();
, this.wait();
会抛出 InterruptedException
然后被捕捉,接下来输出 running here.
interrupt for join
Thread thread = new Thread(){
@Override
public void run() {
while (true){
}
}
};
thread.start();
Thread thread1 = new Thread(){
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("running here.");
}
};
thread1.start();
thread1.interrupt();
由于这里需要测试线程等待 join。即执行到thread.join()
后面的语句需要等待thread线程执行完之后,才会得到执行。所以这里启动了两个线程。
当执行到thread1.interrupt()
语句时,thread.join()
语句会抛出异常,然后被捕捉到,接着执行后面的语句,输出running here.
wait() 和 notify()
注意,这两个方法不是定义在Thread类的,也是定义在Object类中的。前面有介绍到为什么要设计到Object中。以及wait方法和notify方法的使用。那这里就仅仅看看wait和notify方法的重载方法:
void notify()
唤醒在此对象监视器上等待的单个线程。
void notifyAll()
唤醒在此对象监视器上等待的所有线程。
void wait()
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
- notify方法就是在当前monitor下随机唤醒一个正在wait的线程。而notifyAll 是唤醒当前monitor下所有wait线程。
- wait方法表示一直等待,直到被notify或notifyAll给唤醒。而带有timeout则是要不被唤醒,要不到了时间自己行,这里与sleep的意思差不多,但是用法不同。
ThreadGroup
线程组,就是将线程进行分组管理
构造方法
ThreadGroup(String name)
构造一个新线程组。
ThreadGroup(ThreadGroup parent, String name)
创建一个新线程组。
普通方法
下面介绍一下ThreadGroup的方法。其实基本通过下面的解释都可以理解了,但是我们还是操作一遍:
首先定义代码:
ThreadGroup threadGroupA = new ThreadGroup("Thread-Group-A");
threadGroupA.setMaxPriority(8);
Thread threadA = new Thread(threadGroupA,"Thread-A"){
@Override
public void run() {
synchronized (ThreadGroupAPI.class){
try {
ThreadGroupAPI.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
threadA.start();
ThreadGroup threadGroupB = new ThreadGroup(threadGroupA,"Thread-Group-B");
threadGroupB.setMaxPriority(7);
Thread threadB = new Thread(threadGroupB,"Thread-Group-B"){
@Override
public void run() {
synchronized (ThreadGroupAPI.class){
try {
ThreadGroupAPI.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
threadB.start();
ThreadGroup threadGroupC = new ThreadGroup(threadGroupB,"Thread-Group-C");
threadGroupC.setMaxPriority(6);
threadGroupC.setDaemon(true);
Thread threadC = new Thread(threadGroupC,"Thread-C"){
@Override
public void run() {
synchronized (ThreadGroupAPI.class){
try {
ThreadGroupAPI.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
threadC.start();
这里定义了三个线程组合三个线程。ThreadA是线程组ThreadGroupA的线程。ThreadB是线程组ThreadGroupB的线程,ThreadC是线程组ThreadGroupC的线程。并且,ThreadGroupA 是ThreadGroupB 的parent, ThreadGroupB是ThreadGroupC的parent。
常见方法集
int activeCount()
获取当前ThreadGroup下所有的active的线程的个数
int activeGroupCount()
获取当前ThreadGroup下所有active的线程组的个数
void destroy()
销毁此线程组及其子线程组,需要保证当前线程组和子线程组 均为空否则会抛出 `IllegalThreadStateException` 非法线程状态异常
int getMaxPriority()
获取当前线程组及子线程组的最大优先级
String getName()
获取线程组的名字
ThreadGroup getParent()
获取当前线程的父线程组
void interrupt()
中断当前线程组和子线程的所有线程
boolean isDaemon()
判断当前线程组是否为一个守护线程组。
boolean isDestroyed()
测试此线程组是否已经被销毁。
boolean parentOf(ThreadGroup g)
测试此线程组是否为线程组参数或其祖先线程组之一。
void setDaemon(boolean daemon)
更改此线程组的后台程序状态。 设置后台线程,守护线程。
void setMaxPriority(int pri)
和设置线程组的最高优先级。
enumerate()
int enumerate(Thread[] list)
把此线程组及其子组中的所有活动线程复制到指定数组中。
int enumerate(Thread[] list, boolean recurse)
把此线程组中的所有活动线程复制到指定数组中。
int enumerate(ThreadGroup[] list)
把对此线程组中的所有活动子组的引用复制到指定数组中。
int enumerate(ThreadGroup[] list, boolean recurse)
把对此线程组中的所有活动子组的引用复制到指定数组中。
- enumerate 传入的是Thread数组的话
- 如果第二个参数不传或是为true,则表示获取当前线程组和所有子线程组中所有的线程,将其存放在传入的Thread数组中。
- 而如果第二个参数recurse为false,则表示不包含子线程组中的线程。
- enumerate 传入的是ThreadGroup数组的话
- 如果第二个参数不传或是为true,则表示获取当前线程的 所有子线程组(不包含当前线程组,但是包含子线程组的子线程组)。将其放入到传入的ThreadGroup数组中。
- 而如果第二个参数recurse为true,则表示不包含子线程组的子线程组,只返回当前线程组的所有子线程组(不包含当前线程组)
Thread[] threads = new Thread[threadGroupA.activeCount()];
threadGroupA.enumerate(threads);
// [Thread[Thread-A,5,Thread-Group-A], Thread[Thread-Group-B,5,Thread-Group-B],
// Thread[Thread-C,5,Thread-Group-C]]
System.out.println(Arrays.toString(threads)); // 3
threads = new Thread[threadGroupA.activeCount()];
threadGroupA.enumerate(threads, false);
// [Thread[Thread-A,5,Thread-Group-A], null, null]
System.out.println(Arrays.toString(threads));
ThreadGroup[] threadGroups = new ThreadGroup[threadGroupA.activeGroupCount()];
threadGroupA.enumerate(threadGroups);
// [java.lang.ThreadGroup[name=Thread-Group-B,maxpri=10],
// java.lang.ThreadGroup[name=Thread-Group-C,maxpri=10]]
System.out.println(Arrays.toString(threadGroups));
threadGroups = new ThreadGroup[threadGroupA.activeGroupCount()];
threadGroupA.enumerate(threadGroups, false);
// [java.lang.ThreadGroup[name=Thread-Group-B,maxpri=10], null]
System.out.println(Arrays.toString(threadGroups));