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

    万次阅读 多人点赞 2018-03-30 14:34:14
    java中创建线程的四种方法以及区别 Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示: 1)继承Thread类创建线程 2)实现Runnable接口创建线程...

    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 static class MyThread3 implements Callable{
    
        @Override
        public Object call() throws Exception {
            return 5;
        }
    }

    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(future,"有返回值的线程").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,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执行,或周期执行
     
    newSingleThreadExecutor()
    -单例线程,任意时间池中只能有一个线程
    -用的是和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之多线程创建

    千次阅读 2019-03-20 19:10:31
    Java多线程归纳前言传统的线程创建继承Thread类实现Runnable接口两者的共同点两者差别JDK 1.5开始出现的线程创建 前言 进程是资源分配的最小单位,而线程是执行任务的最小的单位。进程里的线程可以共享该进程拥有的...


    前言

    进程是资源分配的最小单位,而线程是执行任务的最小的单位。进程里的线程可以共享该进程拥有的资源,而进程之间不可以,进程奔溃了,在保护模式下,不会影响其他进程,而线程一旦出问题了,该进程也要GG思密达。总而言之,创建一个线程比创建一个进程要省事得多儿,这也是当今为了充分利用cpu性能,处理大规模并发时常用的伎俩之一。


    传统的线程创建

    Java中创建线程的方式有四种,具体方式如下:

    继承Thread类

    在这里插入图片描述
    在这里插入图片描述

    实现Runnable接口

    在这里插入图片描述
    在这里插入图片描述


    两者的共同点

    无论如何创建线程,通过以上的运行,我们可以发现,线程的运行不是根据代码的顺序来执行的,而是随机的,不是说先开启就先运行,有时有些线程可能还设有优先级。像上面的运行结果,main主线程的优先级就明显比创建的线程的优先级高,1总是先被打印出来。

    它们都需要通过start()方法进行开启,没有开启,就直接运行run()方法,跟我们平常调用其他方法一样,没有任何差别,谁先调用谁就先执行,没有随机的说法。这个点也是面试容易遇到的地方,给你个创建线程的程序,让你说出运行结果,此时要注意是否有先调用start方法。

    两者都有一个公共的缺陷:在执行完任务之后如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,因为在源码中,Runnable接口申明的run方法就是void类型的,没有返回值。但在jdk1.5之后,引入的callable和future解决了这个问题。


    两者差别

    通过实现Runnable接口创建的线程要比继承Thread这种方式灵活得多。其实查看源码,我们可以知道,Thread类其实也是继承于Runnable接口。之所以这样处理,无非为了解决Java类的单继承问题,因为继承了Thread类,当有其他类也需要继承时,就很无奈了。另外,从设计模式的角度来说,这种依赖抽象的东西本身就要比依赖细节稳定的多。


    附 jdk1.8 API Thread的构造方法:
    在这里插入图片描述


    JDK 1.5开始出现的线程创建

    在jdk1.5时,开始增加了两种线程创建的办法即Callable和Future,它们弥补了上面所说的以往创建线程方式的公共缺陷。在执行完任务后,通过这两种方式,我们可以获得返回结果。

    场景模拟:现在有一个项目,它划分成A模块和B模块,它们可以同时进行开发,最后组装起来就行,采用Thread和Runnable方式:

    package com.fang.pratice;
    
    public class WorkThread {
        public static void main(String[] args) {
            A threadA = new A();
            threadA.start();
            
            B threadB = new B();
            threadB.start();
            
            try {
                //让父线程等待子线程结束之后才能继续运行
                threadA.join();
                threadB.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            add(threadA,threadB);
        }
        private static void add(A a, B b){
            System.out.println("合并完成");
        }
        static class A extends Thread{
            @Override
            public void run() {
               System.out.println("正在完成A");
               try {
                Thread.sleep(3000);//完成A需要1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
               System.out.println("A完成");
            }
        }
        static class B extends Thread{
            @Override
            public void run() {
               System.out.println("正在完成B");
               try {
                Thread.sleep(2000);//完成A需要2000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
               System.out.println("B完成");
            }
        }
    }
    

    在这里插入图片描述


    通过Callable和FutureTask创建线程

    package com.fang.pratice;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.TimeoutException;
    
    public class WorkThreadByCallable {
        public static void main(String[] args) {
            Callable<Boolean> threadA = new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    System.out.println("正在完成A");
                    Thread.sleep(3000);//完成A需要3000毫秒
                    System.out.println("A完成");
                    return true;
                }
            };
            Callable<Boolean> threadB = new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    System.out.println("正在完成B");
                    Thread.sleep(1000);//完成A需要1000毫秒
                    System.out.println("B完成");
                    return true;
                }
            };
            //创建一个 FutureTask ,它将在运行时执行给定的 Callable
            FutureTask<Boolean> taskA = new FutureTask<Boolean>(threadA);
            FutureTask<Boolean> taskB = new FutureTask<Boolean>(threadB);
            
            new Thread(taskA).start();
            new Thread(taskB).start();
           /**
            * 判断任务A线程和任务B线程是否完成
            * 其实在主线程这里判断,感觉不太合适,因为主线程优先级较高,程序运行后会先执行
            * 但出于学习吧,能了解方法的含义和用途就ok吧
            */
          if(!taskA.isDone()||!taskB.isDone()){
                System.out.println("A或B还没好,此时可已调用cancel方法取消任务");
           }
            try {
                /**
                 * 获得任务A和B的返回值,如果A和B还没完成,会等着
                                    但不会一直等着,通过源码我们可以知道,当任务还未完成时
                                   调用了awaitDone方法进行等待
                    get方法也可以设置超时时间,第一参数为超时时间
                                  第二个参数为时间单位,这里指定1s会抛出超时异常
                **/
                try {
                    if(taskA.get(1,TimeUnit.SECONDS)&&taskB.get()){
                        add(threadA,threadB);
                    }
                } catch (TimeoutException e) {
                    taskA.cancel(true);//取消线程的任务
                    System.out.println("任务超时");
                  // e.printStackTrace();
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }        
        }
        private static void add(Callable<Boolean> threadA, Callable<Boolean> threadB) {
            // TODO Auto-generated method stub  
        }
    }
    

    callable其实就是一接口,跟Runnable差不多,只不多有返回值,它需要有一容器来包装它,就如上线的FutureTask那样,当然,你也可以和线程池一起搭配使用。


    通过线程池创建线程

    前面说的创建线程的方法,都是任务请求时,创建线程(新建),然后经过就绪、运行、阻塞这若干个状态(不是指都会进行,也可能只有运行),最后殊途同归,都会死亡。这显然给服务器带来了更多的压力,因为频繁的创建、销毁线程,不仅浪费资源还造成时间上的延迟。阿里巴巴Java开发规范手册就做了明确的规定:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

    即通过线程池,我们可以统管理线程,如:设置程序创建线程的阈值、核心线程的数量(下面有解释),超时时间,从而可以实现线程的复用。

    线程池的种类很多,这里以ThreadPoolExecutor为例子,在开始一个例子前,说一下它构造方法,几个参数的含义吧:

    • corePoolSize:该线程池中核心线程数最大值,就是线程池中至少应该有多少线程,这些线程会一直待着,不会随便就消失。
    • maximumPoolSize 该线程池中线程总数的最大值,核心线程不够用时,就需要非核心线程了,只要小于等于线程最大值,就可以创建。
    • keepAliveTime :该线程池中非核心线程闲置超时时长,非核心的线程在执行任务后,不会立即消失,可通过设置这个值来决定它存活的时间。
    • TimeUnit unit :时间单位,可指定时间的单位
    • BlockingQueue<Runnable> workQueue,阻塞的队列,线程都在工作时,新加入的队列在这里排队

    在这里插入图片描述
    除此之外,他还有三个构造函数:

    1. 第二构造函数增加了 ThreadFactory threadFactory 参数:线程工厂,你可以指定创建线程的方式,这是一个接口,new它的时候需要实现他接口中的Thread newThread(Runnable r)方法
      在这里插入图片描述
    2. 第三个构造函数在第一个构造函数的基础上增加的是RejectedExecutionHandler handler参数:当线程无法执行新任务时,决定怎样处理,如线程池中的线程数量已经达到最大值、线程池关闭导致的,默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException,因为源码中有一个内部类,为默认这个东西
      在这里插入图片描述
    3. 第四个构造参数无非就增加了第二个和第三个构造函数所增加的属性。

    代码中已经给出注释了,但还是有一点要说一下,ThreadPoolExecutor提供的execute(Runnable run)方法,参数是Runnable类型的,不过它继承了AbstractExecutorService,它提供的submit(Callabletask) 方法可以提交Callable类型的任务(相当于start),并且可以返回代表任务待处理结果的Future。要是觉得麻烦,也可以改成实现Runnable接口

    package com.fang.pratice;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class TestExecutor {
        public static void main(String[] args) { 
            /**
             * 对照源码
             * corePoolSize,int,第一个参数,该线程池中核心线程数最大值
             *  maximumPoolSize ,int,第二个参数,该线程池中线程总数的最大值
             *  keepAliveTime ,long, 该线程池中非核心线程闲置超时时长
             *  TimeUnit unit  ,时间单位,可指定时间的单位
             *  BlockingQueue<Runnable> workQueue,阻塞的队列,线程都在工作时,
             *  新加入的队列在这里排队
             */
            ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 200, TimeUnit.MILLISECONDS,
                    new ArrayBlockingQueue<Runnable>(2));
             
            for(int i=0;i<5;i++){
                ThreadTest myTask = new ThreadTest(i);
                executor.submit(myTask);
                System.out.println("池中当前的线程数:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
                executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
            }
            executor.shutdown();
        }
    
    }
    class ThreadTest implements Callable<Integer>{
        @SuppressWarnings("unused")
        private int name;
        public ThreadTest() {}
        
        public ThreadTest(int i) {
            name = i;
        }
    
        @Override
        public Integer call() throws Exception {
            // TODO Auto-generated method stub
            return null;
        }
        
    }
    

    关系图

    列出了一些常见的,并不代表所有:
    在这里插入图片描述
    线程池,可能有人直接通过一个线程池的大工厂Executors来创建具体的线程池工厂,但阿里巴巴Java开发规范手册就指出了这种方式的弊端。
    在这里插入图片描述

    展开全文
  • C++多线程创建

    千次阅读 2018-06-03 21:06:47
    C++多线程创建
     
    //多线程
    #include<thread>
    void MultiThread()
    { 
        for(int i = 0; i < 10; i++) 
        {  
            printf("Thread1.....\n");
        } 
    }
    void main()
    { 
        std::thread th(MultiThread);
        
        printf("Main....\n");
    }
    直接执行后,出错:

    原因:
        由于在创建了线程后线程才开始执行,但是主线程并没有停止,仍然执行并退出,线程仍然存在,但是指向他的线程对象销毁,导致该异常!!
     
    解决方法:

     

     

        保证子线程执行完并退出后,主线程才能退出;

     

     

    1、thread::join()

    使用join()接口可以解决上述问题,该接口的作用是,让主线程等待,知道子线程执行结束:
    void main()
    {
    	std::thread th(MultiThread);
    	th.join();
    	printf("Main....\n");
    
    }

     

    这样就能正常执行了;
    注意:
        同一个线程只能join()一次,因为join()一次后,就不再joinable了,thread的 joinable标志会设置成false
     
     
    2、thread::detach()
            上面1中提到的问题,detach函数可以解决,该函数的作用是用来和线程对象分离的,这样线程可以独立执行,不过这样会导致没有thread对象指向该线程而导致该线程的失控,当对象析构时线程会继续在后台执行,但是主程序退出是并不能保证该线程能执行完。
     
    3、mutex
            头文件<mutex>,是用来保证线程的同步的,防止不同的线程同时操作一个共享数据;
            
    int num = 0;
    
    std::mutex  Lock;
    //多线程
    void MultiThread1()
    {
    	for(int i = 0; i < 100; i++)
    	{
    		Lock.lock();
    		num++;
    		Lock.unlock();
    		printf("Thread1.....,num:[%d]\n",num);
    	}
    	
    
    }
    void MultiThread2()
    {
    	for(int i = 0; i < 100; i++)
    	{
    		Lock.lock();
    		num++;
    		Lock.unlock();
    		printf("Thread2.....,num:[%d]\n", num);
    	}
    	
    
    }
    
    void main()
    {
    	std::thread th1(MultiThread1);
    	std::thread th2(MultiThread2);
    
    	th1.join();
    	th2.join();
    
    	printf("Main....\n");
    	std::system("pause");
    }

    但是使用mutex是不安全的,当一个线程在解锁之前异常退出的时候,那其他线程就会阻塞

     
    4、std::lock_guard
            lock_guard相比于上面是相对安全的,因为 lock_guard是基于作用域的,该对象创建的时候,会获取互斥锁,当生命周期结束的时候他会自动析构(解锁),不会因为某个线程的异常退出而阻塞其他线程;
     
    int num = 0;
    
    std::mutex  Lock;
    //多线程
    void MultiThread1()
    {
    	for(int i = 0; i < 100; i++)
    	{
    		std::lock_guard<std::mutex> guard_lock1(Lock);
    		num++;
    		printf("Thread1.....,num:[%d]\n",num);
    	}
    	
    
    }
    void MultiThread2()
    {
    
    	for(int i = 0; i < 100; i++)
    	{
    		std::lock_guard<std::mutex> guard_lock2(Lock);
    		num++;
    		printf("Thread2.....,num:[%d]\n", num);
    	}
    	
    
    }
    
    void main()
    {
    	std::thread th1(MultiThread1);
    	std::thread th2(MultiThread2);
    
    	th1.join();
    	th2.join();
    
    	printf("Main....\n");
    	std::system("pause");
    }

    5、原子变量

    由于加锁会导致性能问题,因此,可以使用原子变量:

    #include<thread>
    #include<atomic>
    
    int LoopNum = 50000000;
    std::atomic_int num={0};
    std::mutex Lock;//多线程
    
    void MultiThread1()
    {
        for(int i = 0; i < LoopNum; i++)
        {
            //std::lock_guard<std::mutex> guard_lock1(Lock);
            num++;
        }
    }
    
    void MultiThread2()
    {
        for(int i = 0; i < LoopNum; i++)
        {
            //std::lock_guard<std::mutex> guard_lock2(Lock);
            num++;
        }
    }
    
    void main()
    {
        std::thread th1(MultiThread1);
        std::thread th2(MultiThread2);
        th1.join();
        th2.join();
        printf("Num:[%d]\n",num);
        //printf("Main....\n");
        std::system("pause");
    }

     

    展开全文
  • Linux下的线程创建

    万次阅读 2019-04-14 12:29:30
    文章目录线程创建例子另一个例子 线程创建 在传统Unix进程模型中,每个进程只有一个控制线程。在POSIX线程(pthread)的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的。在创建多个控制线程以前,程序...

    线程创建

    在传统Unix进程模型中,每个进程只有一个控制线程。在POSIX线程(pthread)的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的。在创建多个控制线程以前,程序的行为与传统的进程并没有什么区别。新增的线程可以通过调用pthread_create函数创建。

    #include <pthread.h>
    
    int pthread_create(pthread_t *restrict tidp,
    							const pthread_attr_t *restrict attr, 
    							void *(*start_rtn)(void *), void *restrict arg);
    

    说明:当pthread_create成功返回时,新创建线程的线程ID会被设置成tidp指向的内存单元。attr参数用于定制各种不同的线程属性,目前设置为NULL,创建一个具有默认属性的线程。
    新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要想start_rtn函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

    例子

    创建一个线程,打印进程ID、新线程的线程ID以及初始线程的ID。

    //gcc threadid.c -o a.out -pthread
    //pthread是linux下的线程库,用了多线程就要链接这个库,这时候要在编译选项上增加-pthread
    #include "apue.h"
    #include <pthread.h>
    #include <apueerror.h>
    
    pthread_t ntid;
    
    void
    printids(const char *s)
    {
    	//声明进程id
    	pid_t		pid;
    	//声明线程id
    	pthread_t	tid;
    	//获取进程id
    	pid = getpid();
    	//用pthread_self()获取自己线程id
    	tid = pthread_self();
    	printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
    	  (unsigned long)tid, (unsigned long)tid);
    }
    
    void *
    thr_fn(void *arg)
    {
    	//调用上面的打印id函数
    	printids("new thread: ");
    	return((void *)0);
    }
    
    int
    main(void)
    {
    	int		err;
    
    	//创建线程,主线程把新线程ID存放在ntid中,新线程去执行thr_fn函数
    	err = pthread_create(&ntid, NULL, thr_fn, NULL);
    	if (err != 0)
    		err_exit(err, "can't create thread");
    	printids("main thread:");
    	sleep(1);
    	exit(0);
    }
    

    编译: gcc threadid.c -o a.out -pthread
    note:pthread是linux下的线程库,用了多线程就要链接这个库,这时候要在编译选项上增加-pthread

    main thread: pid 1518 tid 140627397551936 (0x7fe65e13a740)
    new thread:  pid 1518 tid 140627389191936 (0x7fe65d941700)
    

    可以见到,两个线程地址是不一样的,但是pid父进程都是一样的。

    另一个例子

    主线程接受一个输入num,创建一个线程打印2*num。

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <apueerror.h>
    
    // 返回值必须是void *(无类型指针),参数也必须是void *
    void *tfn(void *arg)
    {
        //传进来的是(void *),强制类型转换
        int num = (int)arg;
        long d2num = 2*num;
        printf("In thread %lu arg: %d.\n", (unsigned long)pthread_self(), num);
        sleep(1);
        printf("Thread over! arg: %d\n", num);
        return  (void*)d2num;
    }
    
    void main(int argc, char *argv[])
    {
        pthread_t tid;
        long num;
        void *tret; //获取线程终止状态
        while(scanf("%ld", &num) == 1){
            pthread_create(&tid, NULL, tfn, (void *)num);
            //线程终止
            pthread_join(tid, &tret);
            printf("Thread exit code: %ld\n", (long)tret);
        }
    
        printf("Main thread %lu is over.\n", (unsigned long)pthread_self());
    }
    

    因为在Unix环境高级编程的源代码里代码都是使用makefile链接的,小弟不懂,所以直接将代码copy到已有的文件名中跑了。
    下面是结果:
    在这里插入图片描述
    可以看到结果:创建的线程和主线程id号是不一样的。
    另外:我觉的在
    pthread_create(&tid, NULL, tfn, (void *)num);
    直接将num转为指针有点别扭,于是这样:
    pthread_create(&tid, NULL, tfn, (void *)&num);
    然后在tfn函数中将:
    int num = (int)arg;
    改为:
    int num = *(int*)arg;
    也是可以的。

    展开全文
  • 一个线程创建另一个线程

    千次阅读 2017-02-20 17:39:54
    线程是不分父子线程的,一个线程创建另一个线程,尽管第一个线程已经结束,第二个线程还是可以照常运行
  • 多线程~~简单的线程创建,C语言实现

    千次阅读 2017-08-14 21:28:21
    原文地址:多线程~~简单的线程创建,C语言实现 - XD - CSDN博客  http://m.blog.csdn.net/qq_25425023/article/details/45251771 多线程~~简单的线程创建,C语言实现 发表于2015/4/24 21:30:58 11225人阅读...
  • 线程创建Thread和Runnable

    千次阅读 2017-03-21 15:43:41
    1.线程创建 (1)通过继承Thread类来创建线程,在执行时new MyThread() .start() (2)通过实现Runnable接口来创建线程,在执行时 MyThread mt=new MyThread(); new Thread(mt).start(); 2.Thread和Runnabel两种创建...
  • 易语言大漠多线程创建线程

    千次阅读 2020-05-30 09:22:19
    进程(process)和线程(thread)是操作系统的基本概念 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,...一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行 第一课易语言大
  •  第一课易语言大漠多线程创建线程    小知识:进程和线程的理解 每个进程 系统都会分配资源给这个进程 这个进程对应的所有线程 这些线程都会有一点独立的资源占用 用来存放自己独有的资源 但是更多的是利用进程...
  • windows线程创建

    千次阅读 2018-02-07 16:20:23
    windows系统创建线程: (当然MFC、QT也有对应创建线程的接口函数) 1、使用windows API CreateThread创建线程: (不建议使用该接口创建线程)具体原因:...
  • MFC之AfxbeginThread 线程 创建、挂起、释放、结束、退出 本文简单阐述了如何使用一个afxbeginthread创建一个线程(两种方法,使用默认设置,使用自主设置参数),以及一些如同,挂起,释放。边界锁等操作。 ①....
  • Linux:线程&线程创建&线程终止

    千次阅读 多人点赞 2018-04-08 23:55:34
    线程 1.是操作系统能够进行调度的...比如四个人在一个房子里打麻将,创建一个新进程就相当于是在开一个房间,而创建一个新线程是在原有的房间中增加一个人而已 线程的优点 1.创建一个新的线程的代价要比...
  • 例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才...
  • 关于时间,创建线程使用是直接向系统申请资源的,这里调用系统函数进行分配资源的话耗时不好说。 关于资源,Java线程线程栈所占用的内存是在Java堆外的,所以是不受java程序控制的,只受系统资源限制,默认一个...
  • 线程对象创建后,即进入新建状态,如:Thread t = new MyThread(); 就绪状态(Runnable) 当调用线程对象的start()方法时,线程即进入就绪状态。处于就绪状态的线程只是说明此线程已经做好准备,随时等待CPU调度...
  • 【C#】进程中最大线程创建数量分析

    千次阅读 2016-01-04 13:44:41
    【C#】进程中最大线程创建数量分析作者:凡星 QQ:184167125 一、 简介 多线程在当今软件开发中应用非常广泛,在实际开发过程中,发现很多不正确使用线程的现象,其中一点就是线程使用过于随意,导致进程中的线程...
  • 本文介绍了Java 中你所不知的线程创建的第三种方法。 线程存在的目的是位了在计算机中,在单位时间内(相对于人眼能够识别的速度)完成对个任务。在java中是为了在编写程序是能够并发(同时执行多个程序,或者一个...
  • C++多线程并发(一)--- 线程创建与管理

    千次阅读 多人点赞 2020-03-16 22:21:32
    文章目录 前言 一、何为并发 1.1 并发与并行 1.2 硬件并发与任务切换 1.3 多线程并发与多进程并发 二、如何使用并发 2.1 为什么使用并发 2.2 在C++中使用并发和多线程 三、C++线程创建 3.1 C++11新标准多线程支持库 ...
  • 回调监控进线程创建和退出  两个注册回调的函数:PsSetCreateProcessNotifyRoutine 进程回调PsSetCreateThreadNotifyRoutine 线程回调分别对应的回调函数类型: VOID MyCreateProcessNotify ( IN HANDLE ...
  • 线程启动用到的API函数CreateThread,该函数在主线程的基础上创建一个新线程线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。易语言创建线程挂起线程恢复线程销毁线程 511...
  • 线程创建后,未必立即运行

    千次阅读 2012-06-21 17:38:00
    线程创建,并不立即执行,而是等时间片到来后再执行。    一个进程,包含多个线程。  则这个线程共享进程的数据等资源,各个线程呈现并发执行状态。  线程执行时,所传入的参数值是执行时才传入的,而不是...
  • c++ 线程创建 参数传递

    千次阅读 2018-03-17 14:49:39
    在C语言中,我们使用pthread_create创建线程线程函数是一个全局函数,所以在C++中,创建线程时,也应该使用一个全局函数。static定义的类的成员函数就是一个全局函数。 C++线程使用的注意点 1.多个参数的传递...
  • linux线程创建和销毁

    千次阅读 2018-05-26 17:56:38
    创建线程 int ptread_create(pthread_t *thread, const pthread_att_t *attr, void * (*start)(void *), void *arg) 返回值 参数 thread pthread_t 类型的缓冲区,在创建函数返回前,会在此保存一个该线程...
  • 简单的多线程创建,执行,挂起,终止的例子 创建两个线程,创建时即进入阻塞状态,然后根据主线程的input值,去唤醒或阻塞线程执行。 //简单的多线程创建,执行,挂起,终止的例子 //利用win32 API #include #...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,530,591
精华内容 612,236
关键字:

线程的创建