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

    万次阅读 多人点赞 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方法

    展开全文
  • java创建线程两种方式及区别

    千次阅读 2018-07-24 18:35:47
    本文将介绍创建线程两种方式,示例代码在下面,复制粘贴即可 继承Thread类方式和实现Runnable接口方式 区别:由于在开发的过程中,很多的类都会用到继承的方式,如果采用继承的方式会让各个类之间的关系变得错综...

    本文将介绍创建线程的两种方式,示例代码在下面,复制粘贴即可

    继承Thread类方式和实现Runnable接口方式

    区别:由于在开发的过程中,很多的类都会用到继承的方式,如果采用继承的方式会让各个类之间的关系变得错综复杂

    使得程序的可读性变差,所以在开发当中一般都用实现接口的方式,而且实现接口的方式还可以共享资源,共享资源的

    示例将在以后列出。

    /*
     * 创建线程的第一种方式:继承方式
     * 1.定义线程类,继承Thread类
     * 2.覆盖其中的run方法,并且run方法得用public修饰,否则系统会报错
     * 3.在主函数中创建线程对象
     * 4.调用线程对象的start方法
     */
    class xiancheng extends Thread//第一步
    {
        public void run() //第二步
        {
            for(int x=0;x<60;x++) 
            {
                System.out.println("1");
            }
        }
    }
    
    public class Test1 {
    
        public static void main(String[] args) 
        {
            xiancheng t=new xiancheng();//第三步
            t.start();//第四步
            for(int x=0;x<500;x++) 
            {
                System.out.println("2");
            }
        }
    
    }
    /*
     * 创建线程的第二种方式:实现接口
     * 1.定义线程类并实现Runnable接口
     * 2.覆盖Runnable中的run方法
     * 3.创建Runnable的子类线程对象
     * 4.将创建的子类对象作为参数传给Thread类的构造函数中
     * 5.调用start方法开启线程
     */
    class xiancheng1 implements Runnable//第一步
    {
        public void run() //第二步
        {
            for(int x=0;x<60;x++) 
            {
                System.out.println("1");
            }
        }
    }
    public class Test2 {
    
        public static void main(String[] args) 
        {
            xiancheng1 t=new xiancheng1();//第三步
            Thread t1=new Thread(t);//第四步
            t1.start();//第五步
            for(int x=0;x<500;x++) //主线程
            {
                System.out.println("2");
            }
        }
    
    }
    
    

     

    展开全文
  • 创建线程两种方式的比较

    千次阅读 2010-08-05 14:21:00
    那么这两种实现多线程方式在应用上有什么区别呢? 为了回答这个问题,我们可以通过编写一段代码来进行分析。我们用代码来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程...

    通过铁路售票程序来理解实现多线程的两种方法:通过java.lang.Thread类和通过Runnable接口
      java中有两种实现多线程的方式。一是直接继承Thread类,二是实现Runnable接口。那么这两种实现多线程的方式在应用上有什么区别呢?
      为了回答这个问题,我们可以通过编写一段代码来进行分析。我们用代码来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程表示。
      我们首先这样编写这个程序:

    上面的代码中,我们用ThreadTest类模拟售票处的售票过程,run方法中的每一次循环都将总票数减1,模拟卖出一张车票,同时该车票号打印出来,直接剩余的票数到零为止。在ThreadDemo1类的main方法中,我们创建了一个线程对象,并重复启动四次,希望通过这种方式产生四个线程。从运行的结果来看我们发现其实只有一个线程在运行,这个结果告诉我们:一个线程对象只能启动一个线程,无论你调用多少遍start()方法,结果只有一个线程。
      我们接着修改ThreadDemo1,在main方法中创建四个Thread对象:

    这下达到目的了吗?
      从结果上看每个票号都被打印了四次,即四个线程各自卖各自的100张票,而不去卖共同的100张票。这种情况是怎么造成的呢?我们需要的是,多个线程去处理同一个资源,一个资源只能对应一个对象,在上面的程序中,我们创建了四个ThreadTest对象,就等于创建了四个资源,每个资源都有100张票,每个线程都在独自处理各自的资源。
      经过这些实验和分析,可以总结出,要实现这个铁路售票程序,我们只能创建一个资源对象,但要创建多个线程去处理同一个资源对象,并且每个线程上所运行的是相同的程序代码。在回顾一下使用接口编写多线程的过程。

    上面的程序中,创建了四个线程,每个线程调用的是同一个ThreadTest对象中的run()方法,访问的是同一个对象中的变量(tickets)的实例,这个程序满足了我们的需求。在Windows上可以启动多个记事本程序一样,也就是多个进程使用同一个记事本程序代码。
      可见,实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
      (1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
      (2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
      (3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象时,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。

    展开全文
  • 多线程之创建线程两种方式

    万次阅读 多人点赞 2018-05-29 00:33:56
    彩蛋: ...言归正传,今晚准备详细记录一下线程创建两种方式以及这两种方式的优缺点及适应的情况等等。 多进程:。。。 多线程:在一个应用程序中,有多个顺序流同时执行。(线程:顺序执行的代...

    彩蛋:

    朕是小白,朕近段时间准备学一下有关线程方面的知识。(下面写的东西是我对线程的一些理解)

    今天是2018年5月28号,朕准备每天更新一篇博客来激励自己一直学习,不要间断,毕竟是位又懒自制力又差的家伙,哈哈哈!

    言归正传,今晚准备详细记录一下线程创建的两种方式以及这两种方式的优缺点及适应的情况等等。

    多进程:。。。

    多线程:在一个应用程序中,有多个顺序流同时执行。(线程:顺序执行的代码)

    创建线程有两种方式:

    一,(目标类来)继承Thread类,重写run方法来创建自己的线程。调用start函数就开始执行线程代码昂发啦!

    从Thread类派生一个子类,并创建子类的对象。

    子类应该重写Thread类的run方法,写入需要在新线程中执行的语句段。

    调用start方法来启动新线程,自动进入run方法。

    举例:在新线程中实现计算某个整数的阶乘的过程:

    package multi_thread;
    
    public class FactorialThreadTester {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		System.out.println("main thread starts");
    		FactorialThread thread = new FactorialThread(10);
    		thread.start();
    		System.out.println("main thread ends");
    	}
    }
    
    class FactorialThread extends Thread{
    	private int num;
    	public FactorialThread(int num) {
    		this.num = num;
    	}
    	public void run() {
    		int i = num;
    		int result = 1;
    		System.out.println("new thread started");
    		while(i>0) {
    			result*=i;
    			i--;
    		}
    		System.out.println("The factorial of "+num+" is "+result);
    		System.out.println("new thread ends");
    	}
    }

     

    运行结果:

     

    备注:factorial(阶乘)
    main thread starts
    main thread ends
    new thread started
    The factorial of 10 is 3628800
    new thread ends
    

    注:这里之所以要另外写一个主线程(public static void main(String[] args))是为了想说明创建的新线程并不是start()后就立即执行的。它其实也需要CPU来进行调度,轮到它了它才会被执行。通过运行结果我们可以看出。

    通过继承Thread类来创建线程大致流程就是上面这样。

    下面我想补充一些个人感觉很有用的知识(如果急着看另一种创建线程的方式,请往下翻~~~):

    Thread类常用的API方法,整理如下:

     

    名称 说明
    public Thread() 构造一个新的线程对象(Thread类的构造函数),默认名为Thread-n,n是从0开始递增的整数
    public Thread(Runnable target) 构造一个新的线程对象,以一个实现Runnable接口的类的对象为参数,默认名为Thread-n,n是从0开始递增的整数(哈哈哈,从此处构造函数可以看出创建线程的两种不同方式,处处点题,我真机智!)
    public Thread(String name) 构造一个线程对象,并同时指定线程名
    public static Thread currentThread() 返回一个当前正在运行的线程对象
    public static void yield() 使当前线程对象暂停,允许别的线程开始运行(注:yield:屈服)
    public static void sleep(long millis) 使当前线程暂停运行指定毫秒数,但此线程并不失去已获得的锁
    public void start() 启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run方法的线程
    public void run() Thread的子类应该重写此方法,内容应为该线程应执行的任务
    public final void stop() 停止线程运行,释放该线程占用的对象锁
    public void interrupt() 中断此线程
    public final void join() 如果启动了线程A,调用join方法将等待线程A死亡才能继续执行当前线程
    public final void join(long millis) 如果此前启动了线程A,调用join方法将等待指定毫秒数或线程A死亡才能继续执行当前线程
    设置线程优先级
    public final void setPriority( int newPriority) 设置线程优先级
    public final void setDaemon(Boolean on) 设置是否为后台线程,如果当前运行线程均为后台线程则JVM停止运行。这个方法必须在start()方法前使用(注:daemon:后台程序)
    public final void checkAccess() 判断当前线程是否有权力修改调用此方法的线程
    public void setName(String name) 更改本线程的名称为指定参数
    public final boolean isAlive() 测试线程是否处于活动状态,如果线程被启动并且没有死亡则返回true

     

    二,写一个来实现Runnable接口,在初始化一个Thread类或者Thread子类的线程对象的时候 ,把该类的对象作为参数传递给那个线程对象。(其中由该类提供run方法)。

     

    Runnable接口:只有一个run()方法

    以实现Runnable接口的类的对象为参数建立新的线程

    还是举这个例子:在新线程中实现计算某个整数的阶乘的过程:

    package multi_thread;
    
    public class FactorialThreadTest_Runnable {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		System.out.println("main thread starts");
    		FactorialThreadRunnable t = new FactorialThreadRunnable(10);
    		new Thread(t).start();
    		System.out.println("new thread started, main thread ends");
    	}
    }
    
    class FactorialThreadRunnable implements Runnable{
    	private int num;
    	public FactorialThreadRunnable(int num) {
    		this.num = num;
    	}
    	public void run() {
    		int i = num;
    		int result = 1;
    		while(i>0) {
    			result*=i;
    			i--;
    		}
    		System.out.println("The factorial of "+num+" is "+result);
    		System.out.println("new thread ends");
    	}
    }

     

    运行结果:

     

    main thread starts
    new thread started, main thread ends
    The factorial of 10 is 3628800
    new thread ends
    

    再举一个使用Runnable接口实现上面多线程的例子:

    package multi_thread;
    
    public class ThreadSleepTester_Runnable {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		TestThread_Runnable thread1 = new TestThread_Runnable();
    		TestThread_Runnable thread2 = new TestThread_Runnable();
    		TestThread_Runnable thread3 = new TestThread_Runnable();
    		System.out.println("Starting threads");
    		new Thread(thread1,"Thread1").start();
    		new Thread(thread2,"Thread2").start();
    		new Thread(thread3,"Thread3").start();
    		System.out.println("Threads started,main ends");
    	}
    
    }
    
    class TestThread_Runnable implements Runnable{
    	private int sleepTime;
    	public TestThread_Runnable() {
    		sleepTime=(int)(Math.random()*6000);
    	}
    	public void run() {
    		try {
    			System.out.println(Thread.currentThread().getName()+" going to sleep for "+sleepTime);
    			Thread.sleep(sleepTime);
    		}
    		catch(InterruptedException e){};
    		System.out.println(Thread.currentThread().getName()+" finished");
    	}
    }

    运行结果:

    Starting threads
    Threads started,main ends
    Thread1 going to sleep for 5924
    Thread3 going to sleep for 2385
    Thread2 going to sleep for 2112
    Thread2 finished
    Thread3 finished
    Thread1 finished
    

    通过实现Runnable接口来创建线程大致流程就是上面这样。

     

    接下来我们要思考一个问题:创建线程的两种方式各有其优缺点:

    直接继承Thread类:

    优点:编写简单,直接继承,重写run方法。

    缺点:由于Java的单继承机制,不能再继承其他类了

    使用Runnable接口:

    优点有两个:一,可以继承其他类。Java不支持多继承,如果已经继承了某个基类,便需要实现Runnable接口来生成多线程。

    二,可以将CPU,代码和数据分开,形成清晰的模型,便于多个线程共享资源。

    其他的优缺点应该很好理解,但对于第二个(二,可以将CPU,代码和数据分开,形成清晰的模型,便于多个线程共享资源)优点,还是举个例子来说明比较好!

    通过实现Runnable接口为啥多个线程就可以共享数据等资源呢???

    package multi_thread;
    
    public class ShareTargetTester {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		TestThread_Runnable thread = new TestThread_Runnable();
    		System.out.println("Starting threads");
    		new Thread(thread,"Thread1").start();
    		new Thread(thread,"Thread2").start();
    		new Thread(thread,"Thread3").start();
    		System.out.println("Threads started,main ends");
    	}
    
    }
    class TestThread_Runnable implements Runnable{
    	private int sleepTime;
    	public TestThread_Runnable() {
    		sleepTime=(int)(Math.random()*6000);
    	}
    	public void run() {
    		try {
    			System.out.println(Thread.currentThread().getName()+" going to sleep for "+sleepTime);
    			Thread.sleep(sleepTime);
    		}
    		catch(InterruptedException e){};
    		System.out.println(Thread.currentThread().getName()+" finished");
    	}
    }

    运行结果:

    Starting threads
    Threads started,main ends
    Thread1 going to sleep for 4422
    Thread2 going to sleep for 4422
    Thread3 going to sleep for 4422
    Thread2 finished
    Thread1 finished
    Thread3 finished
    

    Thread1、Thread2和Thread3同时结束。(你运行一下看看就知道啦!)

    这里为啥Thread1、Thread2和Thread3休眠时间是一样的呢?好奇怪!(emmmm,寡人还是不故弄玄虚了。。。)

    原因是:你创建3个新线程传给Thread的参数是一毛一样滴,所以那3个新线程就共享了一个对象里的数据(sleepTime)啊,所以就是一样的!

    		TestThread_Runnable thread = new TestThread_Runnable();
    		new Thread(thread,"Thread1").start();
    		new Thread(thread,"Thread2").start();
    		new Thread(thread,"Thread3").start();

    扶朕起来,朕还可以再举一个例子:

    用三个线程模拟三个售票口,总共出售10张票。(注:这三个线程应该共享10张票的数据,因为总共就10张票,三个进程一起来销售)

    package multi_thread;
    
    public class sellTicketsTester {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		sellTickets t = new sellTickets();
    		new Thread(t,"Thread1").start();
    		new Thread(t,"Thread2").start();
    		new Thread(t,"Thread3").start();
    		
    	}
    }
    
    class sellTickets implements Runnable{
    	private int tickets = 10;
    	public void run() {
    		while(tickets>0) {
    			System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
    		}
    	}
    }

    运行结果:

    Thread1 is selling ticket 10
    Thread3 is selling ticket 8
    Thread2 is selling ticket 9
    Thread3 is selling ticket 6
    Thread3 is selling ticket 4
    Thread3 is selling ticket 3
    Thread3 is selling ticket 2
    Thread3 is selling ticket 1
    Thread1 is selling ticket 7
    Thread2 is selling ticket 5
    

    哈哈哈,看完运行结果你就明白我滴意思了吧!

    在这个例子中,创建了3 个线程,每个线程调用的是同一个sellTickets对象t中的run()方法,访问的也是同一个对象t中的变量tickets。

    但是,如果是通过创建Thread类的子类来模拟售票过程,再创建3个新线程,则每个线程都有各自的方法和变量,虽然方法是相同的,但确是各有10张票,结果就会是每个线程都卖出了10张票,总共卖出了30张票,与原意就不符了!!!

    (个人感觉线程间的数据共享挺重要的,也挺有意思的)

    但是,上面代码运行多次后,会发现也会出现一些问题,比如某一个线程卖出第0张票的情况,比如两个不同的线程卖出同一张票的情况等等……没有保证线程的安全,具体见下:

    public class sellTicketsTester {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            sellTickets t = new sellTickets();
            new Thread(t,"Thread1").start();
            new Thread(t,"Thread2").start();
            new Thread(t,"Thread3").start();
            new Thread(t,"Thread4").start();
            new Thread(t,"Thread5").start();
            new Thread(t,"Thread6").start();
            new Thread(t,"Thread7").start();
    
        }
    }
    
    class sellTickets implements Runnable{
        private int tickets = 10;
        public void run() {
            while(tickets>0) {
                System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
            }
        }
    }

    运行结果见下:

    Thread2 is selling ticket 10
    Thread2 is selling ticket 9
    Thread2 is selling ticket 8
    Thread2 is selling ticket 7
    Thread2 is selling ticket 6
    Thread2 is selling ticket 5
    Thread2 is selling ticket 4
    Thread2 is selling ticket 3
    Thread2 is selling ticket 2
    Thread2 is selling ticket 1
    Thread1 is selling ticket 0
    Thread3 is selling ticket 10
    Thread1 is selling ticket 10
    Thread2 is selling ticket 9
    Thread1 is selling ticket 7
    Thread3 is selling ticket 8
    Thread1 is selling ticket 5
    Thread2 is selling ticket 6
    Thread1 is selling ticket 3
    Thread3 is selling ticket 4
    Thread1 is selling ticket 1
    Thread2 is selling ticket 2

     为了保证线程安全,应该使用同步方法。

    1、在run方法前面加synchronized关键字

    public class sellTicketsTester {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            sellTickets t = new sellTickets();
            new Thread(t,"Thread1").start();
            new Thread(t,"Thread2").start();
            new Thread(t,"Thread3").start();
            new Thread(t,"Thread4").start();
            new Thread(t,"Thread5").start();
            new Thread(t,"Thread6").start();
            new Thread(t,"Thread7").start();
    
        }
    }
    
    class sellTickets implements Runnable{
        private int tickets = 10;
        public synchronized void run() {
            while(tickets>0) {
                System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
            }
        }
    }

    2、同步代码块

    public class sellTicketsTester {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            sellTickets t = new sellTickets();
            new Thread(t,"Thread1").start();
            new Thread(t,"Thread2").start();
            new Thread(t,"Thread3").start();
            new Thread(t,"Thread4").start();
            new Thread(t,"Thread5").start();
            new Thread(t,"Thread6").start();
            new Thread(t,"Thread7").start();
    
        }
    }
    
    class sellTickets implements Runnable{
        private int tickets = 10;
        public  void run() {
            synchronized (this){
                while(tickets>0) {
                    System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
                }
            }
        }
    }

    我还想补充一下线程有关的其他知识:

    一、线程休眠:(调用sleep方法)

    比如之前写的程序:

    package multi_thread;
    
    public class FactorialThreadTester {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		System.out.println("main thread starts");
    		FactorialThread thread = new FactorialThread(10);
    		thread.start();
    		System.out.println("main thread ends");
    	}
    }
    
    class FactorialThread extends Thread{
    	private int num;
    	public FactorialThread(int num) {
    		this.num = num;
    	}
    	public void run() {
    		int i = num;
    		int result = 1;
    		System.out.println("new thread started");
    		while(i>0) {
    			result*=i;
    			i--;
    		}
    		System.out.println("The factorial of "+num+" is "+result);
    		System.out.println("new thread ends");
    	}
    }
    

    运行结果:

     

    备注:factorial(阶乘)
    main thread starts
    main thread ends
    new thread started
    The factorial of 10 is 3628800
    new thread ends

     

    现在我让主线程休眠一毫秒,运行结果就会发生改变了哦,因为主线程休眠了一毫秒!

     

    package multi_thread;
    
    public class FactorialThreadTester {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		System.out.println("main thread starts");
    		FactorialThread thread = new FactorialThread(10);
    		thread.start();
    		try { Thread.sleep(1);}
    		catch(Exception e) {};
    		System.out.println("main thread ends");
    	}
    }
    
    class FactorialThread extends Thread{
    	private int num;
    	public FactorialThread(int num) {
    		this.num = num;
    	}
    	public void run() {
    		int i = num;
    		int result = 1;
    		System.out.println("new thread started");
    		while(i>0) {
    			result*=i;
    			i--;
    		}
    		System.out.println("The factorial of "+num+" is "+result);
    		System.out.println("new thread ends");
    	}
    }

    与上一个程序不同的是我只添加了红色标记的语句,运行结果见下:

    main thread starts
    new thread started
    The factorial of 10 is 3628800
    new thread ends
    main thread ends

    新线程结束后main线程才结束。

    多个线程休眠的情况见下:

    package multi_thread;
    
    public class ThreadSleepTester {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		TestThread thread1 = new TestThread("Thread1");
    		TestThread thread2 = new TestThread("Thread2");
    		TestThread thread3 = new TestThread("Thread3");
    		System.out.println("Starting threads");
    		thread1.start();
    		thread2.start();
    		thread3.start();
    		System.out.println("Threads started,main ends");
    	}
    }
    	
    	class TestThread extends Thread{
    		private int sleepTime;
    		public TestThread(String name) {
    			super(name);
    			sleepTime=(int)(Math.random()*6000);
    		}
    		public void run() {
    			try {
    				System.out.println(getName()+" going to sleep for "+sleepTime);
    				Thread.sleep(sleepTime);
    			}
    			catch(InterruptedException e){};
    			System.out.println(getName()+" finished");
    		}
    }

    备注:Math.random()产生0~1之间的随机数。

    Thread.sleep(sleepTime),其中sleepTime的单位是毫秒!

    运行结果见下:

    Starting threads
    Threads started,main ends
    Thread1 going to sleep for 5492
    Thread2 going to sleep for 1295
    Thread3 going to sleep for 3126
    Thread2 finished
    Thread3 finished
    Thread1 finished

     

    由于线程1休眠时间最长,所以最后结束;线程2休眠时间最短,所以最先结束。

     

    每次运行,都会随机产生不同的休眠时间,所以其实每次运行的结果都不相同!

    结尾也有彩蛋,嘻嘻:

    eclipse变量名如何实现一改全改呢?(个人感觉还是很有用的小技巧)

    双击选择要改的变量名,右键下拉菜单选择refactor重构选项,选择rename,然后就输入自己需要改成的名字,回车,搞定!

     

    展开全文
  • 创建线程两种方式区别

    千次阅读 2019-02-24 12:54:54
    其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法: ◆需要从Java.lang.Thread类派生一个新的线程类,...
  • java 创建线程的三种方式、创建线程池的四种方式

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

    万次阅读 2017-02-26 17:32:48
    今天小钱就给大家介绍使用匿名内部类创建线程,为什么要使用匿名内部类呢,因为使用它代码更简洁也更方便,同样地,它也有两种方式:下面我们来看第一种使用匿名内部类创建线程方式: 1.首先我们要在一个类中new...
  • 传统的线程技术中有两种创建线程方式:一是继承Thread类,并重写run()方法;二是实现Runnable接口,覆盖接口中的run()方法,并把Runnable接口的实现扔给Thread。这两种方式大部分人可能都知道,但是为什么这样玩就...
  • 创建线程两种方式Thread与Runnable 一、简要说明 创建线程两种方式,一是继承Thread类,二是实现Runnable接口,最后都是依据Thread类的构造方法实例化出一个线程对象,调用线程对象的start()方法,就可以通知...
  • Android创建线程两种方式及比较

    万次阅读 2018-06-05 21:06:43
    一、Android创建线程方式通常为以下两种:  Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个...
  • 两种创建线程方式

    千次阅读 2020-11-20 11:37:38
    重写run方法,创建线程对象,调用static开启线程 子类继承Thread具备多线程能力 启动线程:子类对象.static 不建议使用:避免OOP单继承局限性 package com.zl.thread; /* 多线程:创造多个..
  • java创建线程常用的两种方式

    千次阅读 2019-01-29 14:47:32
    一、继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代 表了线程要完成的任务。因此把run()方法称为执行体。 (2)创建Thread子类的实例,即创建了线程对象。 (3)调用...
  • 创建线程常用的两种方式方式一: 创建线程子类(DemoThread1)继承Thread类,重写run()方法,调用start()开启线程。不建议使用:不能避免单继承得局限性。 public class TestThread1 extends Thread { ...
  • Qt中创建线程两种方式

    千次阅读 2018-08-22 09:50:12
    3.在主线程中创建线程 对象,启动子线程,调用start()函数   方法二:  1.将业务处理抽象成一个业务类,在该类中创建一个业务处理函数  2.在主线程中创建一个QThread类对象  3.在主线程中创建一个业务类...
  • 创建线程两种传统方式

    万次阅读 2019-04-09 08:43:34
    1. 在Thread子类覆盖的run方法中编写运行代码 Thread thread = new Thread() { @Override public void run() { //希望代码长期运行下去就编写在... //线程睡眠 Thread.sleep(500); } c...
  • 发送邮件emailName:593036108@qq.comcontext:邮件内容
  • 创建和启动线程两种方式

    万次阅读 多人点赞 2016-10-07 10:52:48
    方式1:继承Thread类 步骤: 1):定义一个类A继承于java.lang.Thread类. 2):在A类中覆盖Thread类中的run方法. ...4):在main方法(线程)中,创建线程对象,并启动线程. (1)创建线程类对象: A类 a = new A类();
  • Java中线程创建两种方式

    千次阅读 2016-06-02 11:15:19
    Java中线程创建两种方式:   1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类    在实际应用中,我们经常用到多线程,如车站的...
  • Qt 线程创建两种方式

    千次阅读 2018-08-30 10:38:26
    方法一:继承QThread老式方法 ...start()启动线程,线程会自动调用run()虚函数;run不可直接调用; #ifndef MYTHREAD_H #define MYTHREAD_H #include &lt;QThread&gt; class myThread: p...
  • Python中线程创建两种方式

    千次阅读 2017-09-05 12:50:49
    from threading import Thread import time def test(): print("线程开启了~!") time.sleep(1) # num = 0 for i in range(5): ... #创建现成的第一种方式 thr=Thread(target=test) thr.start() #
  • Java语言提供了并发机制,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程。一个线程是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程也可以...
  • 第一种方式:继承Thread类 步骤:1、定义类继承Thread 2、覆写Thread类的run方法。 自定义代码放在run方法中,让线程运行 3、调用线程的star方法, 该线程个作用:启动线程,调用run方法。 代码示例: ...
  • 线程创建两种方式和区别

    千次阅读 2012-12-31 14:58:32
    创建线程的一种方式是:继承Thread类,并复写类中run方法 最后调用线程的start方法, 该方法启动线程并调用run方法(即和模板方法类似) getName();获取线程的名称 static Thread currentThread()为获取当前线程对象 ...
  • java中创建线程的4种方式

    千次阅读 2019-04-25 16:00:23
    这种问题在面试中经常被问到,你可能心里马上反映出两种方式(实现Runnable、继承Thread),当你把这两种叙述给面试官听后,面试官会觉得你该掌握的知识已经有了,但是仅仅而已。如果你还说了callable与future、...
  • 1、首先来了解一下线程和进程的概念  进程的概念:  进程是操作系统资源管理的最小单位,进程是一个动态的实体,他是程序的一次执行过程。也叫作一个控制单元  线程的概念:  线程是进程中独立的控制单元,线程...
  • /** * 窗口卖票经典实例,第一继承Thread类多线程进行解决 * * * 需求:四个窗口同时卖票。... * 怎么创建线程? * 1.继承Thread类,并且重写run方法。 * 2.将卖票的动作定义到run方法当中。 * */ pac...
  • Java创建线程种方式: 1. 继承Thread类; 2. 实现Runnable接口; 3. 实现Callable接口,实例化FutureTask类; 4. 创建线程池(以下用Executor框架实现) 说明:这四种方式都潜移默化都用到了Thread类(表示线程),...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 807,839
精华内容 323,135
关键字:

创建线程的两种方式是什么