今天整理一下java的多线程部分的基础知识
废话篇 今年7月中旬的时候从我的大学毕业了,这一段时间在家里带了几天娃,希望我的博客不倒,小外甥女长大能看到。(笑哭
前一段时间学习了Java的多线程,Java多线程编程的基础内容并不难,但是刚学完多线程我对这个多线程有了一些疑问——Java的多线程程序在计算机中究竟是怎么运行的呢?带着这个疑问开始了下面的探寻之路。
Java多线程编程回顾 java的多线程在Java8中总共有3种实现方式,即通过继承thread类,实现Runable接口,使用callable、线程池executor、future来创建可以返回值的线程。下面简单看一下这三者实现的一个demo。
1、继承thread类来创建线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class ThreadMethod extends Thread { private String name; ThreadMethod(String name) { this .name = name; } @Override public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println(name + ":" + i); } try { sleep((int ) (Math.random() * 10 )); } catch (InterruptedException e) { e.printStackTrace(); } } } class TestMain1 { public static void main (String[] args) { ThreadMethod threadClass1 = new ThreadMethod("AA" ); ThreadMethod threadClass2 = new ThreadMethod("BB" ); threadClass1.start(); threadClass2.start(); } }
根据Java的单继承模式(即一个类只能继承一个类),所以如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话(因为java的接口是多继承关系,即一个类可以实现多个接口),这样则很容易的实现资源共享(这里的资源共享是什么意思 ? )。
2、实现Runable接口来创建线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class RunnableThreadMethod implements Runnable { private String name; RunnableThreadMethod(String name) { this .name = name; } public void run () { System.out.println(Thread.currentThread().getName()+"运行开始" ); for (int i = 0 ; i < 10 ; i++) { System.out.println(name + ":" + i); if (i==8 && Thread.currentThread().getName().equals("Thread-0" )){ System.out.println("yield start" ); Thread.yield(); } } try { sleep((int ) (Math.random() * 10 )); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行结束" ); } } class TestMain2 { public static void main (String[] args) { System.out.println(Thread.currentThread().getName()+"运行开始" ); RunnableThreadMethod runnableThreadMethod1 = new RunnableThreadMethod("C" ); RunnableThreadMethod runnableThreadMethod2 = new RunnableThreadMethod("D" ); Thread thread1 = new Thread(runnableThreadMethod1); thread1.start(); new Thread(runnableThreadMethod2).start(); try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行结束" ); } }
3、使用call、线程池executor、future来创建可以返回值的线程 如果对一些场合需要线程返回的结果。就要使用用Callable、Future、FutureTask、CompletionService这几个类。Callable只能在ExecutorService的线程池中跑,但有返回结果,也可以通过返回的Future对象查询执行状态。 Future 本身也是一种设计模式,它是用来取得异步任务的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class CallThreadMethod { static class CallThread implements Callable <Object > { private String taskName; CallThread(String name) { this .taskName = name; } @Override public Object call () throws Exception { System.out.println(Thread.currentThread().getName() + " start" ); int sum = 0 ; Date date1 = new Date(); for (int i = 0 ; i < 10 ; i++) { sum = sum + i; System.out.println(taskName + ":" + i); } Date date2 = new Date(); long time = date2.getTime() - date1.getTime(); System.out.println(Thread.currentThread().getName() + " end" ); return sum; } } public static void main (String[] args) throws ExecutionException, InterruptedException { int taskSize = 2 ; ExecutorService executorServicePool = Executors.newFixedThreadPool(taskSize); Callable callThreadMethod1 = new CallThread("AAa" ); Callable callThreadMethod2 = new CallThread("BBA" ); Future future1 = executorServicePool.submit(callThreadMethod1); Future future2 = executorServicePool.submit(callThreadMethod1); System.out.println("通过方式1获得的10的和为:" + future1.get().toString()); System.out.println("通过方式1获得的10的和为:" + future2.get().toString()); List<Future<Object>> futureList = executorServicePool.invokeAll(asList(new CallThread("AAAA" ), new CallThread("BBB" ))); executorServicePool.shutdown(); for (Future<Object> item : futureList) { System.out.println("通过方式2获得的结果:" +item.get()); } } }
下面再说一说这三者的共同点和差异。
1、继承thread类和实现runnable接口的两种方式最终都是通过调用thread类的start方法启动线程的; 2、前两者(thread、runnable)都不能返回结果; 3、runnable实现的多线程 最终还是归一到thread上,这就表明runnable实现的多线程对象的方法是和thread实现的多线程对象的方法api是一致的; 4、单继承机制导致多线程类不能继承多个类,而runnable是一个接口类,因此可以实现类继承一个类和多个接口; 5、call方法可以返回结果,是使用了线程池,是继承了callable()接口,因此可以像runnable方法一样实现继承多个类。 6、call方法开启线程有两种方式,一种是使用submit方法接收CallThread的具体对象,一种是使用invokeAll方法接收一个CallThread对象列表。
如果需要线程返回结果,那么就选择call方式;如果不需要结果,那么有两种选择,那么建议使用runnable或者call方式。
Java多线程的进一步思考 针对java的多线程,是不是这样写出来的代码就一定是并行运行的?
回想了操作系统相关的知识,进程是操作系统分配资源的最小单位,线程是操作系统调度的最小单位,那么就是说CPU资源是按照线程进行调度的,我们再回到目前的多核多线程的概念中去,目前购买CPU的时候,都会有一个双核4线程、4核8线程这种说法,就是说一个线程运行在CPU的核心上,这样的一般都是一个核心上运行2个线程。对于4核4线程,意味着该CPU可以同时运行4个线程,就是我们写一个4线程程序的时候,如果只有这一个程序使用,理想情况下CPU会全部为该程序服务。也就是说程序(进程)并发,线程在多核心CPU上是并行运行的,多线程程序在单核心单线程的CPU上是并发运行的。
Google一下“CPU多核多线程”keyword,可以搜到一堆关于这方面的讨论,不清楚的话可以多看几篇文章。
项目的源码已开放在github上,项目地址:https://github.com/flowerlake/java-learning ,这个项目包含了学习java的所有代码,感谢star。
参考文献