Java与Spring开发中的并发(concurrency)
今天来讲一下Java和Spring开发中需要重点关注的并发(concurrency)。本文选自BitTiger软件工程师项目实战精品课课程内容。
谈到并发,可以分为线程(thread)和进程(process)两种模式,而当我们在谈论Java的并发的时候,一般都是在讲线程的并发。
初见线程
Java中主要通过主线程来创建其他线程,每一个线程都是和Thread类的实例相关联的。有两种基本的管理Thread对象的方式:
1.“亲力亲为”:
在应用程序需要发起异步任务的后,直接控制线程的创建并对其进行管理。
2.“甩手掌柜”:
把应用程序的任务交给执行器(executor),这样可以将对线程的管理从程序中抽离出来。
创建线程
从无到有,我们当然需要创建一个线程,Java中提供了两种方法:
1.实现一个Runnable接口(Implement Runnable Interface)
2.继承一个线程类(Extend Thread Class)
一般情况下,推荐使用第一种方式,因为java不允许多继承,因此实现了Runnable接口的类可以再继承其他类。
线程创建完了以后,就需要进行管理。正如前文所说,有两种管理方式,第一种“亲力亲为”的方式需要在自己写代码对线程从创建到销毁的全部处理,当然也是可行的,但是太麻烦了。所以一般倾向于采用“甩手掌柜”的方式,让执行器去管理线程的生命周期。
要想启动线程需要两步走。首先,重写 (override)Runnable接口的run方法,实现需要实现的功能,其次调用start方法来真正启动线程。如下图所示:
1. public classHelloRunnable implements Runnable {
2. publicvoid run() {
3. System.out.println("Hello from a thread!");
4. }
5. publicstaticvoid main(String args[]) {
6. (new Thread(newHelloRunnable())).start();
7. }
8. }
暂停线程
有时候模拟位置变更的线程,不需要每时每刻都在变更位置,而只是需要有一定时间间隔后再来更新位置,那就要用到sleep方法来使线程挂起一个指定的时间段。
我们来看一个例子:
1. publicclass SleepMessages {
2. publicstaticvoid main(String args[])
3. throws InterruptedException {
4. String importantInfo[] = {
5. "Mares eat oats",
6. "Does eat oats",
7. "Little lambs eat ivy",
8. "A kid will eat ivy too"
9. };
10. for (int i = 0;
11. i < importantInfo.length;
12. i++) {
13. //Pause for 4 seconds
14. Thread.sleep(4000);
15. //Print a message
16. System.out.println(importantInfo[i]);
17. }
18. }
19. }
中断处理
细心的同学可能已经发现了,上面的代码里面throws后面跟着一个异常类型InterruptedException。试想一下,一个线程本来很舒服地睡在那里(处于挂起状态),突然被暴力丢了出去(中断),心中一定会千万只草泥马奔腾而过。这个抛出的InterruptedException异常这个线程最后的呐喊,作为开发者的我们就需要处理。这个例子里面就简单的抛出异常,没有特殊处理。
深入Java源码,我们会发现在这个sleep方法的实现的时候我们看到用native来修饰,这也就意味着执行sleep方法不仅仅会挂起当前的Java线程,也会挂起对应的操作系统线程。
同步的问题
多线程不可避免的会遇到同步的问题,这也是许多语言选择单线程的原因。虽然单线程相对来说同步的问题,但对于很多CPU密集型操作性能不佳。
1. class Counter {
2. private int c = 0;
3. publicvoid increment() {
4. c++;
5. }
6. publicvoid decrement() {
7. c--;
8. }
9. publicvoid value() {
10. return c;
11. }
12. }
上面是一个经典的例子,基本上只要讲到Java多线程都会举这个例子。在多线程编程过程中必须考虑到同步的问题。由于多线程中,线程访问的顺序是不定的,而自增和自减都不是线程安全的,如果不做同步,就可能导致不一致的情况。
为了使操作结果同步,可以给方法加上synchronized修饰,这样就可以让一个线程在操作数据的时候,加上一把锁,直到处理完才释放。可以将代码修改如下:
1. class Counter {
2. private int c = 0;
3. public synchronizedvoid increment() {
4. c++;
5. }
6. public synchronizedvoid decrement() {
7. c--;
8. }
9. public synchronizedvoid value() {
10. return c;
11. }
12. }
线程安全其他方法
通常情况下,在Java里面,自增和自减都不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作保证是原子操作(Atomic Action)的。如果不想手动处理同步问题,可以使用自带原子属性的原子变量(Atomic Variable),比如AtomicInteger和AtomicBoolean。当你需要自增的时候,就调用AtomicInteger的incrementAndGet()方法就可以实现线程安全的自增了。
也可以通过定义不可变对象(Immutable Object)来实现线程安全。首先,不提供“setter”方法,其次所有的Field都定义为final和private,然后,不让子类重写方法,把类定义为final。总之,让你的对象状态就是一旦被初始化,就不能被改变。
Execute方法
如果一个线程很早就创建了,但一直处于闲置状态,就需要使用Java提供的execute()方法来真正让线程执行起来。
上图定义了一个ExecutorService,执行submit后,如果线程没有挂起,资源也可用,就会执行起来了,否则就暂停。
Spring中的使用
首先是Spring提供的Task Scheduler任务调度器,来运行未来的定时任务,允许按照时间间隔,日期时间安排任务来执行任务。
还有一个是Spring中的AsyncTaskExecutor。假设需要启动5个线程去执行,并且想要这5个线程异步执行,就可以在Spring中实现AsyncTaskExecutor这个接口,调用submit方法,把线程对象传进来,返回Future。Future意味着这个任务当前还没有执行,是一个未来的状态。如果现在线程池的资源满足task需求的话,就会立即被执行,可以使用Future的get方法去获取返回值,如果没有资源来执行或者还处于执行状态,Future会返回null。
刚刚提到的Future,可以调用get方法获取返回值,如果你的task是一个长时间运行的作业,就要小心使用了,因为调用get方法后他会等到你返回结果了以后再结束。
那么接下来,我们应该如何让多个线程之间高效协作呢?又应该如何灵活使用volatile?
这些知识点我们都会明晚正式开课的BitTiger软件工程师项目实战精品课中为大家深入讲解,我们还将免费开放第一周正式课程(共四节课八小时),详细报名方式参见下图,你也可以点击阅读原文了解课程,抓紧最后机会吧!
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。