并发编程学习笔记(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生态中各种多线程的实现方式,并不涉及太多概念和原理问题,具体的原理将会在后面的学习中逐渐补上。

 

posted @ 2018-09-18 23:24  Eternally_dream  阅读(320)  评论(0编辑  收藏  举报