目录
实现Runnable接口
1.操作步骤
2.代码示例
3.为什么不是mr调用start(),而要mr作为参数传入Thread类的实例中才行?
4.两种方式分别适用于哪些场景?
5.使用匿名内部类创建和启动线程
匿名内部类的一些优势:
总结
之前的文章我介绍了采用继承Thread这个类的方式来创建线程,这篇文章主要围绕采用实现Runnable接口,来创建线程。因为Java有单继承的限制,并且对于一些有数据共享,或者为了实现数据和代码分离的场景,采用实现Runnable接口,这种方式更具灵活性。
1.创建一个实现Runnable接口的类,即implements。
2.实现(也就是@Overrride重写)接口中的run(),把线程要执行的功能代码,写入run()方法中。
3.创建当前这个类的对象。
4.把这个对象作为参数传递到Thread类的构造器中,即把当前类的实例作为这个Thread的任务,创建Thread类的实例。
5.Thread类的实例调用start()方法。
还是先实现一个简单的功能,打印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);
}
}
}
运行结果如下:
我们之前知道,采用继承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接口这种方式。
其实我们还可以使用匿名内部类来实现之前的两种方式相同的目的:
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的优点,可以简化代码,将线程创建和启动封装在一起。