多线程编程(一)创建线程的四种方式

创建线程的四种方式

1. 继承Thread类

1.1 创建线程类

集成Thread类,并重写run方法

public class MyThead extends Thread {
    
    
    /**
     * 实现run 方法
     */
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            //暂停0.5秒,模仿业务逻辑执行
            try {
    
    
                Thread.sleep(500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "线程开始执行了:" + System.currentTimeMillis());
        }

    }
}

1.2 测试方法

创建三个线程

public static void main(String[] args) {
    
    

        Thread thread = new MyThead();
        Thread threa2 = new MyThead();
        Thread threa3 = new MyThead();
        thread.start();
        threa2.start();
        threa3.start();
    }

1.3 查看执行结果

Thread-2线程开始执行了:1605334456249
Thread-0线程开始执行了:1605334456250
Thread-1线程开始执行了:1605334456250
Thread-1线程开始执行了:1605334456753
Thread-0线程开始执行了:1605334456753
Thread-2线程开始执行了:1605334456753
Thread-2线程开始执行了:1605334457305
Thread-0线程开始执行了:1605334457306
Thread-1线程开始执行了:1605334457306
Thread-0线程开始执行了:1605334457837
Thread-1线程开始执行了:1605334457838
Thread-2线程开始执行了:1605334457838
Thread-0线程开始执行了:1605334458357
Thread-1线程开始执行了:1605334458358
Thread-2线程开始执行了:1605334458358
Thread-2线程开始执行了:1605334458859
Thread-1线程开始执行了:1605334458859
Thread-0线程开始执行了:1605334458859
Thread-1线程开始执行了:1605334459374
Thread-2线程开始执行了:1605334459374
Thread-0线程开始执行了:1605334459374
Thread-0线程开始执行了:1605334459878
Thread-1线程开始执行了:1605334459878
Thread-2线程开始执行了:1605334459878
Thread-0线程开始执行了:1605334460381
Thread-1线程开始执行了:1605334460381
Thread-2线程开始执行了:1605334460381
Thread-0线程开始执行了:1605334460882
Thread-1线程开始执行了:1605334460882
Thread-2线程开始执行了:1605334460882

Process finished with exit code 0

系统时间有重复的,代表实现了并发

2. 实现Runable接口

2.1 创建线程类

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

public class MyRunable implements Runnable {
    
    
    /**
     * 重写run方法
     */
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            try {
    
    
                //暂停0.5秒,模仿业务逻辑执行
                Thread.sleep(500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "线程开始执行了:" + System.currentTimeMillis());
        }
    }
}

2.2 测试方法

public class Test {
    
    

    public static void main(String[] args) {
    
    

        for (int i = 0; i < 3; i++) {
    
    
            Thread thread = new Thread(new MyRunable());
            thread.start();
        }

    }
}

2.3 查看执行结果

Thread-2线程开始执行了:1605334552337
Thread-1线程开始执行了:1605334552337
Thread-0线程开始执行了:1605334552337
Thread-0线程开始执行了:1605334552845
Thread-1线程开始执行了:1605334552846
Thread-2线程开始执行了:1605334552846
Thread-2线程开始执行了:1605334553357
Thread-0线程开始执行了:1605334553357
Thread-1线程开始执行了:1605334553357
Thread-0线程开始执行了:1605334553869
Thread-2线程开始执行了:1605334553869
Thread-1线程开始执行了:1605334553869
Thread-2线程开始执行了:1605334554373
Thread-1线程开始执行了:1605334554373
Thread-0线程开始执行了:1605334554373
Thread-2线程开始执行了:1605334554876
Thread-1线程开始执行了:1605334554876
Thread-0线程开始执行了:1605334554876
Thread-0线程开始执行了:1605334555391
Thread-2线程开始执行了:1605334555391
Thread-1线程开始执行了:1605334555391
Thread-1线程开始执行了:1605334555906
Thread-0线程开始执行了:1605334555906
Thread-2线程开始执行了:1605334555906
Thread-0线程开始执行了:1605334556409
Thread-2线程开始执行了:1605334556409
Thread-1线程开始执行了:1605334556409
Thread-2线程开始执行了:1605334556910
Thread-1线程开始执行了:1605334556910
Thread-0线程开始执行了:1605334556910

3. 实现Callable接口

在这里插入图片描述

3.1 创建线程类

