Java线程,以及创建方式

Java线程,以及创建方式

1. 进程与线程的区别

  1. 进程是操作系统资源的最小分配单位, 线程是操作系统调度执行的基本单位
  2. 一个进程可能包括多个线程, 也就是说, 进程是包含线程的
  3. 每个进程都有独立的内存空间(虚拟地址空间), 此进程的多个线程(或一个), 共用这个进程的独立内存空间

2. 创建进程的方式

2.1 普通创建线程

2.1.1 继承Thread类

/*
多线程的实现方式第一种:继承方式
1、定义一个类,继承Thread类,Thread类是一个线程类,用于开启线程
2、步骤:
(1)	自定一个类MyThread,继承Thread类
(2)	重写Thread类中的run方法,用于定义新的要执行的内容
(3)	创建自定义类型的对象
(4)	调用线程开启的方法,start()方法
 */
//1.定义一个类继承自Thread类
class MyThread extends Thread {
    //2.重写run方法

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            System.out.println("继承Thread实现的线程");
        }
    }
}

public class Demo01 {
    public static void main(String[] args) {
        MyThread myThread =new MyThread();
        //myThread.run();
        //这样写是错误的,开启多线程应该使用start
        myThread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            System.out.println("主线程");
        }

        new MyThread().start();
    }
}

创建自定义类继承Thread类, 并重写run方法(使用匿名内部类的方式)

/*
多线程的实现方式第一重的第二种:匿名内部类的形式实现多线程
 */
public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println(i);
                }
            }
        };

        thread.start();

        for (int i = 0; i < 100; i++) {
            Thread.sleep(1);
            System.out.println("休息");
        }
    }
}

2.1.2 实现Runable接口, 重写run方法

/*
多线程的实现的第二种方式:实现接口
1、实现Runnable接口:Runnable接口的实现类对象,表示一个具体的任务,将来创建一个线程对象,让线程对象执行这个任务
2、步骤:
(1)	定义一个任务类MyRunnable,实现Runnable接口
(2)	重写接口中的run方法,用于定义任务的内容
(3)	创建任务类对象,表示任务
(4)	创建Thread类对象,创建线程,将任务对象作为构造方法的参数传递,用于执行任务的类对象  Thread(Runnable r)
(5)	调用线程开启的方法,start() 开启新线程
 */
//(1)	定义一个任务类MyRunnable,实现Runnable接口
class MyRunnable implements Runnable {
    private String name;
    @Override
    //(2)	重写接口中的run方法,用于定义任务的内容
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "打了" + i + "小兵");
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Demo03 {
    public static void main(String[] args) {
        //(3)	创建任务类对象,表示任务
        MyRunnable myRunnable = new MyRunnable();
        myRunnable.setName("鲁班");
        //创建Thread类对象,创建线程
        //(4)	创建Thread类对象,创建线程,将任务对象作为构造方法的参数传递,用于执行任务的类对象  Thread(Runnable r)
        Thread thread = new Thread(myRunnable);
        //启动线程
        //(5)	调用线程开启的方法,start() 开启新线程
        thread.start();

        //任务
        MyRunnable myRunnable2 = new MyRunnable();
        myRunnable2.setName("貂蝉");
        //创建Thread类对象,创建线程
        Thread thread2 = new Thread(myRunnable2);
        //启动线程
        thread2.start();

        //任务
        MyRunnable myRunnable3 = new MyRunnable();
        myRunnable3.setName("刘备");
        //创建Thread类对象,创建线程
        Thread thread3 = new Thread(myRunnable3);
        //启动线程
        thread3.start();


        //Runable是一个接口,也可以通过匿名内部类的方式进行实现
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(i);
                }
            }
        };
        Thread thread4 = new Thread(runnable);
        thread4.start();
    }
}

实现Runable接口, 重写run方法(使用匿名内部类的方式)

/*
多线程的实现方式第一重的第二种:匿名内部类的形式实现多线程
 */
