并发编程式JAVA的一个相对较复杂的模块。纵观各招聘网站的要求,纷纷要求要具备高并发,高性能开发经验。这一块的知识,不得不学啊。学习并发有断断续续,有很长一段时间了,相关并发书籍也看了不少。感觉有些收获,特开系列文章,分享下自己的学习收获。
线程的内容涉及的内容特别多,仅几篇文章不可能透彻阐明,个人又不喜欢弄个大块头文章,所以还是做个系列文章吧。作为系列的第一篇,本章主要内容:
线程VS进程
并行VS并发
线程的创建和启动
user thread VS daemon thread
返回结果类型的线程
join操作
1.进程 &线程
进程
进程有独立的地址空间,对应于系统的程序和软件。比如QQ,word java tomcat web应用,一个进程崩溃,不会影响其他进程。属于重量级的资源管理方式。进程切换时,耗费资源较大,效率要差一些。
线程
而线程只是一个进程中的不同执行路径。一个进程,可以包含多个线程。线程有自己的堆栈和局部变量,而多个线程共享内存。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。一般情况下main()作为应用程序的入口。属于轻量级别的资源管理方式。
2.并行VS并发
并发:一个处理器同时处理多个任务。通过CPU分片执行任务,属于逻辑上的同时发生。比如:一个人同时吃三个馒头。
并行:多个处理器或者是多核的处理器同时处理多个不同的任务。物理上的同时发生。比如:三个人同时吃三个馒头。
并行的前提:具备多个处理器和多核的处理资源。
并发的前提:在资源CPU能力一定的基础上,区分I/O密集型任务,CPU密集型任务,尽量减少CPU等待时间,压榨CPU的处理极限。
JAVA领域,涉及的都是并发。
3.线程的创建和启动
JAVA的线程有2中方式
1.实现 java.lang.Runnable接口
2.继承java.lang.Thread类
HeavyWorkRunnable.java,实现 java.lang.Runnable接口
package com.threadexample.base; public class HeavyWorkRunnable implements Runnable { @Override public void run() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Doing heavy processing - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
MyThread.java,继承java.lang.Thread类
package com.threadexample.base; public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println("MyThread - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("MyThread - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
测试代码ThreadRunExample
package com.threadexample.base; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadRunExample { public static void main(String[] args){ Thread t1 = new Thread(new HeavyWorkRunnable(), "t1"); Thread t2 = new Thread(new HeavyWorkRunnable(), "t2"); System.out.println("Starting Runnable threads"); t1.start(); t2.start(); System.out.println("Runnable Threads has been started"); Thread t3 = new MyThread("t3"); Thread t4 = new MyThread("t4"); System.out.println("Starting MyThreads"); t3.start(); t4.start(); System.out.println("MyThreads has been started"); ExecutorService executorService = Executors.newCachedThreadPool(); Thread t5 = new Thread(new HeavyWorkRunnable(), "t5"); HeavyWorkRunnable t6 = new HeavyWorkRunnable(); executorService.submit(t5);//启动 executorService.execute(t6);//启动 executorService.shutdown(); } }
代码展示了2种创建线程的方式,以及多种启动方式。需要注意的是ExecutorService API,ExecutorService 的设计是为了尽可能实现线程的复用,因为线程也会占用资源,线程复用可以降低资源占用,提升性能,是并发优化的一个途径,当然使用ThreadFactory的工厂,为线程管理提供了便利。
Runnable vs Thread
1.不仅仅提供执行任务,还提供额外功能,实现 java.lang.Runnable接口
2.如果类的唯一功能是作为线程执行,可以继承java.lang.Thread
需要注意,thread 提供的方法没有返回值。
------------------------------
执行结果
Starting Runnable threads
Runnable Threads has been started
Starting MyThreads
MyThreads has been started
MyThread - START t3
Doing heavy processing - START t2
MyThread - START t4
Doing heavy processing - START t1
Doing heavy processing - START pool-1-thread-1
Doing heavy processing - START pool-1-thread-2
MyThread - END t3
Doing heavy processing - END t1
MyThread - END t4
Doing heavy processing - END t2
Doing heavy processing - END pool-1-thread-1
Doing heavy processing - END pool-1-thread-2
通过结果分析:线程实际执行结果,和程序编写顺序不一致。而且多次运行执行的结果也不同。这就是多线程,执行结果的不确定性。
4.user thread VS daemon thread
对上面的ThreadRunExample ,进行DEBUG跟踪。
threads有些是自己创建的,有些不是自己创建的,如Finalizer线程,负责垃圾收集线程,是虚拟机自动创建的线程。
导出thread信息,部分信息截图
通过两个线程对比,Finalizer是daemon线程,优先级是8. 属于 “user thread"
我们创建的线程不是daemon线程,优先级是5.属于"daemon thread".
所谓守护线程(daemon thread),是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于退出时机的差别:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
JavaDaemonThread.java代码
package com.threadexample.base; public class JavaDaemonThread { public static void main(String[] args) throws InterruptedException { Thread dt = new Thread(new DaemonThread(), "dt"); dt.setDaemon(true); dt.start(); //continue program Thread.sleep(30000); System.out.println("Finishing program"); } } class DaemonThread implements Runnable{ //此处是个死循环(如果不是daemon线程,他会一直执行下去,永不退出; //对于daemon线程,它会随user thread一并退出 @Override public void run() { while(true){ processSomething(); } } private void processSomething() { try { System.out.println("Processing daemon thread"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
执行结果
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Finishing program
5.返回结果类型的线程(Callable Future)
下面是Runnable 接口.执行方法没有返回值。
public interface Runnable { public abstract void run(); }
如果在面对执行计算任务是,需要有返回值怎么办?
也是可以的,以前面的例子稍加改造。
HeavyWorkRunnableWithResult.java
package com.threadexample.base; public class HeavyWorkRunnableWithResult implements Runnable { private String result; public String getResult(){ return result; } @Override public void run() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); this.result="执行结果="+Thread.currentThread().getName(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Doing heavy processing - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
测试代码ThreadRunExampleWithResult.java
package com.threadexample.base; public class ThreadRunExampleWithResult { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult(); Thread t1 = new Thread(runnableWithResult,"t1"); t1.start(); System.out.println("输出结果:"+runnableWithResult.getResult()); } }
打印结果
------------------------
Runnable Threads has been started
输出结果:null
Doing heavy processing - START t1
Doing heavy processing - END t1
--------------------------
经测试结果不是我们想要的结果,返回值为空。这是因为在打印输出结果时,线程还没有执行完。
怎么修复这个问题呢。
升级后的HeavyWorkRunnableWithResult.java,添加了个完成标记字段。
package com.threadexample.base; public class HeavyWorkRunnableWithResult implements Runnable { private String result; private volatile boolean done=false; public String getResult(){ return result; } public boolean isDone(){ return this.done; } @Override public void run() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); this.result="执行结果="+Thread.currentThread().getName(); this.done=true; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Doing heavy processing - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
注意一定要将done 设置为volatile 类型,否则main线程永远不会读取不到更新的值。关于volatile
,又涉及到底层的JMM模型了,不在此涉及了。
改造后的ThreadRunExampleWithResult.java,加个循环,保证有结果之后打打印。
package com.threadexample.base; public class ThreadRunExampleWithResult { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult(); Thread t1 = new Thread(runnableWithResult,"t1"); t1.start(); while(!runnableWithResult.isDone()){ //空循环 } System.out.println("输出结果:"+runnableWithResult.getResult()); } }
执行结果
-----------------------------
Runnable Threads has been started
Doing heavy processing - START t1
Doing heavy processing - END t1
输出结果:执行结果=t1
-----------------------------
仔细想想,我们做了好多无用的事,比如完成判断,循环等待。JAVA专为解决此类问题,提供了Callable接口。
public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
HeavyWorkCallable.java 作为HeavyWorkRunnableWithResult 的Callable版本
package com.threadexample.base; import java.util.concurrent.Callable; public class HeavyWorkCallable implements Callable<String> { @Override public String call() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); return "执行结果="+Thread.currentThread().getName(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
ThreadRunExampleCallable.java 测试类
package com.threadexample.base; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; public class ThreadRunExampleCallable { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); //Get ExecutorService from Executors utility class, thread pool size is 10 ExecutorService executor = Executors.newFixedThreadPool(5); //create a list to hold the Future object associated with Callable List<Future<String>> list = new ArrayList<Future<String>>(); //Create MyCallable instance Callable callable = new HeavyWorkCallable(); for(int i=0; i< 10; i++){ //submit Callable tasks to be executed by thread pool Future<String> future = executor.submit(callable); //add Future to the list, we can get return value using Future list.add(future); } for(Future<String> fut : list){ try { //print the return value of Future, notice the output delay in console // because Future.get() waits for task to get completed System.out.println(new Date()+ "::"+fut.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } //shut down the executor service now executor.shutdown(); // System.out.println("输出结果:"+runnableWithResult.getResult()); } }
执行结果如下
-----------------------
Runnable Threads has been started
Doing heavy processing - START pool-1-thread-1
Doing heavy processing - START pool-1-thread-2
Doing heavy processing - START pool-1-thread-3
Doing heavy processing - START pool-1-thread-5
Doing heavy processing - START pool-1-thread-4
Doing heavy processing - START pool-1-thread-3
Sun Jun 26 17:41:51 CST 2016::执行结果=pool-1-thread-1
Doing heavy processing - START pool-1-thread-5
Sun Jun 26 17:41:57 CST 2016::执行结果=pool-1-thread-2
Doing heavy processing - START pool-1-thread-1
Sun Jun 26 17:41:57 CST 2016::执行结果=pool-1-thread-3
Doing heavy processing - START pool-1-thread-4
Doing heavy processing - START pool-1-thread-2
Sun Jun 26 17:41:57 CST 2016::执行结果=pool-1-thread-4
Sun Jun 26 17:41:57 CST 2016::执行结果=pool-1-thread-5
Sun Jun 26 17:41:57 CST 2016::执行结果=pool-1-thread-3
Sun Jun 26 17:42:03 CST 2016::执行结果=pool-1-thread-1
Sun Jun 26 17:42:03 CST 2016::执行结果=pool-1-thread-5
Sun Jun 26 17:42:03 CST 2016::执行结果=pool-1-thread-2
Sun Jun 26 17:42:03 CST 2016::执行结果=pool-1-thread-4
Process finished with exit code 0
-----------------------
6.join操作
Java Thread join方法可以用于直到指定线程死亡时停止当前线程。有三个重载方法
public final void join()
public final synchronized void join(long millis): 停止当前线程直到线程死时或者超过millis时间。
public final synchronized void join(long millis, int nanos)
我们可以对5节中的ThreadRunExampleWithResult,进行改造下,达到想要的效果
package com.threadexample.base; public class ThreadRunExampleJoin { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult(); Thread t1 = new Thread(runnableWithResult,"t1"); t1.start(); // while(!runnableWithResult.isDone()){ // //空循环 // } try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("输出结果:"+runnableWithResult.getResult()); } }
join方法一般使用在“直到....才”的场景。和自带的CountDownLatch工具类有重合的部分。
输出结果
----------
Runnable Threads has been started
Doing heavy processing - START t1
Doing heavy processing - END t1
输出结果:执行结果=t1
---------
引用资源
http://www.journaldev.com/1024/java-thread-join-example
http://www.cnblogs.com/luochengor/archive/2011/08/11/2134818.html
http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html
最后
本文主要讲了线程的几点用法,比较简单。
Runnable ->Thread->Daemon ->Callable-->Join。捎带着提了下violatile关键字。
由于线程间没有共享变量,所以不涉及到一丁点线程安全的东西。后面专门抽出几篇讲解下。
附件为代码