精华内容
下载资源
问答
  • 1.线程的概念与实现方式 1.1 线程的概念 概念:线程是进程内部的一条执行...用户自己创建的多线程,即多个处理路径,无法使用多处理器的资源,在内核眼里就只是一条路径。 (2)内核级线程:由内核直接创建、直接管理

    1.线程的概念与实现方式

    1.1 线程的概念

    概念:线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。线程是进行资源调度和分配的基本单位 。
    (1)每个进程至少有一条执行路径,所以一个进程至少有一个线程。
    (2)每个进程都有一个主线程。

    在这里插入图片描述

    1.2 线程的实现方式

    在操作系统中,线程的实现有以下三种方式:
    (1)用户级线程:由线程库中的代码进行管理,处理 ,销毁。用户自己创建的多线程,即多个处理路径,无法使用多处理器的资源,在内核眼里就只是一条路径。
    (2)内核级线程:由内核直接创建、直接管理、直接调度,直接结束。开销大,可以利用处理器的资源。
    (3)组合级线程:内核空间允许其使用多处理器的资源。比如用户创建多个线程,内核可以创建两个线程来处理这些线程,以达到可以有效使用处理器资源的目的。
    在这里插入图片描述

    1.3Linux 中线程的实现

    Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)。

    1.4 进程与线程的区别

    (1)进程是资源分配的最小单位,线程是 CPU 调度的最小单位。
    (2)进程有自己的独立地址空间,线程共享进程中的地址空间。
    (3)进程的创建消耗资源大,线程的创建相对较小。
    (4)进程的切换开销大,线程的切换开销相对较小。

    2.线程基本使用

    2.1 线程库中的接口介绍

    #include <pthread.h>
    
    /*
    pthread_create()用于创建线程
    thread: 接收创建的线程的 ID
    attr: 指定线程的属性//一般传NULL
    start_routine:指定线程函数
    arg: 给线程函数传递的参数
    成功返回 0, 失败返回错误码
    */
    int pthread_create(pthread_t * thread, const pthread_attr_t *attr,void *(*start_routine) ( void *),void *arg);
    
    
    
    /*
    pthread_exit()退出线程
    retval:指定退出信息
    */
    int pthread_exit( void *retval);
    
    
    
    /*
    pthread_join()等待 thread 指定的线程退出,线程未退出时,该方法阻塞
    retval:接收 thread 线程退出时,指定的退出信息
    */
    int pthread_join(pthread_t thread, void **retval);
    

    2.2 多线程代码

    如下简单写一个多线程代码:

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<assert.h>
    #include<pthread.h>
    
    void* pthread_fun(void* arg)
    {
        for(int i = 0; i < 5; i++)
        {
            printf("fun run\n");
            sleep(1);
        }
    }
    
    int main()
    {
    
        pthread_t tid;
        int res = pthread_create(&tid,NULL,pthread_fun,NULL);
        assert(res == 0);
    
        for(int i = 0; i < 10; i++)
        {
            printf("main run\n");
            sleep(1);
        }
    
        exit(0);
    }
    
    

    运行结果(注意编译链接需要带上库 -lpthread):
    在这里插入图片描述
    如果子线程循环10次,主线程循环5次呢?代码及运行结果如下:

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<assert.h>
    #include<pthread.h>
    
    void* pthread_fun(void* arg)
    {
        for(int i = 0; i < 10; i++)
        {
            printf("fun run\n");
            sleep(1);
        }
    }
    
    int main()
    {
    
        pthread_t tid;
        int res = pthread_create(&tid,NULL,pthread_fun,NULL);
        assert(res == 0);
    
        for(int i = 0; i < 5; i++)
        {
            printf("main run\n");
            sleep(1);
        }
    
        exit(0);
    }
    

    运行结果:
    在这里插入图片描述
    发现主线程结束后,子线程并没有打印完也紧跟着结束了。
    所以,主线程不会因为其他线程的结束而结束,但是其它线程的结束会因为主线程的结束而结束。这是因为主线程结束后会退出进程,所以进程里的其他线程都会终止结束。所以为了正常运行程序,一般我们都会让主线程等待其他线程结束后再结束。代码如下:

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<assert.h>
    #include<pthread.h>
    
    void* pthread_fun(void* arg)
    {
        for(int i = 0; i < 10; i++)
        {
            printf("fun run\n");
            sleep(1);
        }
        pthread_exit(NULL);
    }
    
    int main()
    {
    
        pthread_t tid;
        int res = pthread_create(&tid,NULL,pthread_fun,NULL);
        assert(res == 0);
    
        for(int i = 0; i < 5; i++)
        {
            printf("main run\n");
            sleep(1);
        }
    
        char* s = NULL;
        pthread_join(tid,(void**)&s);
        exit(0);
    }
    
    

    运行结果:
    在这里插入图片描述
    其他线程没有结束的话,主线程会在pthread_join()处阻塞。所以主线程会在其他线程结束之后再结束,程序正常退出。

    2.3 线程并发运行

    示例代码 1

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<assert.h>
    #include<pthread.h>
    
    void* pthread_fun(void* arg)
    {
        int index = *(int*)arg;
        int i = 0;
        for(; i < 5; i++)
        {
            printf("index = %d\n",index);
            sleep(1);
        }
    }
    
    int main()
    {
        pthread_t id[5];
        int i = 0;
        for(; i < 5; i++)
        {
            pthread_create(&id[i],NULL,pthread_fun,(void*)&i);
        }
    
        for(i = 0; i < 5; i++)
        {
            pthread_join(id[i],NULL);
        }
    
        exit(0);
    }
    
    

    运行结果1:
    在这里插入图片描述
    运行结果2:
    在这里插入图片描述
    为什么会产生这种情况呢?线程并发问题。
    这是因为我们向pthread_fun传入i的地址。首先来说说为什么会出现多个线程拿到同一个i的值。线程创建在计算机中需要很多个步骤,我们进入for循环传入i的地址后就去进行下一个for循环,创建的线程还没有从地址中获取打印i的值,主函数就继续创建后面的线程了,导致多个线程并发,拿到同一个i值,而且不是创建该线程的时候i的值。
    注意到打印第一个运行结果都是打印0,这是因为主函数第一个for循环已经结束了,后面一个for循环将i又置为0,而这些线程在主函数第一个for循环执行的时候,都没有回获取i的值打印,直到下一个for循环,这些线程才获取i值打印,所以打印出来 都是0。

    示例代码 2:多线程并发访问同一块内存的问题

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<assert.h>
    #include<pthread.h>
    
    int g = 0;
    
    void* pthread_fun(void* arg)
    {
        for(int i = 0; i < 1000; i++)
        {
            printf("g = %d\n",++g);
        }
        pthread_exit(NULL);
    }
    
    int main()
    {
        pthread_t id[5];
        for(int i = 0; i < 5; i++)
        {
            pthread_create(&id[i],NULL,pthread_fun,NULL);
        }
    
        for(int j = 0; j < 5; j++)
        {
            char* s = NULL;
            pthread_join(id[j],(void**)&s);
        }
    
        exit(0);
    }
    

    三次运行结果:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    运行结果最后可能是5000,也可能是4900多,这是怎么回事呢?
    看一下本人的虚拟机设置,处理器数量2个,每个处理器2个内核。
    在这里插入图片描述
    原因就是linux的线程是内核级线程。程序中对g++并不是原子操作,对g++,计算机需要 很多次操作 ,比如将内存中的g读取到寄存器中,再从寄存器中读走进行++,再回头进行写入等等一系列操作。可能一个线程拿到了内存中的g,还没来得及++再写回去,另一个线程被分配到另一个处理器上,读取了相同值的g进行++。所以我们得到的值有时候会比5000要小。
    解决方法有:
    (1)将处理器设置为单核处理器;
    (2)进行线程同步。

    展开全文
  • 在我们对线程的概念有所了解后,开始使用前还需要对它进行创建。这里有四种创建方法:Thread、Runnable、Callable和Future、线程池。前两者是比较主流的创建方法,也是我们在学习时需要重点掌握的,后两种方法也可以...

    9k=

    在我们对线程的概念有所了解后,开始使用前还需要对它进行创建。这里有四种创建方法:Thread、Runnable、Callable和Future、线程池。前两者是比较主流的创建方法,也是我们在学习时需要重点掌握的,后两种方法也可以尝试着学习和运用。接下来我们就创建线程的四种方法,进行详细的解析。

    1.继承Thread类

    调用 start() 方法。使用简单方便,但是由于Java规定类只能继承一个类,所以就无法继承其他类了。public class ThreadDemo extends Thread {

    @Override

    public void run() {

    System.out.println("通过继承 Thread 类创建线程");

    }

    public static void main(String[] args) {

    ThreadDemo threadDemo = new ThreadDemo();

    Thread thread = new Thread(threadDemo);

    thread.start();

    }

    }

    2.实现Runnable接口的run ()方法

    然后再用 Thread 类包裹后,调用 start() 方法。因为类可以实现多个接口,因此扩展性更好,但是还需要再创建Thread对象,并调用start() 方法。public class ThreadDemo implements Runnable {

    @Override

    public void run() {

    System.out.println("通过实现 Runnable 接口创建线程");

    }

    public static void main(String[] args) {

    ThreadDemo threadDemo = new ThreadDemo();

    Thread thread = new Thread(threadDemo);

    thread.start();

    }

    }

    3.Callable和Future创建线程

    (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

    (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

    (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

    (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值CallableInstance ci = new CallableInstance();//实现了Callable接口

    FuuuuutureTask ft = new FutureTask<>(ctt);

    new Thread(ft,"有返回值的线程").start();

    System.out.println(ft.get());

    4.使用线程池方式,由线程池创建线程

    以上就是java线程创建的4种方式,看完后相信大家已经能够独立的创建线程。如果对线程池感兴趣,可以课后查阅资料或是关注接下来的内容分享。

    展开全文
  • 线程创建常用的四种方式

    千次阅读 2021-03-24 16:28:17
    java中创建线程的四种方法以及区别 Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示: 1)继承Thread类创建线程 2)实现Runnable接口创建线程 ...

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

    1)继承Thread类创建线程

    2)实现Runnable接口创建线程

    3)使用Callable和Future创建线程

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

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

     

    1.继承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();//创建并启动线程
    
      }
    
    }

     

    2.实现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();
    
      }
    
    }

     

     

    3.使用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();
    
       }
    
      }
    
    }

     

    4.使用线程池例如用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()方法,该方法便会自动在一个线程上

     

     

     
    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.什么是进程

    进程是系统进行资源调度与分配的最小单位,一般而言,进程具有三个特点:

    独立性:每个进程都有自己独立的私有资源,不与其他进程共享,也就是说,其他进程不可以直接访问其他进程的地址空间。动态性:进程具有自己的生命周期和状态转变。并发性:并发是指在同一时刻只能有一条指令执行,但多个进程可以被快速轮换执行,由于处理速度非常快,感觉上多个进程在同时进行。

    2.什么是线程

    线程也被成为轻量级进程,当进程中需要并发执行多个任务时,就需要使用线程的概念,线程是进程的执行单元,它们可以共享进程中的资源。

    3.进程与线程的区别

    进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元。同一个进程中可以包含多个线程,一个进程中至少包含一个主线程。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。同一个进程中的线程可以并发执行。线程间的通信比进程间通信要方便的多,但是线程之间需要处理好同步与互斥的关系。

    二、线程的创建与启动

    Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例,Java中创建多线程有三种方法:

    继承Thread类重写run方法实现Runnable接口重写run方法使用Callable和Future创建线程

    1.继承Thread类创建线程类

    (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。 (2)创建Thread子类的实例,即创建了线程对象。 (3)调用线程对象的start()方法来启动该线程。

    代码如下所示:

    public class ThreadTest extends Thread{

    int i = 0;

    @Override

    public void run(){

    for(;i<50;i++){

    System.out.println(getName() + " " +i);

    }

    }

    /**

    *@param args

    */

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    ThreadTest thread1 = new ThreadTest();

    ThreadTest thread2 = new ThreadTest();

    for(int i=0; i<50;i++){

    System.out.println(Thread.currentThread().getName()+" : "+i);

    if(i==10)

    {

    thread1.start();

    thread2.start();

    }

    }

    }

    }

    运行结果可能如下所示:

    main : 15

    Thread-1 0

    Thread-1 1

    Thread-1 2

    Thread-1 3

    Thread-1 4

    Thread-1 5

    main : 16

    Thread-1 6

    main : 17

    Thread-0 0

    Thread-1 7

    Thread-0 1

    main : 18

    Thread-0 2

    Thread-1 8

    Thread-0 3

    main : 19

    main : 20

    Thread-0 4

    由测试结果可知,main进程执行至i=10时,开始新建两个新线程,两个新线程从i=0开始分别执行,Thread-0和Thread-1中的i值是不连续的,证明两个线程中的i值不共享,因为两个新线程是ThreadTest创建的两个实例,Thread-0和Thread-1不能共享实例变量。

    2.实现Runnable接口创建线程类

    (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 (3)调用线程对象的start()方法来启动该线程。

    代码如下所示:

    public class RunnableTest implements Runnable{

    int i = 0;

    @Override

    public void run() {

    // TODO Auto-generated method stub

    for(;i<50;i++){

    System.out.println(Thread.currentThread().getName() + " " +i);

    }

    }

    /**

    *@param args

    */

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    RunnableTest runnable = new RunnableTest();

    for(int i = 0;i < 100;i++)

    {

    System.out.println(Thread.currentThread().getName()+" "+i);

    if(i==20)

    {

    new Thread(runnable,"线程1").start();

    new Thread(runnable,"线程2").start();

    }

    }

    }

    }

    运行结果可能如下所示:

    main 53

    main 54

    main 55

    main 56

    线程1 0

    线程2 0

    main 57

    线程1 1

    main 58

    线程2 2

    main 59

    线程1 3

    main 60

    线程2 4

    main 61

    由测试结果可知,两个子线程的i值是连续变化的,说明使用Runnable接口的方式创建的多个线程可以共享线程类的实例变量。

    3.使用Callable和Future创建线程

    (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值,call()方法还可以抛出异常。 (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。 (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    实现代码如下所示:

    import java.util.concurrent.Callable;

    import java.util.concurrent.ExecutionException;

    import java.util.concurrent.FutureTask;

    /**

    * 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值

    *@author zhx

    *

    */

    class CallableDemo implements Callable {

    @Override

    public Integer call() throws Exception {

    // 计算1-100的和

    int sum = 0;

    for (int i = 1; i <= 100; i++)

    sum += i;

    return sum;

    }

    }

    public class CallableTest {

    public static void main(String[] args) {

    CallableDemo cd = new CallableDemo();

    //创建Callable实现类的实例,使用FutureTask类来包装Callable对象

    //该FutureTask对象封装了该Callable对象的call()方法的返回值

    FutureTask result = new FutureTask<>(cd);

    //使用FutureTask对象作为Thread对象的target创建并启动新线程。

    new Thread(result).start();

    int sum = 0;

    // 接收运算结果

    // 只有当该线程执行完毕后才会获取到运算结果,等同于闭锁的效果

    try {

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

    sum = result.get();

    } catch (InterruptedException e) {

    e.printStackTrace();

    } catch (ExecutionException e) {

    e.printStackTrace();

    }

    System.out.println("sum is " + sum);

    }

    }

    三、三种创建方式的对比

    1.采用实现Runnable、Callable接口的方式创见多线程

    优势:

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

    在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

    劣势:

    编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

    2.使用继承Thread类的方式创建多线程

    优势:

    编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

    劣势:

    线程类已经继承了Thread类,所以不能再继承其他父类。

    展开全文
  • 问题:线程创建有一定数量后就创建失败 原因:没有将线程资源进行回收,导致资源不足 解决办法:在线程结束时,由系统释放线程资源,设置线程属性为detach,使线程分离主线程。 原版: pthread_create(&ip, NULL...
  • C#中新创建的线程无法访问UI线程创建的窗口控件处理 在多线程程序中,有时候会碰到新创建的线程无法访问UI线程创建的窗口控件的问题,诸如:“”线程间操作无效: 从不是创建控件“xxx控件”的线程访问它。"之类错误...
  • 在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程线程之间连接用的链表...
  • java多线程创建方式以及线程安全

    千次阅读 多人点赞 2021-05-11 10:53:53
    新建(New):创建后尚未启动的线程处于这种状态 运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。 等待(Wating...
  • 创建线程的3种方式 第一种方式是将类声明为 Thread 的子类。 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。 创建Thread子类的...
  • 文章目录1.线程的定义,进程和线程的关系2.Linux下的线程Linux原生线程库(pthread库) 1.线程的定义,进程和线程的关系 线程是“一个进程内部的控制序列”一切进程至少都有一...创建进程不仅要创建task_struct还要开辟进
  • 一、通过继承Thread类来创建线程类实现步骤:1.定义一个类继承Thread并重写Thread类中的run方法,run方法的方法体就是线程要完成的任务2.创建该类的实例对象3.调用线程对象的start方法来启动线程。二、通过实现...
  • 一、线程、进程、多线程1、线程与进程定义进程:进程是系统进行资源分配和调度的基本单位,是操作系统中一个运行的程序,是线程的容器;线程线程是处理机调度的最小单位,包含在进程中,是进程中实际运作单位;一...
  • hello,小伙伴们好,我是江湖人送...今天是系列文章首篇,咱们来谈谈Java线程创建的一些细节问题: Java线程是如何与OS线程建立联系的 Java线程与OS线程共用一套线程状态吗 Java线程是如何做到创建与启动分开的 Java线
  • FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable被线程执行,也可以作为Future得到Callable的返回值。 二、代码实现 import java.util.concurrent.Callable; import java.util.concurrent....
  • 内核和用户模式下进程与线程创建

    千次阅读 2021-02-04 17:59:27
    内核模式下进程与线程创建 进程创建 在内核模式中,一个进程的创建是从函数NtCreateProcess开始的。该函数位于文件ntosrnl.exe中,该文件位于%windir%\system32.它对用户传进的部分参数进行简单处理,然后交给函数...
  • 文章目录线程启动、结束,创建线程方法,join,detach1、范例演示:线程的开始和结束1.1、thread1.2、join()1.3、detach()1.4、joinable()2、其他创建线程的方式 线程启动、结束,创建线程方法,join,detach 1、...
  • Android 多线程创建

    千次阅读 2021-11-07 10:11:24
    Android多线程创建,两种方式 1.继承Thread类 2.实现Runable接口 这两种属于Java多线程的创建,这里不做总结。下面总结一下两种的区别: 继承Thread类,属于继承,存在缺陷,一个类只可以有一个父类,但是可以实现...
  • 目录1、基本概念2、线程创建2.1、 继承 Thread 类(重点)Thread API2.2、 实现Runnable接口(重点)Runnable接口API以上两种方式的比较:初识并发问题——买火车票龟兔赛跑问题:img2.3、 实现Callable接口(了解)...
  • 线程的三种创建方式

    2021-07-17 20:39:28
    读入数据总结欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右...
  • Linux下使用C++创建线程,求助本帖最后由 hello_world_2012 于 2012-08-04 20:57:34 编辑请高手指点下:代码如下:#include#include#includeusingnamespacestd;/*新创建1个线程要执行的操作测试函数*/voidf1(void*)/...
  • Java多线程创建与运行

    2021-11-10 14:06:37
    进程是系统进行资源分配和调度的基本单位。而线程是程序运行的最小单位。...进入到Thread源码中可以看到源码提供了两种创建线程的方式 方式一:继承Thread类 class PrimeThread extends Thread { long minP
  • Linux线程创建与同步

    千次阅读 2021-12-02 09:50:44
    #线程的概念与实现方式 ##线程的概念 线程是进程内部的一条执行序列或执行路径一个,一个进程可以包含多个线程。 事实上,Linux实现线程的机制非常独特,从内核的角度来说,它并没有线程这个概念。Linux把所有的线程...
  • 在主进程中创建一个线程,在线程中再创建两个子线程,代码如下:#include#includevoidthread();voidchild_thread1();voidchild_thread2();intmain(){pthread_tid;pthread_create(&id,NULL,(void*)thread,NULL);...
  • java 线程创建和运行

    千次阅读 2021-12-06 10:29:08
    // 创建线程对象 Thread t = new Thread() { public void run() { // 要执行的任务 } }; // 启动线程 t.start(); 使用 Runnable 配合 Thread 把【线程】和【任务】(要执行的代码)分开 Thread 代表线程 ...
  • c++创建线程的6种方法

    千次阅读 2021-04-08 17:57:40
    } -------------------------------------------------------------------------------------------------------------- //带参数的线程创建 void printInfo(int& num) { cout 子线程\t" ; } int main4() { //std::...
  • 比如打开一个浏览器、打开一个word等操作,都会创建进程。 线程线程是进程内部的一个独立执行单元; 一个进程可以同时并发运行多个线程; 比如进程可以理解为医院,线程是挂号、就诊、缴费、拿药等业务活动.....
  • 创建线程: 包含头文件,调用thread类创建一个线程对象; #include <thread> #include <iostream> using namespace std; void print(){ cout<<"子线程"<<endl; } int main(){ thread ...
  • 在头文件 threads.h 中,定义和...函数 thrd_create()用于创建并开始执行一个新线程。函数 thrd_create()的其中一个参数为在新线程中需要被执行的函数 thrd_create()的其中一个参数为在新线程中需要被执行的函数。th...
  • Java 采用 thread-per-task 的线程模型,即一个任务(一段代码)对应一个 Java 线程(thread),而一个 Java 线程对应一个操作系统线程,所以了解一些操作系统进程的管理知识可以更好的了解 Java 线程,下面以 Liunx 为...
  • 两种线程创建方式的对比: 在开发中我们优先选择实现Runnable接口的方式来创建线程 原因: 实现Runnable接口的方式创建多线程没有类的单继承的局限性 比如有一个类A要单独创建出一个线程来执行它所具有的功能,如果这...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,652,734
精华内容 661,093
关键字:

线程创建