在上一节我们介绍了线程和进程的区别,这一小节我们来具体介绍线程的基础知识。
线程的基本方法
编号 | 方法 | 说明 |
---|---|---|
1 | public void start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) | 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) | 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) | 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() | 中断线程。 |
8 | public final boolean isAlive() | 测试线程是否处于活动状态。 |
9 | public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
10 | public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
11 | public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
线程的创建
线程的实现可以通过两种方式:
继承thread类
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主动创建的第"+num+"个线程");
}
}
在这个基础上写一个main方法来测试一下这个线程。
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主动创建的第"+num+"个线程");
}
}
//输出
主动创建的第1个线程
注意我们的线程类名叫MyThread
,而此时的class类名叫Test
,使用public修饰,因为我们知道Java中在一个类中允许有多个类,但只能有一个使用Public修饰,这是因为:
每编写一个Java文件就是Java编译器编译Java源代码的一个编译单元,具体怎么编译呢?这个就需要去了解Java编译器的工作原理了。编译器每编译一个.java文件(编译单元),对应着.java文件中的每个类都会有一个输出文件,而该输出文件的名称与.java文件中每个类的名称相同,只是多了一个后缀名.class。因此,在编译少量.java文件之后,会得到大量的.class文件。在.java文件中,不是必须含有public类的。public类只是用来表示编译单元中存在公开接口。
但如果含有public类,最多只能有一个,必须名字和文件名一样。因为编译的时候Java编译器会判断如果存在public类,该类当作这个编译单元的对外接口,类加载器需要把该类加载。对于一个public类,它是可以被项目中任何一个类所引用的,只需在使用它前import一下它所对应的class文件即可,将类名与文件名一一对应就可以方便虚拟机在相应的路径(包名)中找到相应的类的信息。如果不这么做的话,就很难去找,而且开销也会很大。运行的时候则是调用main()函数运行的。
好,回到我们的线程类当中,启动线程是调用线程的start()
方法,此时线程启动会执行对应线程在run()
方法体中的代码。
注意,不是调用run()
方法启动线程,run()
方法中只是定义需要执行的任务,如果调用run()
方法,即相当于在主线程中执行run()
方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。
为了分清start()
方法调用和run()
方法调用的区别,请看下面一个例子:
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
}
}
//输出结果
主线程ID:1
name:thread2 子线程ID:1
name:thread1 子线程ID:11
从输出结果可以得出以下结论:
1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run()
方法调用并不会创建新的线程,而是在主线程中直接运行run()
方法,跟普通的方法调用没有任何区别;
2)虽然thread1的start()
方法调用在thread2的run()
方法前面调用,但是先输出的是thread2的run()
方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。
实现Runnable接口
public class TestThread2 {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("子线程ID:"+Thread.currentThread().getId());
}
}
//结果
主线程ID:1
子线程ID:11
Thread和Runnable的区别
继承Thread 类和实现Runnable 接口都可以实现多线程,但是有经验的程序员都会选择实现Runnable接口 ,其主要原因有以下两点:
首先,java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了。
其次,如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
例如下面的例子
package testThread;
class runnable implements Runnable{
private int ticket = 10 ;
public void run() {
for (int i = 0; i < 10 && ticket >0; i++) {
System.out.println("Runnable ticket count : " + ticket--);
}
}
}
class thread extends Thread{
private int ticket = 10 ;
@Override
public void run() {
for (int i = 0; i < 10 && ticket >0; i++) {
System.out.println("Thread ticket count : " + ticket--);
}
}
}
public class ShareDataDemo {
public static void main(String[] args) {
runnable runnable1 = new runnable();
new Thread(runnable1).start();
new Thread(runnable1).start();
new Thread(runnable1).start();
new thread().start();
new thread().start();
new thread().start();
}
}
//其中一次的结果
Runnable ticket count : 9
Runnable ticket count : 10
Runnable ticket count : 8
Runnable ticket count : 7
Runnable ticket count : 6
Runnable ticket count : 5
Runnable ticket count : 4
Runnable ticket count : 3
Runnable ticket count : 2
Runnable ticket count : 1
Thread ticket count : 10
Thread ticket count : 9
Thread ticket count : 8
Thread ticket count : 7
Thread ticket count : 6
Thread ticket count : 5
Thread ticket count : 4
Thread ticket count : 3
Thread ticket count : 2
Thread ticket count : 1
Thread ticket count : 10
Thread ticket count : 9
Thread ticket count : 8
Thread ticket count : 7
Thread ticket count : 6
Thread ticket count : 5
Thread ticket count : 4
Thread ticket count : 3
Thread ticket count : 2
Thread ticket count : 1
Thread ticket count : 10
Thread ticket count : 9
Thread ticket count : 8
Thread ticket count : 7
Thread ticket count : 6
Thread ticket count : 5
Thread ticket count : 4
Thread ticket count : 3
Thread ticket count : 2
Thread ticket count : 1
从上面的例子中不难看出,实现Runnable接口创建的多个线程中,他们的ticket数据是共享的,所以该循环只执行了10次即结束了。而通过继承Thread类创建的线程,他们所拥有的ticket数据是不共享的。