首页 > 并发

这书怎么说呢,讲的挺浅的,而且主要是无意义的代码片段贴的过多了。

很多知识点比如说书中的synchronized关键字讲解、wait notify等方法讲解,明明梳理知识点讲完就完事了,偏偏要贴几十页代码,有水字数嫌疑。

贴代码也就算了吧,还贴的是eclipse的截图代码,也不知道该说什么。

 

本来看synchronized关键字的时候还以为会讲讲jvm实现方式、对象头储存的数据、monitorenter、monitorexit指令之类的。

结果啥都没讲。Lock类也就讲ReentrantLock、ReentrantReadWriteLock的使用,我也以为会有实现、机制、AQS的讲解来着。

每天早上起床看半小时书,就这本看完感觉没学到啥= =

我估摸着是给没怎么接触过java多线程的人群看的,可能不太适合有多线程编程经验的职业人士。

 

最后在吐槽一句: 这些多线程相关的类的使用方法,我不会看API嘛,闲得蛋疼慢慢看这书

阅读全文

看下这样一段代码:

我在一个方法中建立了个死循环,循环的判断条件为一个boolean类型的成员变量。

然后在main线程中,创建了一个新的名为 “t1” 的线程,去执行这个方法。

等待一秒后,mian线程自身,将该成员变量的值改为false,试图使其不满足条件从而循环终止。

按照正常的逻辑来说,按照脑海中预演的情况来说,应该是没问题的。

可是执行后却没有得到想要的结果,控制台输出 “start” 后再无反应,程序一直没有终止,即 死循环没有退出。

 

想要让程序正确执行的话,将定义语句 boolean flag = true; 改为 volatile boolean flag = true;  程序就可以正确执行,这是为什么呢?

 

这里要涉及一点 java 内存模型,JMM。

JMM 里头,他有分配一块内存,叫主内存。类比于操作系统中的主存,主要存放线程间的共享数据(除去方法参数、局部变量)。

然后,每个线程在执行的过程当中,都有一个WorkingMemory,是主内存中部分数据的副本,只能该线程访问,类比于硬件的高速缓存。线程对数据的访问通常只能在workingMemory中进行,不能直接与主存交互。

比如cpu运行多个不同的线程的话,每个线程都有一个WorkingMemory,cup就是将主内存里边的数据读过来读到WorkingMemory里,然后再进行修改,比如+1+1+1+1,修改完后,再给他写回内存去。

 

我上边写的代码,这几个线程是怎么执行的呢?

在我的代码中,flag 是存在于堆内存中的 v 对象中。

当线程 t1 开始跑后,它首先会从主内存中把这个 flag 变量给它 copy 到自己的工作内存里边,然后开始运行。在cpu处理的过程之中,由于这cpu处理这个线程的部分他非常的忙,一直在while循环嘛,就不再去读主内存里边的数据了,一直在读自己的缓冲区(线程的工作内存)。

main 线程,改flag的参数,也是把 flag 参数 true 给读到自己的工作内存里边,然后进行修改,修改完事后,发现自己工作内存中的数据和主存中不一致,于是将自己工作内存中的数据给他刷新到主存中去。

这个时候就有问题了。第一个线程没有在主存里重新读啊?所以,这时候第一个线程就结束不了。

 

 

volatile 关键字功能及作用:

简单的来说,volatile 关键字,主要功能是使一个变量在多个线程间可见。

volatile关键字修饰的变量一旦在主存中被改变时,就会通知别的使用到该变量的线程:你们的缓冲区中的内容过期了,需要再重新刷新一下。

以上面那个代码例子来说 ,这样定义语句 volatile boolean flag = true;

当 main 线程修改主内存中 flag 的值时。这个时候, t1 线程,就会去重新读一遍主存,刷新自己的缓冲区(工作空间)。此时 t1 线程中的 flag 刷新为 false ,故循环停止。程序运行完毕。

 

虽然 volatile 用起来比较简单,但是该关键字背后代表的逻辑还是很深的。

 

 

还有个点要注意:

