用Java巧妙利用线程的应用(2)——实现Runnable接口

目录

实现Runnable接口

1.操作步骤

2.代码示例

3.为什么不是mr调用start(),而要mr作为参数传入Thread类的实例中才行?

4.两种方式分别适用于哪些场景?

5.使用匿名内部类创建和启动线程

匿名内部类的一些优势:

总结


实现Runnable接口

       之前的文章我介绍了采用继承Thread这个类的方式来创建线程,这篇文章主要围绕采用实现Runnable接口,来创建线程。因为Java有单继承的限制,并且对于一些有数据共享,或者为了实现数据和代码分离的场景,采用实现Runnable接口,这种方式更具灵活性。

1.操作步骤

1.创建一个实现Runnable接口的类,即implements。

2.实现(也就是@Overrride重写)接口中的run(),把线程要执行的功能代码,写入run()方法中。

3.创建当前这个类的对象。

4.把这个对象作为参数传递到Thread类的构造器中,即把当前类的实例作为这个Thread的任务,创建Thread类的实例。

5.Thread类的实例调用start()方法。

2.代码示例

还是先实现一个简单的功能,打印50以内的整数。同时获取线程的名称。

public class MyRunnable implements Runnable {
    @Override
    public void run(){
         for(int i=0;i<50;i++){
             System.out.println(Thread.currentThread().getName()+i);
         }
     }
}

       在main线程中测试我们想要的结果。mr这个对象作为Thread实例的参数传入,t1调用start()方法。启动线程并执行run()中的功能代码。这里补充一点:严格来讲,对于抽象方法的重写,应该用实现更准确。

public class TestMyRunnable {
    public static void main(String args[]){
        MyRunnable mr=new MyRunnable();
        //创建线程对象
        Thread t1=new Thread(mr);
        t1.start();

        //main线程对应的操作
        for(int i=0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

运行结果如下:

用Java巧妙利用线程的应用(2)——实现Runnable接口_第1张图片


3.为什么不是mr调用start(),而要mr作为参数传入Thread类的实例中才行?

       我们之前知道,采用继承Thread类的方式,不需要有参数传入这一步。首先我们看一下Thread的源码中的部分内容。

public class Thread implements Runnable {
    // ...

    private Runnable target; // Runnable对象,表示线程要执行的任务

    // 构造函数,接受一个Runnable对象作为参数,用于指定线程的任务
    public Thread(Runnable target) {
        this.target = target;
    }

    // run方法,线程执行的入口
    @Override
    public void run() {
        if (target != null) {
            target.run(); // 执行Runnable对象中的run方法,即线程要执行的任务逻辑
        }
    }
    // ...
}

       Thread类中有一个类型为Runnable接口的成员变量target,它用于保存线程的任务,即实现了Runnable接口的对象,在这里也就是mr。以上面的代码为例,在我们创建Thread的对象t1的时候,通过源码中的构造函数传入的Runnable参数,会被赋值给target。

       然后在源代码的run()中,可以看到,只要我们传入了Runnable接口的对象,即mr,if就会被满足,并执行下去。这样就最终达成了start()的执行。

       实际上,从代码本身的角度去看,Runnable和Thread本质上就是接口和类的区别,也就是说,其实是继承了的Thread类,它实现了Runnable接口,可以用代理的思想去理解。当然,我们更要掌握的是:发挥各自的优势适用在不同的场景中。橙色线部分代表继承Thread类,蓝线代表实现Runnable接口这种方式。

用Java巧妙利用线程的应用(2)——实现Runnable接口_第2张图片


4.两种方式分别适用于哪些场景?

用Java巧妙利用线程的应用(2)——实现Runnable接口_第3张图片


5.使用匿名内部类创建和启动线程

其实我们还可以使用匿名内部类来实现之前的两种方式相同的目的:

public class TestAnonymousRunnable {

    public static void main(String args[]){
        //创建并启动第一个线程
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        });
        t1.start();

        //main线程对应的操作
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }

        //创建并启动第二个线程
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        });
        t2.start();
    }
}

匿名内部类的一些优势:

1.简洁:在创建对象的地方直接实现接口或继承类,并覆写run()方法,减少了代码量。

2.可封装:匿名内部类将实现细节可以封装到我们创建的方法中,而且不会影响其他代码的可见性。有助于代码的模块化和清晰化。

3.灵活性:匿名内部类可以直接访问外部类的成员变量和方法,同时也可以访问final修饰的局部变量,这样使得程序员在一些相对大体量的代码下,编写代码更灵活。

总结

       本文通过代码示例详细介绍了使用实现Runnable接口的方式创建线程的具体步骤,首先需要创建一个实现了Runnable接口的类,并重写run()方法,在run()方法中编写线程需要执行的代码。然后在测试类中创建这个实现类的实例,并作为参数传递给Thread类的构造方法创建线程对象,最后调用线程对象的start()方法启动线程。

       文中解释了为什么要传递Runnable实例而不是直接调用实例的start()方法,是因为Thread类中将接收到的Runnable实例保存在成员变量target中,在线程启动时执行target的run()方法,这样就实现了执行Runnable中的run()逻辑。文章还比较了Thread和Runnable两种创建线程方式的优劣,Runnable方式更适合多线程资源共享的场景。

       最后介绍了使用匿名内部类实现Runnable的优点,可以简化代码,将线程创建和启动封装在一起。

你可能感兴趣的:(java,开发语言)