实现callable接口,并重写cal方法

public class MyCallable implements Callable<Integer> {
    
    
    /**
     * @return
     * 重写call方法
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
    
    

        for (int i = 0; i < 10; i++) {
    
    
            try {
    
    
                //暂停0.5秒,模仿业务逻辑执行
                Thread.sleep(500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "线程开始执行了:" + System.currentTimeMillis());
        }
        return 1;
    }
}

3.2 测试方法

public class Test {
    
    

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
            Thread thread = new Thread(futureTask);
            thread.start();
        }
    }
}

3.3 FutureTask 常用方法

3.3.1 get()方法

该方法会阻塞主线程,一直到call()方法执行完毕

public class Test {
    
    

    public static void main(String[] args) {
    
    
        List<FutureTask<Integer>> futureList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
    
    
            FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
            futureList.add(futureTask);
            Thread thread = new Thread(futureTask);
            thread.start();
        }
        try {
    
    
            Integer callCount = new Integer(0);
            for (FutureTask<Integer> future : futureList) {
    
    
                Integer integer = future.get();
                callCount += integer;
            }
            System.out.println("所有线程执行完毕。总的执行次数为:"+callCount);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
    }
}

3.3.2 get()执行测试

Thread-1线程开始执行了:1605336496803
Thread-2线程开始执行了:1605336496803
Thread-0线程开始执行了:1605336496803
Thread-1线程开始执行了:1605336497311
Thread-0线程开始执行了:1605336497311
Thread-2线程开始执行了:1605336497311
Thread-2线程开始执行了:1605336497837
Thread-1线程开始执行了:1605336497837
Thread-0线程开始执行了:1605336497837
Thread-2线程开始执行了:1605336498359
Thread-1线程开始执行了:1605336498359
Thread-0线程开始执行了:1605336498359
Thread-0线程开始执行了:1605336498865
Thread-2线程开始执行了:1605336498865
Thread-1线程开始执行了:1605336498865
Thread-2线程开始执行了:1605336499369
Thread-0线程开始执行了:1605336499369
Thread-1线程开始执行了:1605336499369
Thread-2线程开始执行了:1605336499871
Thread-0线程开始执行了:1605336499871
Thread-1线程开始执行了:1605336499871
Thread-2线程开始执行了:1605336500375
Thread-1线程开始执行了:1605336500375
Thread-0线程开始执行了:1605336500375
Thread-0线程开始执行了:1605336500879
Thread-1线程开始执行了:1605336500879
Thread-2线程开始执行了:1605336500879
Thread-2线程开始执行了:1605336501384
Thread-0线程开始执行了:1605336501384
Thread-1线程开始执行了:1605336501384
所有线程执行完毕。总的执行次数为:3

3.3.3 cancel()方法

  • 当 FutureTask 处于未启动状态时,执行 FutureTask.cancel()方法将此任务永远不会执行;
  • 当 FutureTask 处于已启动状态时,执行 FutureTask.cancel(true)方法将以中断线程的方式来阻止任务继续进行,如果执行 FutureTask.cancel(false)将不会对正在执行任务的线程有任何影响;
  • 当FutureTask处于已完成状态时,执行 FutureTask.cancel(…)方法将返回 false。
public class Test {
    
    

    public static void main(String[] args) {
    
    
        List<FutureTask<Integer>> futureList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
    
    
            FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
            futureList.add(futureTask);
            Thread thread = new Thread(futureTask);
            thread.start();
        }
        Integer futureCount = 0;
        for (FutureTask<Integer> future : futureList) {
    
    

            if(futureCount == 1){
    
    
                future.cancel(true);
            }
            futureCount ++;
        }
    }
}


3.3.4 cancel()执行测试

没有了线程1

Thread-2线程开始执行了:1605337302642
Thread-0线程开始执行了:1605337302642
Thread-2线程开始执行了:1605337303150
Thread-0线程开始执行了:1605337303150
Thread-2线程开始执行了:1605337303655
Thread-0线程开始执行了:1605337303656
Thread-2线程开始执行了:1605337304172
Thread-0线程开始执行了:1605337304191
Thread-2线程开始执行了:1605337304678
Thread-0线程开始执行了:1605337304716
Thread-2线程开始执行了:1605337305181
Thread-0线程开始执行了:1605337305229
Thread-2线程开始执行了:1605337305689
Thread-0线程开始执行了:1605337305737
Thread-2线程开始执行了:1605337306190
Thread-0线程开始执行了:1605337306237
Thread-2线程开始执行了:1605337306696
Thread-0线程开始执行了:1605337306743
Thread-2线程开始执行了:1605337307198
Thread-0线程开始执行了:1605337307245

4. 线程池创建

4.1 newCacheThreadPool

可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

public static ExecutorService newCachedThreadPool() {
    
    
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

最大线程数:Integer.MAX_VALUE
核心线程数:0
阻塞队列:BlockingQueue 当没有指定时,容量大小 Integer.MAX_VALUE

MyRunable 类

public class MyRunable implements Runnable {
    
    
    /**
     * 实现方法
     */
    @Override
    public void run() {
    
    
        for (int i = 0; i < 2; i++) {
    
    
            /*try {
                //暂停0.5秒,模仿业务逻辑执行
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            System.out.println(Thread.currentThread().getName() + "线程开始执行了:" + System.currentTimeMillis());
        }
    }
}

测试方法

public class Test {
    
    

    public static void main(String[] args) {
    
    

        ExecutorService executorService = Executors.newCachedThreadPool();

        for(int i=0;i<10;i++){
    
    
            try {
    
    
                Thread.sleep(200);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            executorService.execute(new MyRunable());
        }

        executorService.shutdown();
    }
}

测试结果

pool-1-thread-1线程开始执行了:1605340746556
pool-1-thread-1线程开始执行了:1605340746556
pool-1-thread-1线程开始执行了:1605340746760
pool-1-thread-1线程开始执行了:1605340746760
pool-1-thread-1线程开始执行了:1605340746970
pool-1-thread-1线程开始执行了:1605340746970
pool-1-thread-1线程开始执行了:1605340747183
pool-1-thread-1线程开始执行了:1605340747183
pool-1-thread-1线程开始执行了:1605340747416
pool-1-thread-1线程开始执行了:1605340747416
pool-1-thread-1线程开始执行了:1605340747633
pool-1-thread-1线程开始执行了:1605340747633
pool-1-thread-1线程开始执行了:1605340747835
pool-1-thread-1线程开始执行了:1605340747835
pool-1-thread-1线程开始执行了:1605340748038
pool-1-thread-1线程开始执行了:1605340748038
pool-1-thread-1线程开始执行了:1605340748272
pool-1-thread-1线程开始执行了:1605340748272
pool-1-thread-1线程开始执行了:1605340748483
pool-1-thread-1线程开始执行了:1605340748483

结论: 全部都是线程pool-1-thread-1执行的,证明了 如果有,就直接使用。如果没有,就建一个新的线程加入池中

4.2 newFixedThreadPool

创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程

public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

最大线程数:输入固定数量
核心线程数:输入固定数量
阻塞队列:BlockingQueue 当没有指定时,容量大小 Integer.MAX_VALUE

测试方法

public class Test {
    
    

    public static void main(String[] args) {
    
    

        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for(int i=0;i<10;i++){
    
    
            executorService.execute(new MyRunable());
        }

        executorService.shutdown();
    }
}

测试结果

pool-1-thread-1线程开始执行了:1605341196229
pool-1-thread-1线程开始执行了:1605341196230
pool-1-thread-1线程开始执行了:1605341196230
pool-1-thread-1线程开始执行了:1605341196230
pool-1-thread-1线程开始执行了:1605341196230
pool-1-thread-1线程开始执行了:1605341196230
pool-1-thread-2线程开始执行了:1605341196231
pool-1-thread-3线程开始执行了:1605341196231
pool-1-thread-4线程开始执行了:1605341196232
pool-1-thread-5线程开始执行了:1605341196232

结论: 最大创建的线程数是5个

4.3 ScheduledThreadPoolExecutor

创建一个定长线程池,支持定时及周期性任务执行

public ScheduledThreadPoolExecutor(int corePoolSize) {
    
    
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

最大线程数:Integer.MAX_VALUE
核心线程数:输入的
阻塞队列:DelayedWorkQueue 默认16,

测试方法

public class Test {
    
    

    public static void main(String[] args) {
    
    

        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);


        for(int i=0;i<10;i++){
    
    
            scheduledThreadPool.scheduleAtFixedRate(new MyRunable(), 1, 3, TimeUnit.SECONDS);
        }
    }
}

测试结果

pool-1-thread-1线程开始执行了:1605342365607
pool-1-thread-1线程开始执行了:1605342368598
pool-1-thread-2线程开始执行了:1605342371603

结论: 每3秒执行一次

4.4 newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

public static ExecutorService newSingleThreadExecutor() {
    
    
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

最大线程数:1
核心线程数:1
阻塞队列:LinkedBlockingQueue

测试方法

public class Test {
    
    

    public static void main(String[] args) {
    
    

        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for(int i=0;i<10;i++){
    
    
            executorService.execute(new MyRunable());
        }
        executorService.shutdown();
    }
}

pool-1-thread-1线程开始执行了:1605342567332
pool-1-thread-1线程开始执行了:1605342567334
pool-1-thread-1线程开始执行了:1605342567335
pool-1-thread-1线程开始执行了:1605342567335
pool-1-thread-1线程开始执行了:1605342567336
pool-1-thread-1线程开始执行了:1605342567337
pool-1-thread-1线程开始执行了:1605342567337
pool-1-thread-1线程开始执行了:1605342567337
pool-1-thread-1线程开始执行了:1605342567338
pool-1-thread-1线程开始执行了:1605342567338

结论:保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,始终只有一个线程执行

5. 线程池阻塞队列

5.1 ArrayBlockingQueue

ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。

5.2 LinkedBlockingQueue

LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。

5.3 PriorityBlockingQueue

PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。

5.4 SynchronizedQueue

SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成。

6. 自定义线程池

由于Executors 创建的线程池,所使用的的阻塞队列 如:BlockingQueue 容量默认为Integer.MAX_VALUE,经常会造成oom ,所以建议线上自定义阻塞队列,自定义线程池

自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池。
常见的构造函数:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)

public ArrayBlockingQueue(int capacity, boolean fair) {
    
    
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

该队列会创建定长的对象,用非公平重入锁

测试代码

public class Test {
    
    

    public static void main(String[] args) {
    
    

        //自定义阻塞队列,最大容量为10
        BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
        // ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 6, 50, TimeUnit.MILLISECONDS, bq);

        // 创建10个任务
        for(int i=0;i<20;i++){
    
    
            Runnable runable = new MyRunable();
            tpe.execute(runable);
            System.out.println(i+"》》线程池中有效程数:"+tpe.getActiveCount());
            System.out.println(i+"》》当前线程池队列数:"+tpe.getQueue().size()+"\n");
        }

        tpe.shutdown();
    }
}

测试结果

0》》线程池中有效程数:1
0》》当前线程池队列数:0

1》》线程池中有效程数:2
1》》当前线程池队列数:0

2》》线程池中有效程数:3
2》》当前线程池队列数:0

3》》线程池中有效程数:3
3》》当前线程池队列数:1

4》》线程池中有效程数:3
4》》当前线程池队列数:2

5》》线程池中有效程数:3
5》》当前线程池队列数:3

6》》线程池中有效程数:3
6》》当前线程池队列数:4

7》》线程池中有效程数:3
7》》当前线程池队列数:5

8》》线程池中有效程数:3
8》》当前线程池队列数:6

9》》线程池中有效程数:3
9》》当前线程池队列数:7

10》》线程池中有效程数:3
10》》当前线程池队列数:8

11》》线程池中有效程数:3
11》》当前线程池队列数:9

12》》线程池中有效程数:3
12》》当前线程池队列数:10

13》》线程池中有效程数:4
13》》当前线程池队列数:10

14》》线程池中有效程数:5
14》》当前线程池队列数:10

15》》线程池中有效程数:6
15》》当前线程池队列数:10

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.jxs.multithread.threadpool.MyRunable@6f79caec rejected from java.util.concurrent.ThreadPoolExecutor@67117f44[Running, pool size = 6, active threads = 6, queued tasks = 10, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
	at com.jxs.multithread.threadpool.Test.main(Test.java:17)

结论:
核心线程数为3,最大线程数:6 阻塞队列:10
当执行时,先执行核心线程池的中的线程,当超过核心线程池的数量,便会阻塞到 阻塞队列中,当阻塞队列满了,会用最大线程数的数量, 当最大线程也满了,就会报错

猜你喜欢

转载自blog.csdn.net/jinian2016/article/details/109690112