Java多线程(JUC)
- 线程基本知识
-
- 一、线程与进程
- 二、线程安全与不安全
- 三、CopyOnWriteArrayList — 读写分离,写时复制
- 线程进阶
-
- 一、多线程8锁
- 二、生-消模型
- 三、控制线程顺序
- 四、读写分离-ReadWriteLock
- 五、主线程等待 — CountDownLath
- 六 、循环屏障 — CyclicBarrier
- 七 、信 灯 — Semaphore
- 八 、Callable
- 九 、阻塞队列 — BlockingQueue
- 十 、线程池
- 十一、手写线程池
线程基本知识
一、线程与进程
1、线程与进程是什么
进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
2、线程能做什么
一个进程中可以有多个线程,也就是说,在一个进程运行时,会有很多属于这个进程的线程为其干活,而计算机中的线程是采用时间片轮转的方式来获取时间片,如线程A得到了时间片执行了0.03秒,线程A暂停,线程B得到了时间片,执行了0.001秒,时间片在分给别的线程。因此给用户造成了一个假象,就仿佛是几个任务在同时运行一样。这样的好处是,如果一个任务极其占用时间,会阻塞其他任务,因此采用这种方案可以防止堵塞,也可以增加CPU的利用率。
3、举个例子
用QQ打个比方,我们说QQ主程序是一个进程,这个进程中包含聊天线程,下载线程,视频线程,用户在使用的时候,可以后台下着软件的同时聊着天。
再比如,我们使用IDEA写代码的时候,会自动提示,自动判断语法错误,也就是说,在我们敲代码的同时,后台还有提示以及差错线程在为我们服务。
二、线程安全与不安全
我们都知道ArrayList是线程不安全的,可能有点人说,我平时用ArrayList用的挺好啊, 没发现什么不对啊, 但是当你用多个线程同时操作ArrayList的时候,就会发现结果是不正确的,有时候ArrayList里面存储的是NULL,有的时候会少存储一个,甚至会 ConcurrentModificationException异常。
那么ArrayList不安全谁安全呢,肯定有人说Vector啊,Vector是线程安全的,没错,我们深究底层,发现Vector的构造方法调用this(10)重载构造方法,说明Vector初始大小是10,而体现在线程安全的代码如下图所示。
Vector添加方法
Vector是ArrayList的上一代,Vector牺牲了性能保证了安全性,ArrayList牺牲了安全性保证了性能。
其实还有一种方案,就是通过 将ArrayList转换成线程安全的对象。
那上面的方案看起来换汤不换药,那有没有更好的方案去解决高并发呢,首先给大家引入一个重要概念读写分离,写时复制
三、CopyOnWriteArrayList — 读写分离,写时复制
读写分离,写时复制听起来好像不太好懂,我们拿上课签到举例子:
需求:现在有一张空名单,来的人要将自己的名字写上去完成签到。
方案一:采用线程不安全的方案
小三正在纸上写着自己的名字,由于想签到的同学(线程)太多了,没有老师来限制他们,他们就疯的一般上前去签到,小三刚刚写完 “ 小 ” 字,就被被人抢走了,导致数据出现错误。
方案二:采用加锁方案
小三正在纸上写着自己的名字,由于想签到的同学(线程)太多了,每个人都想先签到,但是老师让他们在门外等着,只有小三签到以后他们才能签到。也就是说小三签到的时候,别人看不到已签到名单(写则不能读)。
方案三:读写分离,写时复制。
我们都知道,只有写操作才会对资源造成修改,但是读不会对资源造成修改,所以我们就将已签到名单粘在墙上,同学们都可以看墙上的名单(并发读),当有同学要签到的时候,将墙上的名单复制(别管怎么复制,这只是例子,他有超能力)一份,把自己的名字写在复制版名单上(加锁写),在此期间并没有影响其他同学读名单,然后在将墙上的替换掉。这就是读写分离,写时复制。
CopyOnWriteArrayList就是采用这种思想,在实际实现中,我们发现只需要将替换成之后就可以解决高并发产生的问题。
但是我们不知仅仅满足解决这个问题,也不能满足仅仅懂得读写分离,写时复制的思想,我们还应该了解CopyOnWriteArrayList的底层源码。
CopyOnWriteArrayList 构造方法
CopyOnWriteArrayList基本的组成部分我们看了,再看看关键的add()方法。
CopyOnWriteArraylist add方法
主类(仅供参考)
题目:多线程8锁
1、 标准访问,当邮件和短音都有锁的时候,请问先打印邮件还是短信br> 答:先打印邮件,后打印短信
结论:一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
2、 邮件新增暂停4秒钟的方法,请问先打印邮件还是短信br> 答: 先打印邮件后打印短信。
结论: 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
3 、 新增普通的hello方法,请问先打印邮件还是hello
答:先打印邮件在打印hello
结论: 加个普通方法后发现和同步锁无关
4、 有两部手机,请问先打印邮件还是短信br> 答:先打短信后打邮件
结论: 换成两个对象后,不是同一把锁了,情况立刻变化,谁先执行完就先打印谁,不冲突。
5、 两个静态同步方法,同一部手机,请问先打印邮件(静态)还是短信br> 答:先打邮件后打短信,既谁先抢到谁就锁。
结论:见(6)
6 、两个静态同步方法,2部手机,请问先打印邮件还是短信br> 答:先打邮件再打短信
结论: synchronized是实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
(1)对于普通同步方法,锁是当前实例对象等同于同步方法块。
(2) 对于同步方法块。锁的是Synchonized括 里配置的对象。
(3)对于静态同步方法,锁是当前类的Class对象本身,
7 、1个静态同步方法,1个普通同步方法,1部手机,请问先打印邮件(静态)还是短信br> 答:先打短信在打邮件
结论: 见(8)
8 、1个静态同步方法,1个普通同步方法,2部手机,请问先打印邮件还是短信br> 答:先打短信在打邮件
结论:所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class,具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后。
二、生-消模型
题目
现在两个线程,可以操作初始值为零的一个变量, 实现一个线程对该变量加1,一个线程对该变量减1,实现交替,来10轮,变量初始值为零。
资源类(仅供参考)
主类(仅供参考)
public class ThreadWaitNotifyDemo{ public static void main(String[] args)throws Exception {AirConditioner airConditioner = new AirConditioner();new Thread(() -> { for (int i = 1; i 10; i++) { try {Thread.sleep(200);airConditioner.increment(); } catch (Exception e) {e.printStackTrace(); } }},"A").start();new Thread(() -> { for (int i = 1; i 10; i++) { try {Thread.sleep(300);airConditioner.decrement(); } catch (
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!