public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println(i);
                }
            }
        };

        thread.start();

        for (int i = 0; i < 100; i++) {
            Thread.sleep(1);
            System.out.println("休息");
        }
    }
}

2.1.3 实现Callable接口

/*
多线程的第三种实现方式:实现Callable接口
1、相关的方法:
(1)	V call(): 计算结果,如果无法计算结果,则抛出一个异常
(2)	FutureTask(Callable<V> cal):创建一个FutureTask,一旦运行就执行给定的Callable
(3)	V get(): 获取计算完成的返回值
2、实现思路:
(1)	如果创建Thread,执行Runnable任务,需要Runnable对象
(2)	Runnable是一个接口,有一个特殊的实现类
(3)	FutureTask是Runnable的实现类
(4)	FutureTask在创建对象的时候,需要传递Callable类型的对象
(5)	Callable是一个接口,所以需要一个Callable实现类对象
(6)	Callable接口中提供了一个方法,call() 相当于原来的run,但是该方法是可以有返回值的
3、步骤:
(1)	定义一个MyCallable实现Callable接口
(2)	在实现类MyCallable中重写Call方法
(3)	创建MyCallable对象
(4)	创建FutureTask对象,把MyCallable的对象作为构造方法的参数传递给FutureTask
(5)	创建Thread类的对象,把FutureTask对象作为构造方法的参数
(6)	使用Thread类调用start方法启动线程
(7)	FutureTask对象调用get方法,可以获取线程执行的任务,这个任务最终返回的结果
 */

//1. 定义一个类去实现Callable接口
class MyCallable implements Callable<Integer> {

    //2. 重写call方法
    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 0; i < 100; i++) {
            System.out.println(i + "Callable");
            sum += i;
        }
        return sum;
    }
}
public class Demo05 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3. 创建MyCallable对象 , 也就是实现类的对象
        MyCallable myCallable = new MyCallable();
        //4. 创建FutureTask对象, 并把第一步创建的实现类传入其对象的构造方法中
        FutureTask futureTask = new FutureTask<>(myCallable);
        //5. 创建Thread类的对象, 把第四步的FutureTask对象传入其对象的构造方法中
        Thread thread = new Thread(futureTask);

        //6. Thread对象启动线程
        thread.start();

        //7. 可以使用FutureTask对象调用get方法,可以获取线程执行的任务,这个任务最终返回的结果
        //查看源码,返回值为Value,此处应该为Integer
        System.out.println(futureTask.get());

    }
}

2.1.4 使用Lambda表达式

public class Demo06 {
    public static void main(String[] args) throws InterruptedException {
        //
        Thread t1 = new Thread(() -> System.out.println("使用匿名类"));
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("使用匿名类创建 Thread 子类对象");
            }
        });
        t1.start();
        t2.start();
    }
}

2.2 线程池创建线程

线程池创建线程

2.2.1 FixedThreadPool

创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

public static void executorServiceTest() {
    // 创建线程池  2个数据级
    ExecutorService threadPool = Executors.newFixedThreadPool(2);

    // 创建任务
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("执行:" + Thread.currentThread().getName());
        }
    };

    // 执行
    // 执行方式:    submit  &   execute
    threadPool.submit(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
}

2.2.2 CachedThreadPool

创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

public static void cachedThreadPoolTest() {
    // 创建线程池
    ExecutorService threadPool = Executors.newCachedThreadPool();

    for (int i = 0; i < 10; i++) {
        // 创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行:" + Thread.currentThread().getName());
            }
        };
        threadPool.execute(runnable);
    }
}

2.2.3 ScheduledThreadPool

执行延迟任务

public static void scheduledThreadPoolTest() {
    // 创建线程池
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);

    // 此线程池, 可以执行延迟任务
    // 现在设置5s后执行打印任务
    System.out.println("开始执行:" + new Date());
    executorService.schedule( () -> {
        System.out.println("任务执行中:" + new Date());
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } , 1 , TimeUnit.SECONDS);
}

