大娃写点什么。


  • 首页

  • 标签

  • 归档

  • 关于

并发漫谈

发表于 2017-06-13

几个基本概念

  • 为什么需要并发?
    这些年。并发。多线程这些词不绝于耳。
    感觉现在没有没有搭上这些词都感觉落后了。但是想专注学一个东西时要先搞清为什么。
    那么。为什么需要并发?
    我看过一些资料上有几个原因。但我个人偏看好以下这点是主要原因。
    由于硬件的发展。近年来的cpu主频已经到达了瓶颈。为了提高性能。而转向多核的集成。这时候把传统串行程序改造成串行+并行的程序(并行部分就能够合理利用多核的优势)。
    那有哪些领域需要这些性能呢?
    毫无疑问,就是计算量大,访问量大的领域需要这些性能。自然而然也就是图像处理和服务端了。
    除了服务端和图像处理领域。其他领域不建议使用并发编程。毕竟并发编程就是典型的把简单问题复杂化的做法。

  • 概念之间的关系

概念关系图

  • 并发、并行
    并发:在单核处理器上实现多线程编程,即多个任务一起执行,表面上我们感受到是一起执行的,但实际上是交替执行的(串行)
    并行:在多核处理器上能把并发上交替执行的部分变成同时执行。

  • 同步、异步
    同步:主线程在同步调用时,只有等到子线程线程完成方法调用返回后,主线程才能开始下面的任务。
    异步:异步调用时,主线程会开辟一个单独的线程来处理这个任务,然后直接处理主线程接下来的任务,而不等待子线程调用返回

  • 同步、互斥
    在操作系统课上。谈到进程时就有讲到进程之间的关系,同步和互斥。作为『轻量级的进程』–线程,同样也有这样的关系。具体概念如下:
    互斥:在一个线程执行某一任务时,不允许其他线程一起来执行这个任务,只能等待他执行完成才能接着执行该任务
    同步:多个线程之间按照规定的次序来执行一项任务,在某一线程运行期间同样具有排他性(即是一种更为复杂的互斥)。这里和上面同步的概念并没有矛盾。只是看待角度不一样。其实是同一个东西。

  • 临界区
    表示一种共享资源/数据。在多线程环境中,临界资源应该只有一个线程能调用他,其他线程必须等待其释放这个资源。

  • 堵塞、非堵塞
    由于临界区的存在,并发需要得到控制。也就有了堵塞的概念。
    堵塞:一个线程被堵塞,那么在其他线程释放资源前,他是无法运行的。
    非堵塞:具有三种级别:无障碍,无锁,无等待。(非堵塞级别逐步增高)。他们之间的差别这里不谈,只是会说下非堵塞是采取某些手段使得临界区资源可以被多个线程同时访问,某种意义上提高了程序的并行比例(注1),从而提高性能。

  • 死锁、饥饿
    由于临界区的存在,线程需要等待,在等待时会出现以下情况:
    死锁:线程a持有临界资源1,并且需要临界资源2才能处理接下来的任务,与此同时,线程b持有临界资源2,并且需要临界资源1才能处理接下来的任务。这个时候两个线程彼此都在等待,就产生了死锁。
    饥饿:解释饥饿需要引入线程的优先级,对于优先级高的线程会被优先处理,而导致优先级低的线程一直处于等待状态。这时候就是饥饿。

多线程的协作

