java多线程——线程的创建
1、创建线程方式一:继承Thread类
(1)定义一个类继承Thread;
(2)重写run方法;
(3)创建线程对象;
(4)调用start方法,开启线程并让线程执行,同时还会告诉JVM调用run方法。
1 class Demo extends Thread{ 2 private String name; 3 Demo(String name){ 4 this.name=name; 5 } 6 @Override 7 public void run() { 8 for(int i=1;i<=20;i++) { 9 System.out.println("name="+name+"......."+i); 10 } 11 } 12 } 13 14 public class ThreadDemo { 15 public static void main(String[] args) { 16 //method0(); 17 //method1(); 18 Demo d1=new Demo("小强"); 19 Demo d2=new Demo("旺财"); 20 d2.start();//开启多一个执行路径 21 d1.run();//由主线程负责 22 } 23 24 public static void method1() { 25 Demo d1=new Demo("小强"); 26 Demo d2=new Demo("旺财"); 27 d1.run();//由主线程负责 28 d2.start();//开启多一个执行路径,主线程结束才开启 29 } 30 31 public static void method0() { 32 Demo d1=new Demo("小强"); 33 Demo d2=new Demo("旺财"); 34 d1.run();//由主线程负责 35 d2.run();//由主线程负责 36 } 37 }
以上代码中,method1和method0运行效果相同,但是method1确实开启了多线程,只是在主线程结束后才开启。
线程对象调用run方法和调用start方法的区别?
调用run方法不开启线程,仅是对象调用方法;调用start开启线程,并让JVM调用run方法在开启的线程中执行。
为什么要继承Thread类并重写run方法?
因为Thread类描述的是线程事物,具备线程该有的功能。既然如此?为什么不能直接创建Thread类的对象:Thread t1=new Thread(); t1.start();这种写法没有错,但是该start方法调用的是Thread类中的run方法,而这个run方法没有任何操作,更重要的是,这个run方法中没有定义我们需要让线程执行的代码。创建线程的目的是为了建立单独的路径,让多部分代码实现同时执行。也就是说,线程创建并执行需要给定的代码(线程的任务)。对于我们常用的主线程,它的任务都定义在main函数中。自定义线程需要执行的任务都定义在run方法中。Thread类中的run方法内部的任务不是我们所需要,所以需要进行run方法的重写。
内存占用:多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间,进行方法的压栈和弹栈。当执行线程的任务结束了,线程自动在栈内存中释放了。当所有的执行线程都结束了,进程就结束了。
2、创建线程方式二:实现Runnable接口
(1)声明一个类实现Runnable
接口(避免了继承Thread类的单继承局限性)。
(2)该类实现run
方法(将线程任务代码定义到run方法中)。
(3)创建Thread类的对象(只有创建Thread类的对象才能创建线程)。
(4)将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
(5)启动线程。
1 //)声明一个类实现Runnable接口,该类实现run方法。 2 class Demo2 implements Runnable{ 3 private String name; 4 Demo2(String name){ 5 this.name=name; 6 } 7 @Override 8 public void run() { 9 for(int i=1;i<=20;i++) { 10 System.out.println("name="+name+"....."+Thread.currentThread().getName()+"...."+i); 11 } 12 } 13 } 14 15 public class ThreadDemo2 { 16 public static void main(String[] args) { 17 //(2)创建Runnable子类的对象 18 Demo2 d1=new Demo2("小强"); 19 Demo2 d2=new Demo2("旺财"); 20 //(3)创建Thread类的对象,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。 21 Thread t1=new Thread(d1); 22 Thread t2=new Thread(d2); 23 //(4)启动线程。 24 t1.start(); 25 t2.start(); 26 for(int i=0;i<20;i++) { 27 System.out.println(Thread.currentThread().getName()+"----->"+i); 28 } 29 30 } 31 }
在Thread类中相关源码抽离出来如下:
1 public class Thread { 2 private Runnable target; 3 public Thread(Runnable target){ 4 this.target=target; 5 } 6 7 public void run() { 8 if (target != null) { 9 target.run(); 10 } 11 } 12 }
优点(避免单继承+解耦):
第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
实现Runnable接口的方式,更加符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既有线程对象,又有线程任务。而Runnable接口,将线程任务单独分离出来,封装成对象,类型就是Runnable接口类型。
3、创建线程方式三:使用Callable和Future创建线程
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值,且call()方法可以声明抛出异常。
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法没有返回值,再创建Callable实现类的实例。(从java8开始,可以直接使用Lambda表达式创建Callable对象)。
(2)使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get方法来获得子线程执行结束后的返回值。
1 import java.util.concurrent.Callable; 2 import java.util.concurrent.ExecutionException; 3 import java.util.concurrent.FutureTask; 4 5 class DemoCall implements Callable{ 6 private int n; 7 @Override 8 public Object call() throws Exception { 9 int i=1; 10 while(i++<n) { 11 System.out.println(Thread.currentThread().getName()+"-------->"+i); 12 } 13 return Thread.currentThread().getName()+"运行完毕----------n="+n; 14 } 15 16 public DemoCall(int n) { 17 this.n=n; 18 } 19 } 20 class CallableDemo { 21 public static void main(String[] args) { 22 DemoCall d1=new DemoCall(20); 23 DemoCall d2=new DemoCall(50); 24 // 使用Callable方式创建线程,需要FutureTask类的支持,用于接收运算结果,可以使用泛型指定返回值的类型 25 FutureTask<String> ft1=new FutureTask<String>(d1); 26 FutureTask<String> ft2=new FutureTask<String>(d2); 27 new Thread(ft1).start(); 28 new Thread(ft2).start(); 29 // 接收运算结果 30 // 只有当该线程执行完毕后才会获取到运算结果,等同于闭锁的效果 31 try { 32 System.out.println(ft1.get()); 33 System.out.println(ft2.get()); 34 } catch (InterruptedException e) { 35 // TODO Auto-generated catch block 36 e.printStackTrace(); 37 } catch (ExecutionException e) { 38 // TODO Auto-generated catch block 39 e.printStackTrace(); 40 } 41 42 } 43 }