2.2.4 newWorkStealingPool

创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8+ 版本中才能使用。

public static void newWorkStealingPoolTest() {
    // 创建线程池
    ExecutorService threadPool = Executors.newWorkStealingPool();

    for (int i = 0; i < 10; i++) {
        int index = i;
        // 创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行:" + index + "---" +Thread.currentThread().getName());
            }
        };
        threadPool.execute(runnable);
    }

    // 此时发现任务的执行顺序是不确定的,因为它是抢占式执行的。
    // 为确保执行完成
    while (!threadPool.isTerminated()) {

    }
}

2.2.5 SingleThreadExecutor

创建单个线程数的线程池,它可以保证先进先出的执行顺序。

public static void singleThreadExecutorTest() {
    // 创建线程池
    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    for (int i = 0; i < 10; i++) {
        int index = i;
        // 创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行:" + index);
            }
        };
        threadPool.execute(runnable);
    }
}

2.2.6 SingleThreadScheduledExecutor

创建一个单线程的可以执行延迟任务的线程池。

使用示例如下:

public static void SingleThreadScheduledExecutor() {
    // 创建线程池
    ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
    // 添加定时执行任务(2s 后执行)
    System.out.println("添加任务,时间:" + new Date());
    threadPool.schedule(() -> {
        System.out.println("任务被执行,时间:" + new Date());
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
    }, 2, TimeUnit.SECONDS);
}

2.2.7 ThreadPoolExecutor

最原始的创建线程池的方式,它包含了 7 个参数可供设置。

// 最原始的创建线程池的方式,它包含了 7 个参数可供设置。
public static void myThreadPoolExecutorTest() {
    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
    // 执行任务
    for (int i = 0; i < 10; i++) {
        final int index = i;
        threadPool.execute(() -> {
            System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}
ThreadPoolExecutor 参数介绍

ThreadPoolExecutor 最多可以设置 7 个参数,如下代码所示:

 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler) {
     // 省略...
 }
参数名 说明
corePoolSize 核心线程数,线程池中始终存活的线程数。
maximumPoolSize 最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
keepAliveTime 最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
unit 单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间
workQueue 一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全
threadFactory 线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。
handler 拒绝策略,拒绝处理任务时的策略
参数 4:unit:

单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间 ,参数 keepAliveTime 的时间单位有以下 7 种可选:

  • TimeUnit.DAYS:天
  • TimeUnit.HOURS:小时
  • TimeUnit.MINUTES:分
  • TimeUnit.SECONDS:秒
  • TimeUnit.MILLISECONDS:毫秒
  • TimeUnit.MICROSECONDS:微妙
  • TimeUnit.NANOSECONDS:纳秒
参数 5:workQueue

一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型:

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

较常用的是 LinkedBlockingQueueSynchronous,线程池的排队策略与 BlockingQueue 有关。

参数 6:threadFactory

线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。

参数 7:handler

拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:

  • AbortPolicy:拒绝并抛出异常。
  • CallerRunsPolicy:使用当前调用的线程来执行此任务。
  • DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
  • DiscardPolicy:忽略并抛弃当前任务。

默认策略为 AbortPolicy

总结

线程的使用, 尽量使用线程池, 线程池的使用尽量使用ThreadPoolExecutor.

ThreadPoolExecutor 最多可以设置 7 个参数,当然设置 5 个参数也可以正常使用,ThreadPoolExecutor 当任务过多(处理不过来)时提供了 4 种拒绝策略,也可以自定义拒绝策略

后面会写一篇详细的ThreadPoolExecutor 的文章

参考&鸣谢

https://juejin.cn/post/6907086699021631501#heading-2

https://juejin.cn/post/7023969128419426318

posted @ 2022-07-26 15:11  DevourZuan  阅读(88)  评论(0)    收藏  举报