Java多线程基本用法总结
这篇是java多线程基本用法的一个总结。
本篇文章会从一下几个方面来说明java多线程的基本用法:
- 如何使用多线程
- 如何得到多线程的一些信息
- 如何停止线程
- 如何暂停线程
- 线程的一些其他用法
如何使用多线程
启动线程的两种方式
java 提供了2种方式来使用多线程, 一种是编写一个类来继承thread,然后覆写run方法,然后调用start方法来启动线程。这时这个类就会以另一个线程的方式来运行run方法里面的代码。另一种是编写一个类来实现runnable接口,然后实现接口方法run,然后创造一个thread对象,把实现了runnable接口的类当做构造参数,传入thread对象,最后该thread对象调用start方法。
这里的start方法是一个有启动功能的方法,该方法内部回调run方法。所以,只有调用了start方法才会启动另一个线程,直接调用run方法,还是在同一个线程中执行run,而不是在另一个线程执行run
此外,start方法只是告诉虚拟机,该线程可以启动了,也就说该线程在就绪的状态,但不代表调用start就立即运行了,这要等待jvm来决定什么时候执行这个线程。也就是说,如果有两个线程a,b ,a先调用start,b后调用start,不代表a线程先运行,b线程后运行。这都是由jvm决定了,可以认为是随机启动。
下面我们用实际的代码,来说明两种启动线程的方式:
第一种,继承thread
public class examplethread extends thread{ @override public void run() { super.run(); system.out.println("这是一个继承自thread的examplethread"); } }
测试的代码可以看test目录下的examplethreadtest类
另一种,实现了runnable接口
public class examplerunable implements runnable{ public void run() { system.out.println("这是实现runnable接口的类"); } }
测试的代码可以看test目录下的examplerunabletest类。
如何得到多线程的一些信息
我们在启动多线程之后,希望能通过一些api得到启动的线程的一些信息。jdk给我们提供了一个thread类的方法来得到线程的一些信息。
- 线程的名字 —— getname()
- 线程的id —— getid()
- 线程是否存活 —— isalive()
得到线程的名字
这些方法是属于thread的内部方法,所以我们可以用两种方式调用这些方法,一个是我们的类继承thread来使用多线程的时候,可以用过this来调用。另一种是通过thread.currentthread() 来调用这些方法。但是这两个方法在不同的使用场景下是有区别的。
我们先简单来看两个方法的使用。
第一个thread.currentthread()的使用,代码如下:
public class examplecurrentthread extends thread{ public examplecurrentthread(){ system.out.println("构造方法的打印:" + thread.currentthread().getname()); } @override public void run() { super.run(); system.out.println("run方法的打印:" + thread.currentthread().getname()); } }
测试的代码如下:
public class examplecurrentthreadtest extends testcase { public void testinit() throws exception{ examplecurrentthread thread = new examplecurrentthread(); } public void testrun() throws exception { examplecurrentthread thread = new examplecurrentthread(); thread.start(); thread.sleep(1000); } }
结果如下:
构造方法的打印:main run方法的打印:thread-0 构造方法的打印:main
为什么我们在examplecurrentthread内部用thread.currentthread()会显示构造方法的打印是main,是因为thread.currentthread()返回的是代码段正在被那个线程调用的信息。这里面很显然构造方法是被main线程执行的,而run方法是被我们自己启动的线程执行的,因为没有给他起名字,所以默认是thread-0。
接下来,我们在看一看继承自thread,用this调用。
public class complexcurrentthread extends thread{ public complexcurrentthread() { system.out.println("begin========="); system.out.println("thread.currentthread().getname=" + thread.currentthread().getname()); system.out.println("this.getname()=" + this.getname()); system.out.println("end==========="); } @override public void run() { super.run(); system.out.println("run begin======="); system.out.println("thread.currentthread().getname=" + thread.currentthread().getname()); system.out.println("this.getname()=" + this.getname()); system.out.println("run end=========="); } }
测试代码如下:
public class complexcurrentthreadtest extends testcase { public void testrun() throws exception { complexcurrentthread thread = new complexcurrentthread(); thread.setname("byhieg"); thread.start(); thread.sleep(3000); } }
结果如下:
begin========= thread.currentthread().getname=main this.getname()=thread-0 end=========== run begin======= thread.currentthread().getname=byhieg this.getname()=byhieg run end==========
首先在创建对象的时候,构造器还是被main线程所执行,所以thread.currentthread()得到的就是main线程的名字,但是this方法指的是调用方法的那个对象,也就是complexcurrentthread的线程信息,还没有setname,所以是默认的名字。然后run方法无论是thread.currentthread()还是this返回的都是设置了byhieg名字的线程信息。
所以thread.currentthread指的是具体执行这个代码块的线程信息。构造器是main执行的,而run方法则是哪个线程start,哪个线程执行run。这么看来,this能得到的信息是不准确的,因为如果我们在run中执行了this.getname(),但是run方法却是由另一个线程start的,我们是无法通过this.getname得到运行run方法的新城的信息的。而且只有继承了thread的类才能有getname等方法,这对于java没有多继承的特性语言来说,是个灾难。所有后面凡是要得到线程的信息,我们都用thread.currentthread()来调用api。
得到线程的id
调用getid取得线程的唯一标识。这个和上面的getname用法一致,没什么好说的,可以直接看exampleidthread和他的测试类exampleidthreadtest。
判断线程是否存活
方法isalive()的作用是测试线程是否处于活动状态。所谓活动状态,就是线程已经启动但是没有终止。即该线程start之后,被认为是存活的。
我们看一下具体的例子:
public class alivethread extends thread{ @override public void run() { super.run(); system.out.println("run方法中是否存活" + " " + thread.currentthread().isalive()); } }
测试方法如下:
public class alivethreadtest extends testcase { public void testrun() throws exception { alivethread thread = new alivethread(); system.out.println("begin == " + thread.isalive()); thread.start(); thread.sleep(1000); system.out.println("end ==" + thread.isalive()); thread.sleep(3000); } }
结果如下:
begin == false run方法中是否存活 true end ==false
我们可以发现在start之前,该线程被认为是没有存活,然后run的时候,是存活的,等run方法执行完,又被认为是不存活的。
如何停止线程
判断线程是否终止
jdk提供了一些方法来判断线程是否终止 —— isinterrupted()和interrupted()
停止线程的方式
这个是得到线程信息中比较重要的一个方法了,因为这个和终止线程的方法相关联。先说一下终止线程的几种方式:
- 等待run方法执行完
- 线程对象调用stop()
- 线程对象调用interrupt(),在该线程的run方法中判断是否终止,抛出一个终止异常终止。
- 线程对象调用interrupt(),在该线程的run方法中判断是否终止,以return语句结束。
第一种就不说了,第二种stop()方法已经废弃了,因为可能会产生如下原因:
- 强制结束线程,该线程应该做的清理工作,无法完成。
- 强制结束线程,该线程已操作的加锁对象强制解锁,造成数据不一致。
具体的例子可以看stoplockthread以及他的测试类stoplockthreadtest
第三种,是目前推荐的终止方法,调用interrupt,然后在run方法中判断是否终止。判断终止的方式有两种,一种是thread类的静态方法interrupted(),另一种是thread的成员方法isinterrupted()。这两个方法是有所区别的,第一个方法是会自动重置状态的,如果连续两次调用interrupted(),第一次如果是false,第二次一定是true。而isinterrupted()是不会的。
例子如下:
public class exampleinterruptthread extends thread{ @override public void run() { super.run(); try{ for(int i = 0 ; i < 50000000 ; i++){ if (interrupted()){ system.out.println("已经是停止状态,我要退出了"); throw new interruptedexception("停止......."); } system.out.println("i=" + (i + 1)); } }catch (interruptedexception e){ system.out.println("顺利停止"); } } }
测试的代码如下:
public class exampleinterruptthreadtest extends testcase { public void testrun() throws exception { exampleinterruptthread thread = new exampleinterruptthread(); thread.start(); thread.sleep(1000); thread.interrupt(); } }
第四种方法和第三种一样,唯一的区别就是将上面的代码中的抛出异常换成return,个人还是喜欢抛出异常,这里处理的形式就比较多,比如打印信息,处理资源关闭或者捕捉之后再重新向上层抛出。
注意一点,我们上面抛出的异常是interruptedexception,这里简单说一下可能产生这个异常的原因,在原有线程sleep的情况下,调用interrupt终止线程,或者先终止线程,再让线程sleep。
如何暂停线程
在jdk中提供了以下两个方法用来暂停线程和恢复线程。
- suspend()——暂停线程
- resume()——恢复线程
这两个方法和stop方法一样是被废弃的方法,其用法和stop一样,暴力的暂停线程和恢复线程。这两个方法之所以是废弃的主要由以下两个原因:
- 线程持有锁定的公共资源的情况下,一旦被暂停,则公共资源无法被其他线程所持有。
- 线程强制暂停,导致该线程执行的操作没有执行完全,这时访问该线程的数据会出现数据不一致。
线程的一些其他用法
线程的其他的一些基础用法如下:
- 线程让步
- 设置线程的优先级
- 守护线程
线程让步
jdk提供yield()方法来让线程放弃当前的cpu资源,将它让给其他的任务去占用cpu时间,但是这也是随机的事情,有可能刚放弃资源,又马上占用时间片了。
具体的例子可以参考exampleyieldthread以及他的测试类exampleyieldthreadtest
设置线程的优先级
我们可以设置线程的优先级来让cpu尽可能的将执行的资源给优先级高的线程。java设置了1-10这10个优先级,又有三个静态变量来提供三个优先级:
/** * the minimum priority that a thread can have. */ public final static int min_priority = 1; /** * the default priority that is assigned to a thread. */ public final static int norm_priority = 5; /** * the maximum priority that a thread can have. */ public final static int max_priority = 10;
我们可以通过setpriority来设置线程的优先级,可以直接传入上诉三个静态变量,也可以直接传入1-10的数字。设置后线程就会有不同的优先级。如果我们不设置优先级,会是什么情况?
线程的优先级是有继承的特性,如果我们在a线程中启动了b线程,则ab具有相同的优先级。一般我们在main线程中启动线程,就和main线程有一致的优先级。main线程的优先级默认是5。
下面说一下优先级的一些规则:
- 优先级高的线程一般会比优先级低的线程获得更多的cpu资源,但是不代表优先级高的任务一定先于优先级低的任务先执行完。因为不同优先级的线程中run方法内容可能不一样。
- 优先级高的线程一定会比优先级低的线程执行的快。如果两个线程是一样的run方法,但是优先级不一样,确实优先级高的线程先执行完。
线程守护
jdk中提供setdaemon的方法来设置一个线程变成守护线程。守护线程的特点是其他非守护线程执行完,守护线程就自动销毁,典型的例子是gc回收器。
具体可以看exampledaemonthread和exampledaemonthreadtest。
总结
这篇文章主要总结了java线程的一些基本的用法,关于线程安全,同步的知识,放到了第二篇。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!