基本操作

  • wait、notify、notifyAll
    在synchronized块中,一个线程A在处理一个临界资源对象object可以调用wait(为了协调多个线程之间的工作),object.wait();这个时候线程A被置入object的等待队列,并且释放object的锁,另外一个线程B由于临界资源的没有被占用,自然也是可以取得该object来做相应的处理,这时候线程B由于业务上的需要可以通知线程A继续任务时,就可以调用object.notifyAll();或者object.notify();(在明确等待队列只有一个时。)。值得注意的是。线程B调用这个函数的同时,并不会马上释放object的锁,而是要等到线程B执行完毕后才会释放,也就是说线程A要等到线程B完全执行完任务后才能被唤醒。

  • interrupt、sleep
    interrupt:一个线程调用这个方法并不会直接使其退出,而是给该线程发送一个退出的消息,至于什么时候退出由线程自己决定。
    sleep:让当前线程休眠一段时间

  • join
    在main函数中,若新建一个线程A并执行,然后要等待A执行完得到某些结果,main函数根据这些结果才接着执行,这时候就可以在main函数中接着调用a.join();。这样就能完成上述需求。
    同样。在A线程需要其他多个线程,例如B,C,D线程的处理结果时,可以在A线程创建好BCD线程并start这些线程后,调用b.join();c.join();d.join();即可

jdk提供的工具

一般开发过程中,不会直接使用上述基本方法,而是直接使用jdk提供的工具。这些工具让我们更好的去处理并发问题。

  • ReentrantLock、Condition
    这两个东西分别代替synchronized和wait,notify。
    在一个实现了Runnable的类A中,可以创建一个ReentrantLock lock,当多个实现类A的线程对象在操作临界资源前,可以使用lock.lock();来加锁,主要要在try-catch-finally块中的finally部分调用lock.unlock();来解锁,否则其他线程无法取得锁进而一直处于等待状态。

在上述情景中,lock.newCondition()可以取得一个Contition c对象,这个对象具有c.await();和c.signal()、c.signalAll()方法,对应wait,notify,notifyAll

  • ReadWriteLock
    读写锁是非堵塞的概念的一个实现,他能够让多个线程访问临界资源时的读与读操作之间不堵塞。但是写-写,读-写,写-读这三个方面仍然是堵塞的。
    1
    2
    3
    ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    lock.readLock();
    lock.writeLock();

可以分别取得读锁和写锁。

具体使用可以参考
Java并发编程实战P236
实战Java高并发程序设计的p85
JDK8 API中java.util.concurrent.locks包下的ReentrantReadWriteLock类中的sample demo
(ReentrantLock的使用和这个没有基本差别,至于Condition部分也请自行找具体demo,在下面参考资料上都有基本的用法实例。之后的内容也一样。)

线程池

为了避免频繁创建和销毁线程所带来的负担,我们可以让创建的线程进行复用。

jdk支持

  • 基本用法:
1
2
3
4
ExecutorService s=Executors.newFixedThreadPool(3);//Executors为工厂
for(int i=0;i<10;i++){
s.execute(new Task());//Task实现了Runnable
}

即在一个3个线程的线程池里复用线程来解决这10个任务。

  • 4种线程池
    public static ExecutorService newFixedThreadPool(int nThreads)
    public static ExecutorService newSingleThreadExecutor()
    public static ExecutorService newCachedThreadPool()
    public static ScheduledExecutorServicenewScheduledThreadPool(int corePoolSize)

  • jdk8有新增:
    Executors工厂

至于这些线程池的说明可以参考其他资料,这里就不赘述了。

内部实现

  • 线程池框架结构图

线程池框架结构图

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
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//jdk8新增
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
public static ExecutorService unconfigurableExecutorService(ExecutorService executor) {
if (executor == null)
throw new NullPointerException();
return new DelegatedExecutorService(executor);
}

从源码和类图可以看到,Executors类要么是调用ThreadPoolExecutor,要么调用ScheduledThreadPoolExecutor来创建以上4个线程池。(jdk8里面新增的线程池是通过DelegatedExecutorService和ForkJoinPool来创建的。)

现在我们着重分析ThreadPoolExecutor的实现:

ThreadPoolExecutor构造器

从ThreadPoolExecutor构造器中我们可以看到他有几个重载形式,其中包含全部参数的构造器代码如下:

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
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}

