Java多线程学习(2)线程的常用方法和创建

上一节我们介绍了线程和进程的区别,这一小节我们来具体介绍线程的基础知识。

线程的基本方法

编号 方法 说明
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数据是不共享的。

猜你喜欢

转载自blog.csdn.net/lin74love/article/details/81099090