java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言, 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台 (即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se)) 的总称
本篇文章主要总结了 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 线程的一些基本的用法, 关于线程安全, 同步的知识, 放到了第二篇
来源: http://www.phperz.com/article/18/0219/358969.html