专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Java基础 线程知识总结(一)

在讲线程之前,首先要说一下程序、进程和线程的概念:

  • 程序:程序是一段静态代码,是一组有序指令的集合,本身没有运行的含义,它是应用软件执行的蓝本
  • 进程:它是程序的一次动态执行,是一个在内存中运行的应用程序。它是系统调度的独立单位,是资源分配的基本单位,各个进程间不会相互影响,因为系统为它们分配了不同的空间和资源
  • 线程:线程是进程中的一个执行流程,线程是进程的组成部分,它代表了一条顺序的执行流。线程是cpu运行调度的基本单位。
  • 应用程序运行可能产生多个进程,一个进程至少拥有一个线程

进程和线程的区别:

  • 地址空间:进程之间是独立的地址空间,而同一进程内的线程共享进程的地址空间
  • 资源占用:进程之间的资源是独立分配的,同一进程内的线程共享进程的资源
  • 健壮性:
    • 一个进程崩溃后,在保护模式下不会对其他进程产生影响
    • 一个线程崩溃整个进程都死掉,所以多进程要比多线程健壮
  • 执行过程:
    • 进程可以独立执行,且每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口
    • 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。线程是处理器调度的基本单位,但是进程不是。线程必须依赖进程而存在
  • 并发和资源消耗:两者均可并发执行。进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程

Java中使用线程的三种方式

  • 继承Thread类
    • 继承Thread类是Java中比较常见,也是很基础的一种实现Java多线程的方式。
    • 具体使用:创建一个类继承Thread类,并重写run()方法
        class MyThread extends Thread{
            private String name;
            public MyThread(String name){
                this.name = name;
            }
            @Override
            public void run() {      
                Thread.currentThread().setName(name); 
                System.out.println("I am Thread :" +name);     
            }
        }

        public class threadLearn {
            public static void main(String[] args)  {
                //实例化继承了Thread的类
                MyThread thread1 = new MyThread("Thread1");
                //通过从Thread类中所继承的start()方法启动线程;
                thread1.start();    
            }
        }

  • 实现Runnable接口
    • Thread类是实现了Runnable接口的run()方法,所以我们也可以实现Runnable接口来使用线程,并且Java中只支持单根继承,使用Runnable接口可以再继承类或实现接口。
    • 具体使用:创建一个类实现Runnable接口的run()方法
        class MyThread implements Runnable{
            private String name;
            public MyThread(String name){
                this.name = name;
            }
            @Override
            public void run() {      
                Thread.currentThread().setName(name); 
                System.out.println("I am Thread :" +name);     
            }
        }

        public class threadLearn {
            public static void main(String[] args)  {
                Thread thread2 = new Thread(new MyThread("Thread2"));
                thread2.start();    
            }
        }

  • 实现Callable接口
    • 在介绍Excutor实现多线程之前,我们先来学习另外一种多线程的实现方式,即使用Callable接口实现多线程的方式。
        class  MyCallable<V> implements Callable<V>{
            @Override
            public V call() throws Exception {
                System.out.println("I am Callable thread : "+Thread.currentThread().getName());
                return null;
            }
        }

        public static void main(String[] args)  {
            Callable <Integer> aCallable = new MyCallable<Integer>();
            FutureTask<Integer> aTask = new FutureTask<Integer>(aCallable);
            Thread aThread = new Thread(aTask);
            aThread.start();
        }

 *  说明:通过实现`Callable`接口方式,我们需要向`Thread`类传入`FutureTask`这个类的实例,`FutureTask`这个类实现了`RunnableFuture`接口,`RunnableFuture`接口又实现了`Runnable`接口。`FutureTask`类的构造方法需要传入一个`Callable`的对象,另一个构造方法接收`Runnable`对象,内部还是生成了一个`Callable`对象。
 *  优点:Callable接口里的call方法,是一个有返回值的方法;且FutureTask类的实现方式中,针对Runnable的实现方式,也是携带有一个参数result,由result和Runnable实例去合并成一个Callable的实例。实现了Callable接口的线程,是具有返回值的。而对于一些对线程要求有返回值的场景,是非常适用的。