具体参数分析:
前面4个参数根据javadoc上的注释已经足够了,这里着重看后面两个参数:

  • BlockingQueue workQueue
    这个队列用来存储已提交但并未处理的Runnable对象。至于BlockingQueue有许多类实现其中一个为LinkedBlockingQueue,这个队列如名所述,是一个线程安全的链表实现的队列,也就能够存无限多的Runnable对象,直至资源耗尽。当然也有ArrayListBlockingQueue这个基于数组的实现,也就是等待队列具有最大容量,达到最大容量就执行RejectedExecutionHandler。
    当然,还有其他形式的队列:
    1.比如newScheduledThreadPool中,这个队列传入参数就是DelayedWorkQueue(),这个队列是一个特殊的延迟队列,基于堆的数据结构,在取消该延迟任务所需要的查找能力和移除该任务能力都很出色。也因为实现了BlockingQueue,所以具备应当有的线程安全能力。
    2.在newCachedThreadPool中,这个队列传入参数是SynchronousQueue,这个队列也是一个特殊的BlockingQueue实现,SynchronousQueue没有容量,所提交任务都会直接交于线程池处理。这里的maximumPoolSize被设置为Integer.MAX_VALUE,就是为了防止出现RejectedExecutionHandler。
    3.PriorityBlockingQueue等。。。
  • RejectedExecutionHandler handler
    内置的4种拒绝策略实现如下(解释截取对应实现类的javadoc):
    1.CallerRunsPolicy:A handler for rejected tasks that runs the rejected task directly in the calling thread of the method,unless the executor has been shut down, in which case the task is discarded.
    2.DiscardOldestPolicy:A handler for rejected tasks that discards the oldest unhandled request and then retries , unless the executor is shut down, in which case the task is discarded.
    3.AbortPolicy:A handler for rejected tasks that throws a RejectedExecutionException.
    4.DiscardPolicy:A handler for rejected tasks that silently discards the rejected task.

RejectedExecutionHandler实现

了解了这些参数后再来看下线程池工作的核心代码
ThreadPoolExecutor的execute方法:

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
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

这里对方法内注释做点自己的理解:
1.线程池接受到新任务时,若正在运行的线程数量少于corePoolSize,则将任务通过addWork()直接执行并返回;
2.当workerCountOf(c) > corePoolSize时,会通过workQueue.offer加入等待队列(会做两次检查),但是加入等待队列时有两种情况:a成功加入时,该任务就等待执行,b加入失败时,通过addWorker(null, false);提交给线程池处理,当线程池达到maximumPoolSize就执行RejectedExecutionHandler;若未达到就分配线程来执行。

具备异常堆栈线程池

参考《实战Java高并发程序设计》P113章节

注解

1.在衡量并发程序比之前串行程序性能提高多少时,有一个概念:并发程序并不是每一个部分都能并发的,有涉及到临界区时也是和原来串行一样,所以并发程序=并发部分+串行部分。一般来说,并发部分越多,其性能也就越高。而非堵塞这个机制就是提高并发部分的一个手段。

参考资料

JDK API 并发部分
JDK源码 线程池部分
《Java并发编程实战》
《实战Java高并发程序设计》

图片失效问题

转去简书看吧。自己维护一个图床好麻烦。
http://www.jianshu.com/p/6f70745305e4

嗯哼。

发表于 2017-06-08

也是。经楚囧提醒。一年没有来这了。不得不感慨时间真快。一直以来都觉得时间挺慢的。也很无聊。期间也发生了不少事。也是一言难尽。但也可一言蔽之。

从前种种譬如昨日死,从后种种譬如今日生。
曾国藩

至于什么时候会再来打理这里。只能说看心情。当然作为话痨嘛。总要找个地方呐喊宣泄。但是我要随着我的本心走。也不能因为某些偏好就去做一些无意义或者意义权重小的事。说是那么说。但有个毛病一直没放下,松懈下来时也是经常唠嗑(吃完饭。跑完步。)。于是所见所学所想无处不肆泄。当然了。能找个愿意听的人也不简单。我也是有反省过。然后能意识到时就收敛,对我来说onenote比这里要方便的多。也是我没打理这里的原因。
我是看《暗时间》从而有打算写博客的想法的。毕竟优点很多。
写博客能分享经验心得。但要给人有价值所在。有太多东西要考究的。原理。文章组织结构。代码。等等。虽然能提高自己表达能力,有总结,也能有名声。但是对于我这种在这里写的博文,如果不是我在github的详情上有挂上自己的链接,估计爬虫都进不来我这里。至于总结,onenote够了。