volatile并不能保证多个线程共同修改某一变量时所带来的不一致问题,也就是说 volatile 不能代替 synchronized。

当然, volatile 的效率高 synchronized 很多倍。但是该上锁的时候也只能上锁。

synchronized 是 既有可见性,又保证原子性。而 volatile 只保证可见性。synchronized的实现也是比 volatile 更重的。

 

 

关于 synchronized  本来也有一篇文章要写的。想想还是算了,本来想写点加锁释放锁的原理、可重入性质和可见性和原理啥的。

不过我过了归纳的那一段时间了,现在想归纳总结一通怪麻烦的。反正这些知识脑子里都有,就不记录了。

 

阅读全文

CyclicBarrier这个类的字面意思是循环屏障,跟CountDownLatch有些像,但不一样。关于CountDownLatch我在该爬虫项目中使用过,没有单独的文章进行讲解。

CyclicBarrier跟CountDownLatch的区别是:
CountDownLatch只计数1次
CyclicBarrier可以通过reset()重置计数,实现更复杂的业务,也会在其等待完毕释放锁后重置计数。

说明:
CyclicBarrier有2个构造
CyclicBarrier(int parties) 设置一个任务的参与数
CyclicBarrier(int parties,Runnable barrierAction) 设置一个任务的参与数和优先执行的barrierAction

步骤:
1、设置任务参与数,每个参与数达到相应阶段后,执行await方法。
2、当最后一个参与者进入await的阶段后,则停止阻塞

注意: barrierAcction屏障动作是由最后一个达到的子线程执行的

 

可以看我下面的代码,以学生去郊游为例,展示了其线程阻塞和可循环使用的功能:

 

输出语句为:

12 学生 已达到学校门口
13 学生 已达到学校门口
11 学生 已达到学校门口
14 学生 已达到学校门口
15 学生 已达到学校门口
15~~大家都到齐了,出发干下件事咯
15 学生 已经坐上 旅游大巴
13 学生 已经坐上 旅游大巴
14 学生 已经坐上 旅游大巴
11 学生 已经坐上 旅游大巴
12 学生 已经坐上 旅游大巴
12~~大家都到齐了,出发干下件事咯
12 学生到达景点
13 学生到达景点
15 学生到达景点
11 学生到达景点
14 学生到达景点
14~~大家都到齐了,出发干下件事咯触发

 

的确实现了线程等待其他线程完成的功能

注意观察线程ID,可以发现每次触发 barrierAction 屏障动作都是由最后一个线程来执行的

阅读全文

在实际业务中进行部分功能的开发时,不可避免的会遇上算数运算、计数等操作。

最典型的表现就是代码中一个又一个的 i++ (或者 num++ 之类的) 、i – – 等自增自减运算。

 

在普通的应用中当然可以这样。但是如果是一个上线的业务,一定会遇到并发访问的情况。

或许是多个线程(比如web请求)对某一方法中的公有变量进行增减。

也或者是实现功能的线程需要对某个值进行动态更改并且获取。

 

如果只是使用普通的加减运算,数量较少还好。但是如果进行的计算比较多、线程也比较多的话,那么普通的加减运算则不能做到足够精准。

java 便在 java.util.concurrent.atomic 包下提供了几个原子操作类比如 AtomicBoolean、AtomicLong、AtomicInteger。

这里主要说 AtomicInteger,其他的也差不了多少,去翻一翻 API 就好啦

 

JDK1.6 的 API 对该类的描述(最新的汉化版就是 1.6的 啦) 可以看一下:

可以用原子方式更新的 int 值。有关原子变量属性的描述,请参阅 java.util.concurrent.atomic 包规范。AtomicInteger 可用在应用程序中(如以原子方式增加的计数器),并且不能用于替换 Integer。但是,此类确实扩展了 Number,允许那些处理基于数字类的工具和实用工具进行统一访问。

 

AtomicInteger类主要方法:

 

建议在多线程环境下将

int i = 1;

i++;

替换为

AtomicInteger atomicInteger = new AtomicInteger(1);

atomicInteger.getAndIncrement();

 

阅读全文
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00