JAVA多线程 – JUC

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进行处理,非常感谢!

上一篇 2021年1月6日
下一篇 2021年1月6日

相关推荐