至于为何还要来这里发一篇奇怪的博文。一是这里还是有三两好友或者一些奇奇怪怪的网友会来看。既然如此,我也在此说下。这里会不定期更新(基本等于不会更新,其实我前面的博文也确实太水太水了。也没什么亮点值得别人追的)。其次,要为这里负责。作为自己取的一个好听的名字。作为大娃写字的地方。也是要发挥出价值的。。
此外。要是以前的我肯定把这里打扫干净(毕竟一大堆黑历史。)但是现在我必当竭尽所能写好每一篇博文。愿能舒缓你之前的恶心感。

以上。
祝好。

LightsTodo(2)

发表于 2017-01-12

这个项目到现在还没有一个像样的版本出来。也是自己拖延的锅。途中有不少技术难点把自己弄得很烦躁。虽然有很多都是隔三差八的解决。但更多的只是在搁置着。
此外。在一次吃饭时说到LightsTodo。也遇到了需求更改的问题。结合了其他人的意见还有自己的一些想法。现说明新特点是什么。

阅读全文 »

Lights to do

发表于 2016-06-12

本来想安安稳稳的睡觉的。可是我它丫的躺在床上愣是睡不着。
也许是大晚上的还在喝茶的缘故。也有可能是因为想到这个项目有点小激动。算了,本来打算明天才写的需求现在不妨赶出来了吧。顺道喝多一杯茶。

名称:Lights to do

目的

设计出一个todo应用,本来是觉得手头上的那个todo用着感觉不太好用,因为自己很少用手机(即使有,也是为了打发时间,或者有事,也自然而然的不去看这个todo list,即使它安然的放在手机主页上。好吧。我承认这是因为我拖延症而找的借口。)。

阅读全文 »

《错不在我》(1)

发表于 2016-06-12

序言

『一定是有人出错了。』

  • 从出生开始,我们每个人都会有自我辩解的冲动,我们会为自己那些错误的行为推卸责任。无论这个行为是大抑或是小事,我们都很难说出『我错了,我犯下一个可怕的错误』这类话,而且只要涉及到的情感、金钱和道义方面所冒的风险越高,讲出这句话的难度就越大。
  • 不仅如此,大多数人在面对所犯错误的证据时,不仅不会改变自己的观点或行为方式,反而会顽固地对其加以辩护。
  • 我们之所以能够适应不愉快的人际关系,是因为毕竟已经这样生活了相当长的时间。我们可以在很长时间里保持一种麻木的工作方式,是因为我们会寻找各种理由为此辩护,而且无法对放弃这种工作方式的好处进行清晰的评估。
  • 当我们与朋友或者亲属之间有隔阂时,我们会将自己视为和睦的守护这–只要对方能够赔礼道歉便消除这个隔阂。
  • 我们每个人都会划出自己的道德底线,并对其加以辩护。

