精华内容
下载资源
问答
  • 线程创建的四种方式

    万次阅读 多人点赞 2018-03-30 14:34:14
    Java可以用四种方式创建线程,如下所示: 1)继承Thread类创建线程 2)实现Runnable接口创建线程 3)使用Callable和Future创建线程 4)使用线程池例如用Executor框架 下面让我们分别来看看这四种创建线程的...

    java中创建线程的四种方法以及区别

    Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示:

    1)继承Thread类创建线程

    2)实现Runnable接口创建线程

    3)使用Callable和Future创建线程

    4)使用线程池例如用Executor框架

    下面让我们分别来看看这四种创建线程的方法。

    ------------------------继承Thread类创建线程---------------------

    通过继承Thread类来创建并启动多线程的一般步骤如下

    1】d定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

    2】创建Thread子类的实例,也就是创建了线程对象

    3】启动线程,即调用线程的start()方法

    代码实例

    public class MyThread extends Thread{//继承Thread类

      public void run(){

      //重写run方法

      }

    }

    public class Main {

      public static void main(String[] args){

        new MyThread().start();//创建并启动线程

      }

    }

    ------------------------实现Runnable接口创建线程---------------------

    通过实现Runnable接口创建并启动线程一般步骤如下:

    1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

    2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

    3】第三部依然是通过调用线程对象的start()方法来启动线程

    代码实例:

    public class MyThread2 implements Runnable {//实现Runnable接口

      public void run(){

      //重写run方法

      }

    }

    public class Main {

      public static void main(String[] args){

        //创建并启动线程

        MyThread2 myThread=new MyThread2();

        Thread thread=new Thread(myThread);

        thread().start();

        //或者    new Thread(new MyThread2()).start();

      }

    }

    ------------------------使用Callable和Future创建线程---------------------

    和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

    》call()方法可以有返回值

    》call()方法可以声明抛出异常

    Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

    >boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

    >V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

    >V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

    >boolean isDone():若Callable任务完成,返回True

    >boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

    介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

    1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

    2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

    3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

    4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    代码实例:

    public class Main {

      public static void main(String[] args){

       MyThread3 th=new MyThread3();

       //使用Lambda表达式创建Callable对象

         //使用FutureTask类来包装Callable对象

       FutureTask<Integer> future=new FutureTask<Integer>(

        (Callable<Integer>)()->{

          return 5;

        }

        );

       new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程

        try{

        System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回

        }catch(Exception e){

        ex.printStackTrace();

       }

      }

    }

    ------------------------使用线程池例如用Executor框架---------------------

    1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。

        Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

        Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

        ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

        Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。   

        public static ExecutorService newFixedThreadPool(int nThreads)

        创建固定数目线程的线程池。

        public static ExecutorService newCachedThreadPool()

        创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线   程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

        public static ExecutorService newSingleThreadExecutor()

        创建一个单线程化的Executor。

        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

        创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

        这四种方法都是用的Executors中的ThreadFactory建立的线程,下面就以上四个方法做个比较




    newCachedThreadPool()                                                                                                                                         

    -缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中
    -缓存型池子通常用于执行一些生存期很短的异步型任务
     因此在一些面向连接的daemon型SERVER中用得不多。但对于生存期短的异步任务,它是Executor的首选。
    -能reuse的线程,必须是timeout IDLE内的池中线程,缺省     timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
      注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。
     


    newFixedThreadPool(int)                                                      

    -newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
    -其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待直到当前的线程中某个线程终止直接被移出池子
    -和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
    -从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过参数不同:
    fixed池线程数固定,并且是0秒IDLE(无IDLE)    
    cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE  

     
    newScheduledThreadPool(int)
    -调度型线程池
    -这个池子里的线程可以按schedule依次delay执行,或周期执行
     
    SingleThreadExecutor()
    -单例线程,任意时间池中只能有一个线程
    -用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)

     

        一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool。(该段话摘自《Thinking in Java》第四版)

                             

    Executor执行Runnable任务

        通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上

    [java] view pl
     
    import java.util.concurrent.ExecutorService;   
    import java.util.concurrent.Executors;   
      
    public class TestCachedThreadPool{   
        public static void main(String[] args){   
            ExecutorService executorService = Executors.newCachedThreadPool();   
    //      ExecutorService executorService = Executors.newFixedThreadPool(5);  
    //      ExecutorService executorService = Executors.newSingleThreadExecutor();  
            for (int i = 0; i < 5; i++){   
                executorService.execute(new TestRunnable());   
                System.out.println("************* a" + i + " *************");   
            }   
            executorService.shutdown();   
        }   
    }   
      
    class TestRunnable implements Runnable{   
        public void run(){   
            System.out.println(Thread.currentThread().getName() + "线程被调用了。");   
        }   
    }  

       某次执行后的结果如下:

       从结果中可以看出,pool-1-thread-1和pool-1-thread-2均被调用了两次,这是随机的,execute会首先在线程池中选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务。

    Executor执行Callable任务

        在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable<T> task) 方法来执行,并且返回一个 <T>Future<T>,是表示任务等待完成的 Future。

        Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。

        当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

        下面给出一个Executor执行Callable任务的示例代码:

    import java.util.ArrayList;   
    import java.util.List;   
    import java.util.concurrent.*;   
      
    public class CallableDemo{   
        public static void main(String[] args){   
            ExecutorService executorService = Executors.newCachedThreadPool();   
            List<Future<String>> resultList = new ArrayList<Future<String>>();   
      
            //创建10个任务并执行   
            for (int i = 0; i < 10; i++){   
                //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中   
                Future<String> future = executorService.submit(new TaskWithResult(i));   
                //将任务执行结果存储到List中   
                resultList.add(future);   
            }   
      
            //遍历任务的结果   
            for (Future<String> fs : resultList){   
                    try{   
                        while(!fs.isDone);//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
                        System.out.println(fs.get());     //打印各个线程(任务)执行的结果   
                    }catch(InterruptedException e){   
                        e.printStackTrace();   
                    }catch(ExecutionException e){   
                        e.printStackTrace();   
                    }finally{   
                        //启动一次顺序关闭,执行以前提交的任务,但不接受新任务  
                        executorService.shutdown();   
                    }   
            }   
        }   
    }   
      
      
    class TaskWithResult implements Callable<String>{   
        private int id;   
      
        public TaskWithResult(int id){   
            this.id = id;   
        }   
      
        /**  
         * 任务的具体过程,一旦任务传给ExecutorService的submit方法, 
         * 则该方法自动在一个线程上执行 
         */   
        public String call() throws Exception {  
            System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());   
            //该返回结果将被Future的get方法得到  
            return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();   
        }   
    }  

        某次执行结果如下:

       

        从结果中可以同样可以看出,submit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。

    自定义线程池

        自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池,用该类很容易实现自定义的线程池,这里先贴上示例程序:

    import java.util.concurrent.ArrayBlockingQueue;   
    import java.util.concurrent.BlockingQueue;   
    import java.util.concurrent.ThreadPoolExecutor;   
    import java.util.concurrent.TimeUnit;   
      
    public class ThreadPoolTest{   
        public static void main(String[] args){   
            //创建等待队列   
            BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);   
            //创建线程池,池中保存的线程数为3,允许的最大线程数为5  
            ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);   
            //创建七个任务   
            Runnable t1 = new MyThread();   
            Runnable t2 = new MyThread();   
            Runnable t3 = new MyThread();   
            Runnable t4 = new MyThread();   
            Runnable t5 = new MyThread();   
            Runnable t6 = new MyThread();   
            Runnable t7 = new MyThread();   
            //每个任务会在一个线程上执行  
            pool.execute(t1);   
            pool.execute(t2);   
            pool.execute(t3);   
            pool.execute(t4);   
            pool.execute(t5);   
            pool.execute(t6);   
            pool.execute(t7);   
            //关闭线程池   
            pool.shutdown();   
        }   
    }   
      
    class MyThread implements Runnable{   
        @Override   
        public void run(){   
            System.out.println(Thread.currentThread().getName() + "正在执行。。。");   
            try{   
                Thread.sleep(100);   
            }catch(InterruptedException e){   
                e.printStackTrace();   
            }   
        }   
    }  

      运行结果如下:

        从结果中可以看出,七个任务是在线程池的三个线程上执行的。这里简要说明下用到的ThreadPoolExecuror类的构造方法中各个参数的含义。   

    public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long         keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)

    corePoolSize:线程池中所保存的线程数,包括空闲线程。

    maximumPoolSize:池中允许的最大线程数。

    keepAliveTime:当线程数大于核心数时,该参数为所有的任务终止前,多余的空闲线程等待新任务的最长时间。

    unit:等待时间的单位。

    workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。

    --------------------------------------四种创建线程方法对比--------------------------------------

    实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

    1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

    2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

    4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

    5、前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池

    注:在前三种中一般推荐采用实现接口的方式来创建多线程

    创建线程本质只有1种,即创建Thread类,以上的所谓创建方式其实是实现run方法的方式:

    实现run方法的方式分为两类,你所看到的其他的都是对这两类的封装:

    1、实现runnable接口的run方法,并把runnable实例作为target对象,传给thread类,最终调用target.run

    2、继承Thread类,重写Thread的run方法,Thread.start会执行run方法

    展开全文
  • 创建线程几种方式

    1 用Runnable方式创建线程 

    /**
     * 用Runnable方式创建线程
     */
    public class RunnableStyle implements Runnable{
        @Override
        public void run() {
            System.out.println("用Runnable方法实现线程");
        }
        public static void main(String[] args) {
            Thread thread = new Thread(new RunnableStyle());
            thread.start();
        }
    }

    2 用Thread方式实现线程 

    /**
     *  用Thread方式实现线程
     */
    public class ThreadStyle extends Thread{
        @Override
        public void run() {
            System.out.println("用Thread类实现线程");
        }
        public static void main(String[] args) {
            new ThreadStyle().start();
        }
    }

    思考:同时使用Runnable和Thread两种实现线程的方式

     继承Thread会直接覆盖run方法,传递进去的Runnable是得不到执行的

    /**
     * 同时使用Runnable和Thread两种实现线程的方式
     */
    public class BothRunnableThread {
        public static void main(String[] args) {
            new Thread(new Runnable() {
                //重写覆盖了Runnable里面的run方法 这个run方法是得不到执行的
                @Override
                public void run() {
                    System.out.println("我来自Runnable");
                }
            }) {
                @Override
                public void run() {
                    System.out.println("我来自Thread");
                }
            }.start();
        }
    }

     

    展开全文
  • java创建线程的三种方式: 继承Thread类创建线程类 通过Runable接口创建线程类 通过Callable和FutureTask创建线程 总结下: 实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值。 1...

    java创建线程的三种方式:

    1. 继承Thread类创建线程类
    2. 通过Runable接口创建线程类
    3. 通过Callable和FutureTask创建线程

    总结下:
    实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值。
    1、如果需要访问当前线程,必须调用Thread.currentThread()方法。
    2、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。
    注:一般推荐采用实现接口的方式来创建多线程

    参考代码:MyThread RunnableThreadTest CallableThreadTest ExecutorServiceTest

    //继承Thread类
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		  System.out.println(getName());    
    	}
    	
    	public static void main(String[] args) {
    		for(int i=0;i<10;i++){
    			new MyThread().start();
    		}
    		
    		//  内部类 方式
    		new Thread(){
    			@Override
    			public void run() {
    				for (int x = 0; x < 10; x++) {
    					System.out.println(Thread.currentThread().getName() + ":" + x);
    				}
    			}
    		}.start();
    	}
        
    }
    
    // 实现runnable接口
    public class RunnableThreadTest implements Runnable {
    
    	public void run() {
    		System.out.println("run:" + Thread.currentThread().getName());
    	}
    
    	public static void main(String[] args) {
    		
    		System.out.println("main:"+ Thread.currentThread().getName());
    		RunnableThreadTest rt = new RunnableThreadTest();
    		new Thread(rt, "线程1").start();
    
    		// 用内部类的方式创建线程,实现runnable接口
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				for (int x = 0; x < 10; x++) {
    					System.out.println(Thread.currentThread().getName() + ":" + x);
    				}
    			}
    		}).start();
    	}
    
    }
    
    /**
     *  实现Callable接口
     *  1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
    	2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
    	3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
    	4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
     *
     */
    public class CallableThreadTest implements Callable<Integer> {
    
    	public static void main(String[] args) {
    		CallableThreadTest ctt = new CallableThreadTest();
    		FutureTask<Integer> ft = new FutureTask<Integer>(ctt);
    		System.out.println(Thread.currentThread().getName() + "===== ");
    		
    		new Thread(ft, "有返回值的线程").start();
    		try {
    			System.out.println("子线程的返回值:" + ft.get());
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} catch (ExecutionException e) {
    			e.printStackTrace();
    		}
    	}
    
    	@Override
    	public Integer call() throws Exception {
    		for (int i=0; i < 10; i++) {
    			System.out.println(Thread.currentThread().getName() + " " + i);
    		}
    		return 20;
    	}
    
    }
    
    
    // 用线程池的方式创建线程,并获得返回值
    // Java线程池结合Future、Callable获得线程返回结果
    public class ExecutorServiceTest {
    
    	private final int NUMBER = 4;
    
    	public void ExecutorService() {
    		// 创建容量为NUMBER的线程池。
    		ExecutorService pool = Executors.newFixedThreadPool(NUMBER);
    		// 接收返回值结果
    		ArrayList<Future<String>> futures = new ArrayList<Future<String>>();
    
    		for (int i = 0; i < 10; i++) {
    			// 自己的业务线程,实际工作中改成对应的业务逻辑
    			AThread t = new AThread(i);
    			Future<String> f = pool.submit(t);
    			futures.add(f);
    		}
    
    		System.out.println("....开始获取结果中...");
    		// Future的get方法在获取结果时候将进入阻塞,阻塞直到Callable中的call返回。
    		for (Future<String> f : futures) {
    			try {
    				// if(f.isDone())
    				System.out.println(f.get());
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    		System.out.println(".....结束得到结果....");
    
    		// 关闭线程池。
    		pool.shutdown();
    	}
    
    	public static void main(String[] args) {
    		new ExecutorServiceTest().ExecutorService();
    	}
    
    	// 业务线程
    	private class AThread implements Callable<String> {
    
    		// 可做为参数传过来
    		private int id;
    
    		public AThread(int id){
    			this.id = id;
    		}
    
    		@Override
    		public String call() {
    			// 执行相应的逻辑
    			System.out.println("线程:" + id + " 运行..");
    			try {
    				Thread.sleep(3000);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    
    			System.out.println("线程:" + id + " -> 结束.");
    			return "返回的字符串" + id;
    		}
    	}
    }
    
    展开全文
  • 这是最新的大厂面试系列,还原真实场景,提炼出知识点分享给大家。 点赞再看,养成习惯~ 微信搜索【武哥聊编程】,关注这个 Java 菜鸟。...Java 创建线程有两种方式: 继承Thread类,并重写run(...

    这是最新的大厂面试系列,还原真实场景,提炼出知识点分享给大家。
    点赞再看,养成习惯~ 微信搜索【武哥聊编程】,关注这个 Java 菜鸟。

    昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的问题:你说一说 Java 创建线程都有哪些方式?

    这哥们心中窃喜,这个老生常谈的问题早已背的滚瓜烂熟,于是很流利的说了出来。

    Java 创建线程有两种方式:

    1. 继承Thread类,并重写run()方法
    2. 实现Runnable接口,覆盖接口中的run()方法,并把Runnable接口的实现扔给Thread

    面试官:(拿出一张白纸)那你把这两种方式写一下吧!

    小哥:(这有何难!)好~

    public static void main(String[] args) {
    	// 第一种
        MyThread myThread = new MyThread();
        myThread.start();
    	// 第二种
        new Thread(() -> System.out.println("自己实现的run-2")).start();
    }
    
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("自己实现的run-1");
        }
    }
    

    面试官:嗯,那除了这两种,还有其他创建线程的方法吗?

    这些简单的问题难不倒这哥们,于是他想到了 Java5 之后的ExecutorsExecutors工具类可以用来创建线程池。

    小哥:Executors工具类是用来创建线程池的,这个线程池可以指定线程个数,也可以不指定,也可以指定定时器的线程池,它有如下常用的方法:

    newFixedThreadPool(int nThreads):创建固定数量的线程池
    newCachedThreadPool():创建缓存线程池
    newSingleThreadExecutor():创建单个线程
    newScheduledThreadPool(int corePoolSize):创建定时器线程池

    面试官:嗯,OK,咱们还是针对你刚刚写的代码,我再问你个问题。

    此时这哥们有种不祥的预感,是不是自己代码写的有点问题?或者要问我底层实现?

    面试官:你写的两种创建线程的方式,都涉及到了run()方法,你了解过Thread里的run()方法具体是怎么实现的吗?

    果然问到了源码了,这哥们之前看了点,所以不是很慌,回忆了一下,向面试官道来。

    小哥:emm……Thread 中的run()方法里东西很少,就一个 if 判断:

    @Override
    public void run() {
    	if (target != null) {
    		target.run();
    	}
    }
    

    有个target对象,会去判断该变量是否为空,非空的时候,去执行target对象中的run()方法,否则啥也不干。而这个target对象,就是我们说的Runnable

    /* What will be run. */
    private Runnable target;
    

    面试官:嗯,那这个Runnable类你了解过吗?
    小哥:了解过,这个Runnable类很简单,就一个抽象方法:

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    

    这个抽象方法也是run()!如果我们使用Runnable接口,就需要实现这个run()方法。由于这个Runnable类上面标了@FunctionalInterface注解,所以可以使用函数式编程。

    那小哥接着说:(突然自信起来了)所以这就对应了刚才说的两种创建线程的方式,假如我用第一种方式:继承了Thread类,然后重写了run()方法,那么它就不会去执行上面这个默认的run()方法了(即不会去判断target),会执行我重写的run()方法逻辑。

    假如我是用的第二种方式:实现Runnable接口的方式,那么它会执行默认的run()方法,然后判断target不为空,再去执行我在Runnable接口中实现的run()方法。

    面试官:OK,可以,我再问你个问题。

    小哥:(暗自窃喜)

    面试官:那如果我既继承了Thread类,同时我又实现了Runnable接口,比如这样,最后会打印什么信息出来呢?

    面试官边说边拿起刚刚这小哥写的代码,对它进行了简单的修改:

    public static void main(String[] args) {
        new Thread(() -> System.out.println("runnable run")) {
            @Override
            public void run() {
                System.out.println("Thread run");
            }
        }.start();
    }
    

    这小哥,突然有点懵,好像从来没想过这个问题,一时没有什么思路,于是回答了个:会打印 “Thread run” 吧……

    面试官:答案是对的,但是为什么呢?

    这小哥一时没想到原因,于是面试官让他回去可以思考思考,就继续下一个问题了。

    亲爱的读者朋友,你们知道为什么吗?你们可以先思考一下。

    其实这个答案很简单,我们来分析一下代码便知:其实是 new 了一个对象(子对象)继承了Thread对象(父对象),在子对象里重写了父类的run()方法;然后父对象里面扔了个Runnable进去,父对象中的run()方法就是最初那个带有 if 判断的run()方法。

    好了,现在执行start()后,肯定先在子类中找run()方法,找到了,父类的run()方法自然就被干掉了,所以会打印出:Thread run。

    如果我们现在假设子类中没有重写run()方法,那么必然要去父类找run()方法,父类的run()方法中就得判断是否有Runnable传进来,现在有一个,所以执行Runnable中的run()方法,那么就会打印:Runnable run 出来。

    说白了,就是问了个 Java 语言本身的父子继承关系,会优先执行子类重写的方法而已,只是借这个场景,换了个提问的方式,面试者可能一时没反应过来,有点懵也是正常的,如果直接问,傻子都能回答的出来。

    后记:通过这道简单的面试题,帮大家分析了一下在创建线程过程中的源码,可以看出来,面试过程中,面试官更加看重一些原理性的东西,而不是背一下方式就行了。同时也能看的出,面试大厂,需要做好充分的准备。另外,在面试的时候要冷静,可能有些问题并没有太难,回答不出来只是当时太紧张造成的。

    这篇文章就写到这,最后祝大家都能面试成功。

    如果觉得有帮助,希望老铁们来个三连击,给更多的人看到这篇文章

    1、关注我的原创微信公众号「武哥聊编程」,专注于Java、数据结构和算法、微服务、中间件等技术分享,保证你看完有所收获。

    2、给俺点个赞呗,可以让更多的人看到这篇文章,顺便激励下我继续写作,嘻嘻。

    作者info

    【作者】:武哥
    【公众号】:武哥聊编程。欢迎大家关注~
    【作者简介】:同济大学,硕士。先后在华为、科大讯飞、拼多多采坑。一个自学 Java 的菜鸟,期待你的关注。

    点赞是对我最大的鼓励
    ↓↓↓↓↓↓

    展开全文
  • java 创建线程的三种方式、创建线程池的四种方式

    万次阅读 多人点赞 2019-02-23 21:01:44
    java创建线程的三种方式: 继承Thread类创建线程类 实现Runnable接口 通过Callable和Future创建线程 java创建线程池的四种方式: newCachedThreadPool 创建一个可缓存的线程池,如果线程池长度超过处理...
  • 创建线程几种不同方式

    千次阅读 2017-04-20 10:45:26
    ③使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。 调用线程的start():启动此线程;调用相应的run()方法 继承于Thread类的线程类,可以直接调用start方法启动线程(使用static也可以实现...
  • 12.创建线程几种不同方式?你喜欢哪一种?为什么?    有三种方式可以用来创建线程:    继承 Thread 类 实现 Runnable 接口 应用程序可以使用 Executor 框架来创建线程池 实现 Runnable 接口这种方式更受...
  • 创建线程几种方式

    千次阅读 2018-08-27 15:48:35
    一般有四方法,Thread,Runnable,Callable,使用Executor框架来创建线程池。 Runnable和Callable的区别是, (1)Callable规定的方法是call(),Runnable规定的方法是run(). (2)Callable的任务执行后可返回值,而...
  • 详解Java创建线程几种方式

    千次阅读 2017-10-19 20:14:30
    在Java线程中,有以下三种方式可以实现线程创建。 通过继承Thread类,调用Thread的start方法实现线程创建 实现Runnable接口 实现Callable接口 继承Thread可以实现线程创建,但当我们需要继承其他父类的时候...
  • 二、创建线程几种方式

    千次阅读 2016-10-19 20:45:40
    一、使用Thread创建线并启动线程java.lang.Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。其中重写run方法的目的是定义该线程要执行的逻辑。...
  • 创建并启动线程几种方式

    千次阅读 2017-12-14 23:26:12
    一:创建线程几种方式 1.第一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法,然后在run方法里填写相应的逻辑代码。class ThreadDemo1 extends Thread{ @Override public void run() {...
  • std::thread 创建线程几种方式

    千次阅读 2019-04-18 08:22:54
    本文主要介绍标准C++中 thread的创建线程几种方式。 使用时需要加头文件: #include <thread> 位于std命名空间中,是跨平台的线程操作 使用说明 1、通过函数指针创建 一般来说,像CreateThread、_...
  • 有三种方式: ①继承Thread类(真正意义上的线程类),是Runnable接口的实现。 ②实现Runnable接口,并重写里面的run方法。 ③使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。调用线程的start...
  • 线程几种实现方式

    万次阅读 2019-02-25 15:33:38
    java多线程的几种实现方式: 1.继承Thread类,重写run方法 2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target 3.通过Callable和FutureTask创建线程 4.通过线程池...
  • Java 创建线程的三种方式总结

    万次阅读 2020-12-03 11:13:19
    那么创建线程几种方式呢? 1、继承 Thread 类 直接继承 Thread 即可实现多线程。Thread 本质上也是实现了 Runnable 接口,线程启动的唯一方法是通过 Thread 类的 start() 实例方法实现的。start() 方法调用时,...
  • 3,使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。 调用线程的start():启动此线程;调用相应的run()方法 继承于Thread类的线程类,可以直接调用start方法启动线程(使用sta...
  • c++多线程创建几种方式

    千次阅读 2017-05-23 12:11:27
    c++创建线程几种方法(入门)
  • 如何创建线程创建线程几种方法?怎么实现的?
  • java中创建线程的4种方式

    千次阅读 2019-04-25 16:00:23
    java线程创建方式几种?这种问题在面试中经常被问到,你可能心里马上反映出两种方式(实现Runnable、继承Thread),当你把这两种叙述给面试官听后,面试官会觉得你该掌握的知识已经有了,但是仅仅而已。如果你还说...
  • 创建线程有哪几种方式?3种

    千次阅读 2020-07-20 08:17:10
    实现Runnable接口 public class RunnableDemoTest implements Runnable{ public void run() { System.out.println("实现Runnable开启线程!"); } public static void main(String[] args) { Thread thread = new ...
  • C++11 std::thread创建线程几种写法

    万次阅读 2020-12-06 14:00:10
    本文主要介绍标准C++中 thread的创建线程几种方式。 使用时需要加头文件:#include <thread> 位于std命名空间中,是跨平台的线程操作。 2 使用说明 1、通过函数指针创建 一般来说,像linux下pthread_create...
  • 对于第一种创建线程方式有两个不足:1.当前线程重写run方法定义该线程要完成的工作,这就导致了任务是定义在线程内部的,于是线程与任务有一个强耦合关系,不利于线程的重用。2.由于java是单继承的,这就导致了若...
  • 在数据处理中,多线程用到的场景很多,在满足计算机CPU处理...我自己总结了一下,可以有两种方式,使用线程池和spring自带多线程注解使用。 使用线程池 我一般使用固定线程数量的线程池,假如数据量很大,我会将...
  • java创建线程的三种方式及其对比

    万次阅读 多人点赞 2014-11-14 22:14:15
    Java中创建线程主要有三种方式: 一、继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。 (2)创建Thread子类的实例...
  • C#几种创建线程方式

    千次阅读 2014-08-21 14:35:21
    using System; using System.Threading; namespace MutiThreadSample {  ///  /// 创建线程方式  ///  class CreateThread  {  ///  /// 不带参数的委托  ///  

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 548,332
精华内容 219,332
关键字:

创建线程的几种不同方式