• 并发编程学习笔记(1)----多线程几种实现方式


    多线程是指机器支持在同一时间执行多个线程,能够提高cpu的利用率 ,提高程序的执行效率。

    (1)继承Thread类

    多线程可以通过继承Thread类并重新Thread的run方法来启动多线程。然后通过Thread的start方法来启动线程。上代码:

    package com.wangx.thread.t1;
    
    public class Demo1 extends Thread {
    
        Demo1(String name) {
            super(name);
        }
        @Override
        public void run() {
            while (!interrupted()) {
                System.out.println("线程" + Thread.currentThread().getName() + "执行了。。。。");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Demo1 demo1 = new Demo1("one");
            Demo1 demo2 = new Demo1("two");
            demo1.start();
            demo2.start();
            demo1.interrupt();
        }
    }

    这里也顺便用了线程的中断,当希望一个线程不再执行时,就是用interrupt()方法来进行中断,此时的的线程对象将不会再执行,interrupted()方法判断线程是否中断,返回boolean值,当当前线程被中断时返回false,使用interrupted()方法可以避免报InterruptedException异常。

    (2)实现Runnable接口

    Runnable接口中只有一个run方法,实现Runnable接口的run方法作为任务处理方法,将Runnable的实现类对象传入到Thread的构造中,创建线程,并使用Thread.start()启动线程。代码:

    package com.wangx.thread.t1;
    
    public class Demo2 implements Runnable {
    
        /**
         * 重写run方法
         */
        @Override
        public void run() {
            System.out.println("执行了");
        }
    
        public static void main(String[] args) {
    
            Demo2 demo2 = new Demo2();
            //将demo2传入到Thread中
            Thread thread = new Thread(demo2);
            Thread thread1 = new Thread(demo2);
    
            thread.start();
            thread1.start();
        }
    }

    这里的Runnable接口其实是作为一个线程任务处理器,查看Thread中的run方法和构造方法可以看出,但传入的target(Runnable对象)不为空时,执行Runnable的run方法,所以通过启动线程时实际先执行的还是Thread中的run方法去调用target的run方法。Thread中的run()方法源码如下:

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

    (3)匿名内部类方法

    匿名内部类其实跟继承Thread类并重写run方法原理一样,都是通过覆盖父类run方法,执行当前对象的run方法的方式来执行只需要处理的任务,只是写法上跟简洁,并且只会执行一次,如果任务只需要执行一次时并且减少代码量时可以使用,代码如下:

    package com.wangx.thread.t1;
    
    public class Demo3 {
        public static void main(String[] args) {
    
            //匿名内部类启动多线程
            new Thread(){
                @Override
                public void run() {
                    System.out.println("Thread in running");
                }
            }.start();
        }
    }

    还可以通过Runnable的匿名内部类来实现,原理与第二点一致,代码如下:

    package com.wangx.thread.t1;
    
    public class Demo3 {
        public static void main(String[] args) {
    
            //匿名内部类启动多线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("runnable thread is running");
                }
            }).start();
        }
    }

    在这里需要注意的时,当同时使用Thread的匿名内部类和Runnable接口的匿名内部类同时使用时,此时执行的是Thread的run方法,而不会执行Runnable的run方法,原因是根据源码可以看出此时run方法已经被重写,所以不会调用target.run语句,所以Runnable的run方法不会执行。代码:

    package com.wangx.thread.t1;
    
    public class Demo3 {
        public static void main(String[] args) {
            //Thread匿名内部类和Runnable匿名内部类同时存在时,打印sub is running
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Runnable");
                }
            }){
                @Override
                public void run() {
                    System.out.println("sub is running");
                }
            }.start();
        }
    }

    (4)创建带返回值的线程

    实现Callable<T>泛型方法,重写call方法,泛型传入什么类型的值,call就返回什么类型的值,并使用FutureTask接收Callable对象,FutureTask的泛型为Callable中传入的类型,将
    FutrueTask对象传入到Thread中启动线程,并使用FutureTask的get方法获取到call方法的返回值,代码如下:
    package com.wangx.thread.t1;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class Demo4 implements Callable<Integer> {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            Demo4 demo4 = new Demo4();
    
            //通过demo4创建task对象
            FutureTask<Integer> task = new FutureTask<>(demo4);
    
            //通过task创建并启动线程
            Thread thread = new Thread(task);
            thread.start();
            Integer result = task.get();
            System.out.println("计算结果为:" + result);
        }
    
        @Override
        public Integer call() throws Exception {
            System.out.println("正在进行紧张的计算....");
            Thread.sleep(1000);
            return 1;
        }
    }

    Thread中可以接接收FutureTask是因为FutureTask是Runnable的实现类,所以也可以说FutureTask是Runnable的另类实现。

    (5)线程池的方式

    由于线程的创建和销毁都会消耗过多的内存资源,所以在jdk5之后JAVA新增了线程池的概念,ThreadPoolExecutor是线程池的核心类,它重载了很多的构造方法让我们构造一个线程池,在线程池中创建指定多个的线程,执行任务时,将从线程池中取出线程去执行任务,任务执行完成后将线程归还到线程池中。线程池不用频繁的创建和销毁线程池,减少了资源的消耗,提高了性能,可以直接通过new ThreadPoolExecutor()来指定自己创建线程池,也可以使用Executors中的静态方法来创建各种类型的线程池,这里先创建一个制定大小的线程池,代码如下:

    package com.wangx.thread.t1;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Demo6 {
        public static void main(String[] args) {
            //创建线程池大小为10的线程池
            ExecutorService executor = Executors.newFixedThreadPool(10);
            //循环执行100次,打印线程名字,发现总是只用10个线程在执行
            for (int i = 0; i < 100; i++){
                executor.execute(()-> {
                    System.out.println(Thread.currentThread().getName());
                });
            }
            executor.shutdown();
        }
    }
     executor.execute()传入Runnable对象,执行任务,这里使用lambda表达式写法,其实就是一个Runnable匿名对象,打印当前线程名称。当任务100次循环之后我们发现程序并没有结束,
    这是因为线程池仍然存活,此时调用executor.shutdown();方法关闭线程池,结束程序。因为Executors中的静态方法也是通过ThreadPoolExecutor来创建的,所以这里就不写直接创建
    的案例了,关于ThreadPoolExecutor构造方法的个参数作用下一节将会详解。
    (6)在spring3+中使用多线程
    spring3之后的版本提供了对多线程的支持,这里案例是spring boot项目为例的,方便引入spring的依赖。
    在spring boot中使用多线程需要使用@EnableAsync注解来开启异步执行的支持,然后在需要执行的方法上加上@Async标记该方法为异步方法,之后直接通过bean调用该方法可以发现该方法是
    异步执行的,实例代码为:
    package com.example.springthread;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.scheduling.annotation.EnableAsync;
    /**
    *springboot启动类,这里也用做了配置类
    */
    @SpringBootApplication
    //开启注解
    @EnableAsync
    public class SpringthreadApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext context = SpringApplication.run(SpringthreadApplication.class, args);
    
            DemoService demoService = context.getBean(DemoService.class);
    
            demoService.a();
            demoService.b();
        }
    }
    /*******************异步方法所在的bean*************************/
    package com.example.springthread;
    
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    
    
    @Service
    public class DemoService {
    //标记方法为异步方法
        @Async
        public void a() {
            while (true){
                System.out.println("a is running");
            }
        }
        @Async
        public void b() {
            while (true){
                System.out.println("b is running");
            }
        }
    }

    启动springboot,调用DemoService的a/b方法,可以看到他们异步执行。这里只是为了方便引入spring的依赖,直接使用spring也是可以实现异步方法调用的spring也支持计划任务,使用@EnableScheduling开启计划任务,@Scheduled标记计划任务的方法,在注解中传入相应的执行时间和周期即可实现计划任务

    (7)创建定时任务
    在java.util包中提供了一个Timer类可以用来创建定时任务,可以指定某个时间开始执行,之后每隔多长时间执行一次。代码如下:
    package com.wangx.thread.t1;
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class Demo5 {
        public static void main(String[] args) {
            Timer timer = new Timer();
            //  创建1秒后开始中,没隔1秒执行一次的定时任务
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("timer task is running");
                }
            },1000, 1000);
        }
    }

    Timer接收一个TimerTask为要执行的任务,其余参数为需要执行的时间方式,可以进入Timer的源码查看个参数的意义,构建自己想要的计划任务。

    本章主要是为了回顾java生态中各种多线程的实现方式,并不涉及太多概念和原理问题,具体的原理将会在后面的学习中逐渐补上。

    原文 并发编程学习笔记(1)----多线程几种实现方式

  • 相关阅读:
    织梦调用所有栏目一级二级三级和无限级栏目输出
    织梦添加自定义独立模型缩略图字段官方的一个BUG
    织梦搜索结果增加dede:type、dede:arclist、dede:channelartlist、dede:tag等标签支持
    织梦搜索指定多个栏目的文档
    POJ 1001
    #207 Div.2 B. Flag Day
    sublime Text 2 配置 C++ 编译环境
    1011. 复数类
    1008. 二哥买期货
    MySQL/mariadb知识点——视图管理语句
  • 原文地址:https://www.cnblogs.com/xiaoshen666/p/10867654.html
Copyright © 2020-2023  润新知