前言
记得本科时最开始听到多线程时,感觉好复杂啊。又是好多种状态啊:运行(running)、挂起(suspend)、恢复(resume)、阻塞(block)、终止(terminate)。又是各种优先级啊。还涉及到并发处理中的各种问题什么什么的。其实现在想想,在Java中,万物皆对象嘛,线程也不例外嘛。Java中的线程类是Thread,对于线程的操作也就封装在它里面了。它肯定是有已经写好的类供你使用了,我们要做的就是按照规矩和需要调其中的方法就好。当然,感觉要是想弄明白多线程的底层原理肯定还是特别难的。
Thread类里面的几个简单方法
首先要知道当开始运行程序开始运行时,肯定是开启了一个线程啊(也肯定会会有进程吧,进程线程区别后面搞明白再说)。在一般Java的main里面程序开始执行时,当然也会开启一个线程。可以通过Thread.CurrentThread()
来得到这个线程的引用,因为不需要new 对象,我们可以想到这个方法是静态的方法。可以找到Thread类中有这个静态方法。
public static native Thread currentThread();
这里的关键字native应该是告诉编译器(其实是JVM)调用的方法在外部定义,关键字native现在还没搞明白。这个方法前面是Thread这个类名,说明这个方法的返回是一个Thread的对象。
下面的程序先是得到main里的Thread的引用,然后演示几种简单的方法用途。
package threadDemo;
public class ThreadDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t = Thread.currentThread();
System.out.println("线程t的名字:"+t.getName());
System.out.println("线程t的id:"+t.getId());
System.out.println("线程t的优先级:"+t.getPriority());
System.out.println("线程t的状态:"+t.getState());
System.out.println("线程t的toString():"+t.toString());
t.setName("myThread");
System.out.println("线程t的名字:"+t.getName());
t.setPriority(4);
System.out.println("线程t的优先级:"+t.getPriority());
System.out.println("线程t的toString:"+t.toString());
}
}
getName()
:得到这个线程的名字,可以通过setName()
的方法来改变它
getId
:得到这个线程Id,Id的话就是能唯一表示这个线程的东西吧。
getPriority()
:得到这个线程的优先级,Java线程的优先级从底到高有1、2、3…10级,也就是10级是最重要的,默认会为5可以通过setPriority()
的方法来设置线程的优先级。
getState()
:得到线程当前的状态,按照官方文档,线程一个有6种状态。
NEW:这个状态是线程已经new出来了但是还没有启动。
RUNNABLE:这个线程正在运行。
BLOCKED:这个线程正在等待monitor lock。关于锁,现在只知道好像是一段代码块被锁住的话,这个代码块不会进行多线程模式,也就是它会一直运行,而不会让给别的线程。
WAITING:这个线程要等待别的线程执行完某项操作,这里它的等待时间是不确定的,就是它不知道它要等多久吧。
TIMED_WAITING:这个状态跟上面的差不多,也是等待别的线程完成某项操作,只不过这里它知道它还要等多长时间。
TERMINATED:结束这个线程呗
toString()
:这个方法本来是Thread父类Object里的方法,这里被重写了,一般这个方法都会被重写的。它在这里返回的是Thread’s name, priority, and thread group.
创建一个线程的方法
前面我们从main里面得到的线程是系统自动帮我们创建的线程,而我们要自己创建一个线程要怎么办咧。一般是有两种方法吧。一是实现Runable接口,二是继承Thread类。
(1)实现Runable接口
看下面的代码:
package threadDemo;
public class ThreadDemo {
public static Thread mainThread;
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread1 t1= new Thread1();
mainThread = Thread.currentThread();
for (int i=0;i<2;i++) {
System.out.println("MainThread is running");
System.out.println("t1线程当前的状态:"+t1.t1.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Thread1 implements Runnable{
Thread t1;
Thread1(){
t1 = new Thread(this,"Thread1");
System.out.println("new完后t1的状态:"+t1.getState());
t1.start();
System.out.println("start()后t1的状态:"+t1.getState());
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i=0;i<1;i++) {
System.out.println("t1 is running");
System.out.println("mainThread线程当前的状态:"+ThreadDemo.mainThread.getState());
try {
Thread.sleep(999);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
直接在同一个包里创建另一个类Thread1 ,并让它实现Runnable接口,并重写方法run()。run方法能够像主线程那样调用其他方法,引用其他类,声明变量。仅有的不同是run()在程序中确立另一个并发的线程执行入口。当run()返回时,该线程结束。
这个类前面不能是public额。public的类的类名必须要和文件名相同的。在Thread1的构造函数里new一个线程,并start。Console栏显示了线程在运行时的一些状态。关于Thread的构造函数还有些问题要讨论。
(2)继承Thread类
package threadDemo;
public class ThreadDemo {
public static Thread mainThread;
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread1 t1= new Thread1();
t1.start();
mainThread = Thread.currentThread();
for (int i=0;i<3;i++) {
System.out.println("MainThread is running");
System.out.println("t1线程当前的状态:"+t1.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Thread1 extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i=0;i<2;i++) {
System.out.println("t1 is running");
System.out.println("mainThread线程当前的状态:"+ThreadDemo.mainThread.getState());
try {
Thread.sleep(980);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
其实和第一种没有太大区别,也必须要重写run()方法,追求本质的话,它们两应该是一样的。
线程结束
另外,一般需要主线程最后结束。前面的两个程序是因为主线程里sleep的时间较t1线程长,所以主线程是最后结束的。然而正常情况下怎么样可以知道其他线程是否结束了,(难道不可以getState()
),参考资料上说的可以通过线程类中isAlive()
方法来判断,如果返回值是true,则线程仍在运行。但是这个方法很少用到,等待线程结束经常用join()
方法,需要用try,catch围住。下面代码就是改了下t1线程的运行时间,并在main线程里用lt1.join()
来等待t1线程结束。
package threadDemo;
public class ThreadDemo {
public static Thread mainThread;
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread1 t1= new Thread1();
t1.start();
mainThread = Thread.currentThread();
for (int i=0;i<1;i++) {
System.out.println("MainThread is running");
System.out.println("t1线程当前的状态:"+t1.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("t1线程当前的状态:"+t1.getState());
}
}
class Thread1 extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i=0;i<4;i++) {
System.out.println("t1 is running");
System.out.println("mainThread线程当前的状态:"+ThreadDemo.mainThread.getState());
try {
Thread.sleep(980);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
还有一些其他的问题需要以后讨论:
线程优先级。这个还好就是优先级高的占用cpu的时间长。
线程同步。这里涉及到管程,以我现在浅薄的理解,就是为了一个线程正在改某个数据,而另一个线程却要读这个数据。有时需要某个线程获得管程,然后其他的一些线程就需要挂起,直到第一个线程退出管程。这里可能理解的还不对。
线程间通信。
线程锁。
线程的挂起恢复和终止
参考:
Java入门经典(强烈推荐).pdf(原谅我没找到书的作者)
jdk官方文档