* 使用Executor框架,将Callable加入到线程池运行

 *  `ExecutorService`接口里有一个方法`<T> Future<T> submit(Callable<T> task);`我们使用如下实现方法
        /**
        * @throws RejectedExecutionException {@inheritDoc}
        * @throws NullPointerException       {@inheritDoc}
        */
        public <T> Future<T> submit(Callable<T> task) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<T> ftask = newTaskFor(task);
            execute(ftask);
            return ftask;
        }

 *  可以看出这个方法实现,本质上还是返回了一个`RunnableFuture`的实例,我们可以通过该实例获取到线程的返回值
 *  实例如下:
        /*
        * Callable接口实现多线程Demo
        */
        class  MyCallable implements Callable<Object>{
            private int task_id;
            MyCallable(int task_id){
                this.task_id = task_id;
            }
            @Override
            public Object call() throws Exception {
                // TODO Auto-generated method stub
                System.out.println("I am Callable thread : "+Thread.currentThread().getName());
                Random rd = new Random();
                int leng = rd.nextInt(9)+1;
                int sum = 0 ;
                for(int i = 0 ; i <= leng ; i++ ){
                    Thread.sleep(1000);
                    sum += i;
                }
                System.out.println("I am Callable thread : "+Thread.currentThread().getName()
                +"Thread name is : ["+this.task_id+"]"
                +" I worked done at ["+new Date().getTime()+"]"
                +" Random Num = "+leng
                );
                return "The task ["+this.task_id+"] get result :【 "+ sum +" 】";
            }
        }

    这个线程会返回1-10以内的随机数的自增合。我们来通过线程池调度一下:
        /*
         *使用Excutor来执行线程,并获取返回值
         */
        int taskSize = 5;
        ExecutorService  exPool = Executors.newFixedThreadPool(taskSize);
        //使用Future来获取线程的结果
        List<Future> list = new ArrayList<Future>(); 
        for (int i = 0; i < taskSize; i++) { 
            Callable c = new MyCallable(i); 
            // 执行任务并获取Future对象 
            Future f = exPool.submit(c);  
            list.add(f); 
        } 
        exPool.shutdown();
        System.out.println("The time we shutDown The Executor Pool : 【"+new Date().getTime()+"】");
        for (Future f : list) { 
            // 从Future对象上获取任务的返回值,并输出到控制台 
            try {
                    System.out.println(">>>" + f.get().toString());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 
        }

  • 使用Executor框架
    • 使用new Thread()创建一个线程,这样的线程不能复用,只能释放线程资源,这种方式性能较差,而且如果不加限制,创建太多的线程会消耗太多系统资源,同时线程太多,如果要定期执行、关闭线程等,都不方便统一管理。
    • 所以我们就需要线程池来管理线程,使用线程池有以下优点:
      • 可以复用线程,减少对象创建,就减少系统资源消耗
      • 可以控制最大并发线程数,提高系统资源利用率
      • 使用线程池可以避免资源竞争,也就减少了阻塞情况
      • 创建线程池简单方便,同时便于统一管理
  • Executor框架
    • 每次创建线程都是资源密集型的。一个很好的替代方法是提前设置好一些线程,也就是我上面说的线程池,然后将我们的任务分配给这些线程。这就是Executors类和ExecutorService非常有用的地方。
    • 如何使用
      • 不再使用new Thread() 创建一个线程,而是先创建一个ExecutorService,Executors类具有ExecutorService的多个实现,这儿我们使用Executors类创建大小为4的固定线程池(newFixedThreadPool)。
            创建线程池
            ExecutorService executors = Executors.newFixedThreadPool(4);
            接下来,我们需要将我们的任务提交给ExecutorService
            Future<Integer> future = executors.submit(w);
            提交任务后,我们将获得一个Future对象的实例  
            实例代码:
            public class ExecutorDemo {
                public static void main(String[] args) {
                    /**
                    * 创建线程池的6种方式
                    */
                    // 1、创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
                    ExecutorService executors = Executors.newFixedThreadPool(4);
                    // 2、创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
                    //ExecutorService executors = Executors.newCachedThreadPool();
                    // 3、创建一个定长线程池,支持定时及周期性任务执行。
                    //ExecutorService executors = Executors.newScheduledThreadPool(4);
                    // 4、创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
                    //ExecutorService executors = Executors.newSingleThreadExecutor();
                    // 5、newWorkStealingPool工作窃取线程池,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中
                    //ExecutorService executors = Executors.newWorkStealingPool(4);
                    // 6、newSingleThreadScheduledExecutor创建线程池同时放入多个线程时,每个线程都会按照自己的调度来执行,但是当其中一个线程被阻塞时,其它的线程都会受到影响被阻塞,不过依然都会按照自身调度来执行,但是会存在阻塞延
                    //ExecutorService executors = >Executors.newSingleThreadScheduledExecutor();
                    Future<Integer>[] futures = new Future[5];
                    Callable<Integer> w = new CallableTask();
                    try {
                        for (int i = 0; i < 5; i++) {
                        Future<Integer> future = executors.submit(w);
                        futures[i] = future;
                        for (int i = 0; i < futures.length; i++) {
                            try {
                                System.out.println("Result from Future " + i + ":" +         futures[i].get());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } catch (ExecutionException e) {
                                e.printStackTrace();
                            }
                        }
                    } finally {
                        executors.shutdown();
                    }
                }
            }

     *  获取结果
            for (int i = 0; i < futures.length; i++) {
                try {
                    System.out.println("Result from Future " + i + ":" + futures[i].get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

     *  运行情况说明

         *  在上面的示例中,我们创建了一个大小为4的固定线程池。
         *  如果我们总共向ExecutorService提交3个任务,那么所有3个任务都将分配给线程池,并且它们将开始执行。
         *  如果我们向ExecutorService提交4个任务,那么所有这4个任务将再次分配给线程池,并且它们将开始执行。
         *  如果我们向该线程池提交5个任务,只有4个任务将分配给线程池。这是因为线程池的大小为4。仅当释放池中的线程之一时,才会分配第五个任务。
     *  关闭线程池: `executors.shutdown();`

         *  ExecutorService的关闭操作通常位于`finally`块中。这是为了确保即使在发生任何异常的情况下,关闭操作始终在代码的末尾被执行。如果关闭操作不正确,那么如果发生任何异常,则ExecutorService仍将运行并消耗其他JVM资源。

文章永久链接:https://tech.souyunku.com/27499

未经允许不得转载:搜云库技术团队 » Java基础 线程知识总结(一)

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们