前言
多线程在Java编程中占据了很重要的地位,他也是处理多并发机制的基础.在此文中将介绍线程的不同创建方式以及他们之间的优缺点和不同.首先我们需要知道什么是线程,线程和进程之间又有什么关系和联系…
线程与进程
我们知道进程就是运行着的程序,操作系统中一个程序的执行周期称为一个进程。在遥远的DOS时期,电脑系统都是单进程的,即在同一时间内只允许一个进程进行工作。后来发展到了Windows系统后,我们知道是可以多个进程同时执行的,所以Windows是一个多进程操作系统
那么线程是什么呢?
线程就是一个轻量级的进程,他是寄生于进程的,没有进程就没有线程。一个进程中可能有多个线程,线程就是进程中的不同任务线。一个进程同时执行多个任务,每一个任务都叫做线程。线程之间可以同时工作互不影响,他们是区别独立存在的单一个体。不同的线程之间可以在进程运行期间不受干扰的并发执行。
多线程和多进程的区别:
本质区别在于每一个进程都拥有自己的一整套变量,而线程则共享数据.也正是因为数据的共享使得线程间通信更加的方便,有效.
多线程的表现
在我们实际的生活中.购物系统就是一个典型的高并发多线程系统,每一个用户都是一个线程,都可以在购物系统中同时操作互不干扰,一旦购物系统关闭,所有线程也随之灭亡.(我们常常提到的高并发就是指访问的线程量非常非常高)
线程的五种状态
一个线程的一生通常包括五种状态:创建,就绪,运行,阻塞,死亡.
①当一个线程创建完成后他不会立即运行而是先处于一个就绪状态来等待cup资源的分配之后才进入运行状态.
②如果一个线程当前运行所需的资源被其他线程正在使用切无法释放那么该线程就会因为缺少运行所需的资源而进入阻塞状态.
线程的创建
继承Thread类实现多线程
java.lang.Thread类是一个线程操作的核心类,创建线程的最简单方式就是继承这个类然后覆写其中的run()方法(run方法就相当于主类中的main()方法,所有线程的执行都是从这里开始的).
定义一个线程的主体类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class MyThread extends Thread{
private String name;
private int i = 0;
public MyThread(String name){
this.name = name;
}
public void run() {
for(int j=0; j<5; j++){
System.out.println("i = "+i);
i++;
}
}
}
此时我们便有了一个线程类,使用这个类就可以创建出我们所希望的线程,在这里我们创建两个不同的线程1
2MyThread thread1 = new MyThread("Thread-1");
MyThread thread2 = new MyThread("Thread-2");
从start()到run()
按照上面所述,线程执行的入口就是run()方法,那么这两个线程我们只需要分别调用run()方法便可以使得他们并发执行同时工作,可实际是这样吗?我们运行并查看下结果1
2
3
4
5
6
7
8
9
10public class Thread1Test {
public static void main(String[] args) {
MyThread thread1 = new MyThread("Thread-1");
MyThread thread2 = new MyThread("Thread-2");
thread1.run();
thread2.run();
}
}
结果如下图:
我们看到两个线程中i都是从0开始到4结束,打印结果中两个线程的运行结果并没有穿插执行而是顺序完成的,这也就意味着两个线程并没有同时工作而是像普通的程序一样顺序完成.这并没有达到我们想要的在一个程序中同时完成两件事的目的,那么为什么会造成这样的原因呢?
其实,当一个线程创建完成之后,他就是一个独立的个体,启动一个线程有专门方法–> start().此方法是专门启动线程的.他又和run()方法有什么区别呢?
● 首先我们来看一下使用start()方法启动线程的结果1
2
3
4
5
6
7
8
9
10public class Thread1Test {
public static void main(String[] args) {
MyThread thread1 = new MyThread("Thread-1");
MyThread thread2 = new MyThread("Thread-2");
thread1.start();
thread2.start();
}
}
此时我们可以看到两个线程共同工作,他们各自的i的值交叉出现,达到了多线程并发的目的.为什么run()和start()会造成不一样的结果呢?
–> 因为run()方法只是一个普通的方法. 多线程的目的是使多个独立的线程能够同时运行,如果直接调用run()方法,此时仍然是要将run()方法执行完才能进行其他的线程仍然达不到多线程的目的.start()方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕就可以直接继续执行下面的代码。
● 我们来查看一下start()方法的源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
Ⅰ.首先我们看到在启动线程时可能会抛出一个IllegalThreadStateException异常,但我们看到对于这个异常并没有进行异常处理,并且也不会报错.所以他是一个RunTimeException异常.其实这个异常就是来帮助我们判断是否能够正常启动线程的,当我们试图启动一个已经被启动的线程那么这个异常就会出现线程也不能被启动.所以,一个线程对象只能启动一次
Ⅱ.再往下我们看到这里设置了一个标记位然后调用了start0()方法,并将标志位改变,而这个方法是一个只声明而没有实现且用关键字native修饰的方法().native指的是调用本机的原生系统函数,因此我们知道这个方法其实已经和我们的Java程序没有关系了,他是由本机原生函数实现的.那么线程的启动又和本机的原生系统函数有什么关联?
Thread类有个registerNatives本地方法,这个方法主要的作用就是注册一些本地方法供Thread类使用,例如start0(),stop0()等,可以说,所有操作本地线程的本地方法都是由他注册的.
这个方法放在一个static语句块中,当该类被加载到JVM中的时候,他就会被调用,进而注册相应的本地方法
而本地方法 registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,里面的内容都是由c语言实现的,它定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21JNIEXPORT void JNICALL
Java_Java_lang_Thread_registerNatives (JNIEnv \*env, jclass cls){ //registerNatives
(\*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
static JNINativeMethod methods[] = {
{"start0", "()V",(void \*)&JVM_StartThread}, //start0 方法
{"stop0", "(" OBJ ")V", (void \*)&JVM_StopThread},
{"isAlive","()Z",(void \*)&JVM_IsThreadAlive},
{"suspend0","()V",(void \*)&JVM_SuspendThread},
{"resume0","()V",(void \*)&JVM_ResumeThread},
{"setPriority0","(I)V",(void \*)&JVM_SetThreadPriority},
{"yield", "()V",(void \*)&JVM_Yield},
{"sleep","(J)V",(void \*)&JVM_Sleep},
{"currentThread","()" THD,(void \*)&JVM_CurrentThread},
{"countStackFrames","()I",(void \*)&JVM_CountStackFrames},
{"interrupt0","()V",(void \*)&JVM_Interrupt},
{"isInterrupted","(Z)Z",(void \*)&JVM_IsInterrupted},
{"holdsLock","(" OBJ ")Z",(void \*)&JVM_HoldsLock},
{"getThreads","()[" THD,(void \*)&JVM_GetAllThreads},
{"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};
我们可以看到调用start0时实际上会调用到JVM_StartThread函数,那么这个函数又是如何实现并且最终调用run()函数的
在实现JVM的C++程序 jvm.cpp中有如下代码片段1
2
3
4
5JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)){
...
native_thread = new JavaThread(&thread_entry, sz);
...
}
这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,
其线程函数是 thread_entry,如下:1
2
3
4
5
6
7
8
9static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,obj,
KlassHandle(THREAD,SystemDictionary::Thread_klass()),
vmSymbolHandles::run_method_name(), //LOOK! 看这里
vmSymbolHandles::void_method_signature(),THREAD);
}
可以看到调用了 vmSymbolHandles::run_method_name 方法,而run_method_name是在 vmSymbols.hpp 用宏定义的:1
2
3
4
5class vmSymbolHandles: AllStatic {
...
template(run_method_name,"run") //这里决定了调用的方法名称是 “run”!
...
}
因此我们可以知道Java线程创建的调用流程图如下:
即先在程序内部调用线程,使用start启动线程后调用start0,这时候并没有直接去调用run()方法而是通过JVM去调用系统函数,以此来获取各种数据及资源最终通过本地的系统原生函数来调用run()方法依次来实现多线程并发执行的目的
Runnable()接口实现多线程
通过继承Thread类来实现多线程有一个很大的弊端就是单继承缺陷,Thread类的核心功能就是进行线程的启动,所以我们只要能够通过调用Thread类来启动线程就好,在java中又提供了另外一种实现模式:Runnable接口
观察一下Runnable接口:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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接口实现线程主体类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class MyThread extends Thread{
private String name;
private int i = 0;
public MyThread(String name){
this.name = name;
}
public void run() {
for(int j=0; j<5; j++){
System.out.println("i = "+i);
i++;
}
}
}
准备好了接口的实现类想要启动线程还是远远不够的,上面也解释了不能够通过直接调用run()方法来启动线程,我们还需要能够调用start()方法的对象,那么就只有Thread类了.通过查看API我们可以看到在Thread的构造方法中有一个传递Runnable接口的实现类,如下所示1
public Thread(Runnable target)
我们只需要在创建Thread对象的时候传递一个Runnable实现类的对象就可以创建出来一个可以正常执行的线程了1
2
3
4
5
6
7
8
9
10
11
12
13public class MyRunnableTest {
public static void main(String[] args) {
MyRunnable myRunnable1 = new MyRunnable("Thread-1");
MyRunnable myRunnable2 = new MyRunnable("Thread-2");
MyRunnable myRunnable3 = new MyRunnable("Thread-3");
new Thread(myRunnable1).start();
new Thread(myRunnable2).start();
new Thread(myRunnable3).start();
}
}
这样便创建好了三个可以并发执行的线程
需要注意的是: 多线程的启动永远都是Thread类的start()方法!!!
Thread和Runnable的区别
Thread是一个类,Runnable是一个接口,所以从形式上来说使用Runnable会更好,因为他不会因为单继承的原因而使程序受到限制.但不仅仅局限于这个原因
查看API我们可以看到其实Thread就是Runnable的一个子类1
2
3
4
5public void run() {
if (target != null) {
target.run();
}
}
他同样覆写了run()方法.
再看看上面创建并运行线程的代码,Runnable是一个接口,他有一个等待子类覆写的run()方法.Thread实现了这个接口并且覆写了run()方法,不光如此他还添加了其他方法以辅助执行run()方法的进行,而我们在使用Thread类的时候也只需要重新覆写run()方法,这不就是代理模式嘛!!
多线程的处理采用的就是代理模式,这也是Thread和Runnable的联系之一!
他们还有一个区别:使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念(并不是说Thread不能)
举个栗子:
现在我们有10张车票分别由A,B,C三个人购买
● 使用Thread类来实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class ThreadTicket extends Thread {
private String name;
private int ticket = 10;
public ThreadTicket(String name){
this.name = name;
}
public void run() {
while(this.ticket>0){
System.out.println(Thread.currentThread().getName()+"目前还有票:"+ticket+"张");
ticket--;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadTicket thread1 = new ThreadTicket("thread-1");
ThreadTicket thread2 = new ThreadTicket("thread-2");
ThreadTicket thread3 = new ThreadTicket("thread-3");
thread1.start();
thread2.start();
thread3.start();
}
}
代码写好后我们运行一下看看
我只截了一部分结果,但是已经能发现问题了,这里似乎三个线程都有自己的十张票而不是三个线程同时购买十张票,为什么会出现这种情况?就是因为当前这三个线程并不能够共享资源(10张票).这也是使用Thread创建线程的一个不方便之处,他不便于多线程共享资源.
● 使用Runnable来实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class RunnableTicket implements Runnable {
private int ticket = 10;
public void run() {
while(ticket>0){
System.out.println(Thread.currentThread().getName()+" 目前还剩余:"+ticket+"张");
if(ticket>0){
synchronized(this){
ticket--;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
RunnableTicket runnableTicket = new RunnableTicket();
Thread thread1 = new Thread(runnableTicket);
Thread thread2 = new Thread(runnableTicket);
Thread thread3 = new Thread(runnableTicket);
thread1.start();
thread2.start();
thread3.start();
}
}
这样就能够很方便的实现十张票的共享(和上面相比还要注意处理线程的异步性问题即加上synchronized关键字),让三个不同的线程同时进行购票操作
Runnable实现的多线程的程序类可以更好的描述出程序共享的概念
Callable实现多线程
Runnable中的run()方法没有返回值,Callable实现多线程可以将线程的结果返回
Callable接口:1
2
3
4
5
6
7
8
9
10
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
接口中的call()方法就相当于Runnable中的run()方法,不同的是作为一个泛型接口他有返回值,可以线程运行结果带回
Callable接口的使用
Callable接口的使用必须配合Future类和FutureTask类
● Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future类位于java.util.concurrent包下,它是一个接口:
● FutureTask
我们先来看一下FutureTask的实现:1
public class FutureTask<V> implements RunnableFuture<V>
FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:1
2
3public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
FutureTask提供了2个构造器:1
2
3
4
5public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
事实上,FutureTask是Future接口的一个唯一实现类。
有了以上两个类我们便可以使用Callable了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class CallableTest implements Callable<String> {
private int ticket = 10;
public synchronized String call() throws Exception {
while(this.ticket>0){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"现在剩余票数为:"+this.ticket);
this.ticket--;
}
return "票已卖完..";
}
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<String>(new CallableTest());
Thread thread1 = new Thread(futureTask);
Thread thread2 = new Thread(futureTask);
Thread thread3 = new Thread(futureTask);
thread1.start();
thread2.start();
thread3.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
多线程常用的操作方法
线程休眠(sleep)
线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行
线程休眠会暂时交出cpu,让cpu去执行其他的任务.但是要注意一点,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
休眠时间使用毫秒做单位
线程让步(yield)
暂停当前正在执行的线程对象并去执行其他线程.
当调用yield方法会让当前线程交出cpu的使用权,并且让其他线程先使用cpu,此方法也不会释放锁.yield不能具体控制交出cpu的时间,并且只能让相同优先级的线程去获得这期间的cpu使用权限
需要注意的是yield不会让线程进入阻塞状态而是让线程重新回到就绪状态,等待cpu的下次调用,这一点是和sleep有区别的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class TestYield {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" i = "+i);
Thread.yield(); //当前线程暂时交出cpu让其他优先级相同的线程先进行,当前线程返回就绪状态
}
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();
thread2.start();
thread3.start();
}
}
等待线程暂时终止(join)
等待该线程终止.意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run()方法先执行完毕后在开始执行主线程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class TestJoin {
public static void main(String[] args) {
System.out.println("主线程开始执行...");
Thread thread = new Thread(new Runnable() {
public void run() {
for(int i =0; i<5; i++){
System.out.println(Thread.currentThread().getName()+" i = "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"SonThread");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行完毕..");
}
}
代码分析:如果不使用join方法,那么子线程和主线程的打印关系是不确定的即可能交叉着出现,子线程一旦调用了join方法,那么主线程的第二句打印肯定会在子线程所有操作结束后进行,因为join使得主线程暂时阻塞,优先进行调用此方法的子线程
线程停止
让线程停止的方法有三种
● 设置标记位使线程退出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class ThreadStop1 implements Runnable {
boolean flag = true; //将标记位设置为true,当为false时线程退出
public void run() {
int i = 1;
while(flag){ //标志位判断
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+i+"次执行"+"线程名为:"+Thread.currentThread().getName());
i++;
}
}
public void setFlag(boolean flag) { //设置set方法使得可以在主线程中更改标志位
this.flag = flag;
}
public static void main(String[] args) throws InterruptedException {
ThreadStop1 threadStop1 = new ThreadStop1();
Thread thread = new Thread(threadStop1,"Thread-1");
thread.start();
Thread.sleep(5000);
threadStop1.setFlag(false); // 更改标志位,退出子线程
System.out.println("代码结束");
}
}
● 使用stop()方法使线程强制退出
此方法现在已经被舍弃了,因为这种方法是一种不安全的方法.stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止.在多线程的程序中,由于方法都是同步的,突然使用stop停止一个线程那么他所占用的资源以及更改的数据都会全部立即停止,这样就对其他线程的访问造成了数据的缺失或者损坏,产生不完整的残废数据
示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public class ThreadStop2 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable(){
public void run() {
int i=0;
while(true){
i++;
System.out.println(Thread.currentThread().getName()+"i= "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop();
}
}
启动线程后发现本可以无限循环打印的线程只能够打印出5个不同的i值,这就是因为在启动main线程后的5s内子线程可以自己进行工作,在5s后便被stop()方法强行停止了
● 中断线程(interrupt)
interrupt()方法也是让线程停下来的一种做法,但是他不同于stop能够立马让线程停止,他有自己的一种更安全的做法.
在Thread类中有一个方法 isInterrupt() 此方法能够查看当前线程的中断标志位,线程的中断标志默认为false.当一个线程调用了interrupt方法便会改变此线程的中断标志位状态将其设置为true,但是不会立马就中断这个正在运行的线程,他需要根据线程当前的状态进行不同的后续操作.
1.如果线程当前处于非阻塞状态:那么就仅仅是将中断的标志位修改为true,由程序员决定接下来怎么处置这个线程;如果在标志位被修改为true之后线程又调用了join,sleep或者wait,立马会抛出一个InterruptedException,且中断标志会被程序自动清除,重新设置为false.
2.如果线程的当前状态处于阻塞状态(由sleep,join,wait造成的阻塞),那么在将中断标志设置为true后会再次将中断标志重新设置为false,并抛出一个InterruptedException异常.
总结:调用线程的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常.因此,通过interrupt方法真正实现线程中断的原理是:开发人员根据中断标志的具体指来决定如何退出线程
代码示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public class ThreadStop3 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable(){
public void run() {
int i = 0;
while(true){
boolean flag = Thread.currentThread().isInterrupted(); //查看线程当前中断标志
if(flag){
System.out.println("线程被中断");
break;
}else{
i++;
System.out.println(Thread.currentThread().getName()+" i= "+i);
//情况二
try {
Thread.sleep(1000);
} catch (InterruptedException e) { //如果线程处于阻塞状态则抛出异常
e.printStackTrace();
System.out.println("当前线程处于阻塞状态,刷新标志位为:"+Thread.currentThread().isInterrupted());
return;
}
}
}
}
},"Thread-1");
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
拓展:线程的优先级和守护线程
线程的优先级
线程的优先级指的是:线程的优先级越高越有可能先执行,但仅仅是有可能而已
● 在Thread类中提供有如下优先级方法
1.设置优先级1
public final void setPriority(int newPriority)
2.取得优先级1
public final int getPriority()
● 优先级的等级划分:
最高优先级:public final static int MAX_PRIORITY = 10;
中等优先级:public final static int NORM_PRIORITY = 5;
最低优先级:public final static int MIN_PRIORITY = 1;
● 主方法所在的线程默认优先级别为5
● 线程是有继承性的,当在A线程中启动B线程,那么B线程和A线程的优先级将是一样的
守护线程
在java中有两种线程:用户线程和守护线程.可以通过isDaemon()方法来区别他们:如果返回为false,则说明该线程是”用户线程”,否则就是”守护线程”.守护线程的代表就是垃圾回收线程.只要在JVM中还有任何一个没有结束的非守护线程,那么守护线程就在工作,只有当最后一个非守护线程结束时,守护线程才会随着JVM一起停止工作