上面是序言中提出的一些论点,在提出这些论点的途中也举了不少例子或者说是论据来支撑这些论点。比如:『你是否在缴纳个人所得税时做过手脚?你可能会用忘记了法定缴税额当作接口,或者你会认为,其他人都这样子,那我不做就是一个傻瓜。』、『你可曾有过未申报一些额外现金收入的情况?考虑政府机构的腐败,你有权这样做』之类的例子。但有一些论据看起来是那种抓住人性的软弱点来举证的,而不是实验,不过这样也能让人更加的感同身受,毕竟大多数人的思维还是停留在『文科生思维』,尤其是对待生活方面,包括我,第一遍看到这些句子的时候也是在想自己有无这些举措,当然了,人非圣贤,孰能无过,也因为这样,才让这个论据更有说服力而不用借助于论证。
也更能吸引读者去读下文,毕竟知道了自己的问题,总要寻医问药,苦苦挣扎下吧?
BTW。作为一个拖延症患者,自己老实讲也是为了『寻医问药』才来看这本书的(好吧。其实更大的原因是今天重新翻看《暗时间》时看到的)。
当然。即便书中在我看来有此缺憾,但并不妨我们了解这个普遍发生的『事实』,如若下次遇到,抿笑而过便是。
文中还提到了『所有的辩护都是正确的,只要我们跨越了这些底线(指做出「错误」的行为并做出辩护),我们便会为这些确认是错误行为进行辩护,这样我们就可以继续将自己视为诚实的人,而不是罪犯或者是窃贼。无论像挪用公款这样的大事,还是旅馆床罩滴上墨水这点小事,自我辩护的机制都是一样的。』
这个观点很有意思,可以说是一针见血,毕竟人活着就是不断强调自己的三观,如果自己三观崩塌了,人不久就失去了生的希望了丫,所以用自我辩护来麻醉自己有时候也是很有必要的。
文中也提到了记忆往往会受到自我助长偏见的影响,让罪过变轻,让时间的边缘变得模糊,令真实发生的一切出现扭曲。用了一个丈夫和妻子分别承担多大比重的家务的例子,如你所见,例子中丈夫和妻子肯定是都会让自己的功劳变大,让自己罪过变小,以至于两人的比例总和会较大幅度的超过100%。以此证明每个人都按照对自己有利的方式进行回忆。
对于我这个轻微『处女座』的人来说,这个在我面对我极想为此树立好印象抑或是好榜样的时候,我便会暴露的无比明显。希望自己在此后发作时能够更好的意识到『人非圣贤』,敢于承担错误并且提出怎么弥补这个错误远比自我辩护来得更有效率。

阅读全文 »

schedule4HR(1)

发表于 2016-04-06

背景

做个课表吧。

这个想法也算是和朋友在闲聊时的儿戏话所铸就的。虽然心里早已受不了世面上课表app的社交圈。各种消息推送。。
并且自己的需求也只是想要一个干净,整洁的课表罢了。

需求

既然是做给自己用的。也是个实践自己所学知识的试验田。那么需求就可以任性一点。满足自己要求即可。(可惜自己的需求和客户一样是时不时抽风想改变的自我,在面对像面条一样的代码不想改变需求的本我以及傲娇到最后老老实实地去码代码的超我之间有着现代办公室上演的客户,码农以及项目经理之间的爱恨情仇。。。)
捂脸。。

阅读全文 »

安利一个离线API查看软件--Zeal

发表于 2016-04-04

快速配置

资源

  • 官网
  • 各类IDE的插件
  • docset的feed (一般用不上。需要时相信你会记得这里有个东西。)
阅读全文 »

如果你是哈克。你会选择什么?

发表于 2016-03-27

刚看完一集冬瓜片。《传颂之物·虚伪的假面》。到了25集。也算是道出了其名字的涵义。
但主角哈克的做法。。实在是不敢恭维。虽然。
但如果给我选。
我。

阅读全文 »

Hello Hexo.我的第一个Blog.

发表于 2016-03-26

这段时间感觉自己应该做点什么。起因也算是一个很好很好的朋友砥砺了吧。他目前在做一个学校批的项目,就整个学期一直在拼。虽不是同一个专业,但空闲时自己时不时感受到自己没学到什么,即使学业上没落下,也有自己想法和路线:Android和算法,并且在这条路上也不断走。对。仅仅是走,相比其他人来说,自己的成长的速度可谓是沧海一粟。

”优秀的人太多太多了,而他们还在不停的跑

这类型的话也听过不少,但之前也不以为然,总觉得自己有自己的想法就好。

阅读全文 »
theoneLee

theoneLee

顺便发个呆。

9 日志
3 标签
GitHub
© 2017 theoneLee
由 Hexo 强力驱动
主题 - NexT.Pisces