Thread类中的方法调用方式:
学习Thread类中的方法是学习多线程的第一步。在学习多线程之前特别提出一点,调用Thread中的方法的时候,在线程类中,有两种方式,一定要理解这两种方式的区别:
1、this.XXX() 和 线程对象实例.XXX() 这里要首先参考 多线程 Thread.currentThread().getName() ,对象实例.getName() 和 this.getName()区别 理解采用Thread继承实现线程的几种启动方式出现的问题
这种调用方式表示的线程是线程实例本身
2、Thread.currentThread.XXX()或Thread.XXX() 这里一个是获取当前线程实例调用实例方法,一个是调用当前线程的静态的方法,都是指向当前调用方法的线程
上面两种写法是一样的意思。这种调用方式表示的线程是正在执行Thread.currentThread.XXX()所在代码块的线程
当然,这么说,肯定有人不理解两者之间的差别。没有关系,之后会讲清楚,尤其是在讲Thread构造函数这块。讲解后,再回过头来看上面2点,会加深理解。
Thread类中的实例方法
从Thread类中的实例方法和类方法的角度讲解Thread中的方法,这种区分的角度也有助于理解多线程中的方法。实例方法,只和实例线程(也就是new出来的线程)本身挂钩,和当前运行的是哪个线程无关。看下Thread类中的实例方法:
1、start()
start()方法的作用讲得直白点就是通知"线程规划器",此线程可以运行了,正在等待CPU调用线程对象得run()方法,产生一个异步执行的效果。通过start()方法产生得到结论,先看下代码:
public class MyThread02 extends Thread { public void run() { try { for (int i = 0; i < 3; i++) { Thread.sleep((int)(Math.random() * 1000)); System.out.println("run = " + Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) { MyThread02 mt = new MyThread02(); mt.start(); try { for (int i = 0; i < 3; i++) { Thread.sleep((int)(Math.random() * 1000)); System.out.println("run = " + Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } }
看下运行结果:
run = Thread-0 run = main run = main run = main run = Thread-0 run = Thread-0
结果表明了:CPU执行哪个线程的代码具有不确定性。再看另外一个例子:
public class MyThread03 extends Thread { public void run() { System.out.println(Thread.currentThread().getName()); } }
public static void main(String[] args) { MyThread03 mt0 = new MyThread03(); MyThread03 mt1 = new MyThread03(); MyThread03 mt2 = new MyThread03(); mt0.start(); mt1.start(); mt2.start(); }
看下运行结果:
Thread-1 Thread-2 Thread-0
尽管启动线程是按照mt0、mt1、mt2,但是实际的启动顺序却是Thread-1、Thread-2、Thread-0。这个例子说明了:调用start()方法的顺序不代表线程启动的顺序,线程启动顺序具有不确定性。
关于start线程可以了解下多线程 Thread.currentThread().getName() ,对象实例.getName() 和 this.getName()区别 理解采用Thread继承实现线程的几种启动方式出现的问题
2、run()
线程开始执行,虚拟机调用的是线程run()方法中的内容。稍微改一下之前的例子看一下:
public static void main(String[] args) { MyThread02 mt = new MyThread02(); mt.run(); try { for (int i = 0; i < 3; i++) { Thread.sleep((int)(Math.random() * 1000)); System.out.println("run = " + Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } }
MyThread02的代码不变,看下运行结果:
run = main run = main run = main run = main run = main run = main
看到打印了6次的"run = main",说明如果只有run()没有start(),Thread实例run()方法里面的内容是没有任何异步效果的,全部被main函数执行。换句话说,只有run()而不调用start()启动线程是没有任何意义的。
3、isAlive()
测试线程是否处于活动状态,只要线程启动且没有终止,方法返回的就是true。看一下例子:
public class MyThread06 extends Thread { public void run() { System.out.println("run = " + this.isAlive()); } }
public static void main(String[] args) throws Exception { MyThread06 mt = new MyThread06(); System.out.println("begin == " + mt.isAlive()); mt.start(); Thread.sleep(100); System.out.println("end == " + mt.isAlive()); }
看下运行结果:
begin == false run = true end == false
看到在start()之前,线程的isAlive是false,start()之后就是true了。main函数中加上Thread.sleep(100)的原因是为了确保Thread06的run()方法中的代码执行完,否则有可能end这里打印出来的是true,有兴趣可以自己试验一下。
isAlive() 备注下
也就是 无论什么情况 sleep wait 等 只要是还没结束 那isAlive()都是存活的
4、getId()
这个方法比较简单,就不写例子了。在一个Java应用中,有一个long型的全局唯一的线程ID生成器threadSeqNumber,每new出来一个线程都会把这个自增一次,并赋予线程的tid属性,这个是Thread自己做的,用户无法执行一个线程的Id。
5、getName()
这个方法也比较简单,也不写例子了。我们new一个线程的时候,可以指定该线程的名字,也可以不指定。如果指定,那么线程的名字就是我们自己指定的,getName()返回的也是开发者指定的线程的名字;如果不指定,那么Thread中有一个int型全局唯一的线程初始号生成器threadInitNum,Java先把threadInitNum自增,然后以"Thread-threadInitNum"的方式来命名新生成的线程
6、getPriority()和setPriority(int newPriority)
7、isDaeMon、setDaemon(boolean on)
讲解两个方法前,首先要知道理解一个概念。Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,它的作用是为其他线程的运行提供便利的服务,最典型的应用便是GC线程。如果进程中不存在非守护线程了,那么守护线程自动销毁,因为没有存在的必要,为别人服务,结果服务的对象都没了,当然就销毁了。
理解了这个概念后,看一下例子
public class MyThread11 extends Thread { private int i = 0; public void run() { try { while (true) { i++; System.out.println("i = " + i); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) { try { MyThread11 mt = new MyThread11(); mt.setDaemon(true); mt.start(); Thread.sleep(5000); System.out.println("我离开thread对象再也不打印了,我停止了!"); } catch (InterruptedException e) { e.printStackTrace(); } }
看一下运行结果:
1 i = 1 2 i = 2 3 i = 3 4 i = 4 5 i = 5 6 我离开thread对象再也不打印了,我停止了! 7 i = 6
要解释一下。我们将MyThread11线程设置为守护线程,看到第6行的那句话,而i停在6不会再运行了。这说明,main线程运行了5秒多结束,而i每隔1秒累加一次,5秒后main线程执行完结束了,MyThread11作为守护线程,main函数都运行完了,自然也没有存在的必要了,就自动销毁了,因此也就没有再往下打印数字。
关于守护线程,有一个细节注意下,setDaemon(true)必须在线程start()之前
8、interrupt()
9、isInterrupted()
以上两个方法请转移
java 多线程5: java 终止线程及中断机制 (stop()、interrupt() 、interrupted()、isInterrupted())
java 多线程6: 中断机制 优雅的终止java线程 这两篇文章
10、join()
11.stop() 停止方法 已废弃
12.suspend() 和 resume() 暂停和恢复 ,已废弃
参考 java 多线程7: (suspend方法与resume方法) 挂起与恢复