并发编程基础

java 并发编程基础:

第一章

多线程的隐患:1 安全性  ( 什么意思?正确性)2 性能问题  3 活跃性问题: 死锁

第二章


安全性问题出现的原因?

1 对象状态在线程之间共享了2 并且对象的状态是可变的。3 并且没有使用同步


安全性问题解决办法?

1 :  不共享该变量 2 : 对象设置为不可变 3 : 使用同步


同步机制有四种?

1 synchronized 2 volatile 3 显示锁4 原子变量


原子变量机制解决的问题?

在只有一个状态 的类中,只要状态时原子类(AtomicInteger 等),就实现了类的线程安全。

原子变量机制的原理?

通过把一个状态 所在的内存加锁,达到多个线程访问同步的效果,从而避免了安全性的问题。

注意问题:

原子变量机制在在不变性条件涉及多个变量的时候,就无效了,

具体场景:一个原子变量改变要求另外一个原子变量同时改变,这是一个原子操作,

但是原子变量只能保证自己的同步,无法保证自己和另外的变量的原子操作。

这个时候需要在多个变量进行操作的时候进行锁机制 同步。

volatile  为什么可以实现同步?

因为volatile 标示的变量,在改变的时候,直接改变的内存中的值,而不是

线程中 副本的值。

多线程间操作同一个对象的时候,会在不同的线程内部存一份对象的副本,如果修改完毕了,

再去修改内存中的值。volatile 没有副本,直接修改内存的值了。

volatile 变量的限制:只有满足以下条件时候才可以使用volatile

变量。

1 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。

2 该变量不会与其他变量 一起 纳入不变性条件中。

3 在访问变量时候不需要加锁。

为什么线程之间会共享对象?

jvm 中创建一个对象是存储到了堆内存中,这部分区域,包括栈内存等都是整个程序(也就是整个进程共享的),如果这个程序里,有好多线程,

那这些线程自然会共享这些对象,举例说明:


两个线程,一个设置 b = 9

一个设置 b =8 

其实他们操作的都是同一个栈的同一个块,

这样,如果是多个线程,同时从一个块中取数据,并且更新数据为 +1 的值,

有这种情况,两个线程同时 取到 8 ,同时设置 数据为9 ,

这就不会达到想要的效果了,

所以线程需要同步执行(给内存块加锁,一个线程执行的时候,另外一个线程不允许执行)


 堆内存也是一样的道理,如果主线程创建了一个对象,而这个对象在多个 线程之间共享了,

那他们同时操作这个对象的时候,也需要同步。

原则:

1设计线程安全的类时,良好的面向对象技术、不可修改性,以及明晰的不变形规则都能起到一定的帮助作用。

2在线程安全类中封装了必要的同步机制,因此客户端不需要进一步采取同步措施。

3无状态的对象一定是线程安全的。

4 在不变性条件涉及多个变量的时候,各个变量之间并不是彼此独立的,而是某个变量的值会对其他变量的值产生约束,

因此,当更新某一个变量时,需要在同一个原子操作中对其他变量同时进行更新。

如果只修改了其中一个变量,那么在这两次修改操作之间,其他线程将发现不变性条件被破坏了,

同样我们也不能保证会同时获取两个值,在线程a 获取这两个值的过程中,线程b 可能修改了他们,这样

线程a 也会发现不变性条件被破坏了。这个时候的解决办法:内置锁。

由于每次只能有一个线程执行内置锁保护的代码块,因此,由这个锁保护的同步代码块会以原子方式执行,

多个线程在执行该代码块时候也不会相互干扰。

5 对于包含多个变量的不变形条件,其中涉及的所有变量都需要由同一个锁来保护。

6 每个共享的和可变的变量都应该只有一个锁来保护,从而使开发人员知道是哪一个锁。

7 对于可能被多个线程同时访问的可变状态变量,在访问他时候都需要持有一个锁,

在这种情况下,我们称状态变量时由这个锁保护的。


第三章:

共享和发布对象:

从而使他们能够安全的由多个线程同时访问。

发布:使对象能够在当前作用域之外的代码中使用。

共享:使多个线程可以同时访问该对象。

如何发布?

1 将一个指向该对象的引用保存到其他代码中可以访问的地方。

2 在某一个非私有的方法中返回该引用。

3 将引用传递到其他类的方法中。

4 将对象的引用保存到一个公有的静态变量中。

逸出:

某个不该发布的对象被发布了,称为逸出。


同步的作用:原子性,内存可见性。

内存可见性:一个线程更改变量值以后,其他多个线程可以看到变量的最新值。

内存可见性解决办法:

volatile 变量

synchronized 

volatile 变量的限制:只有满足以下条件时候才可以使用volatile

变量。

1 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。

2 该变量不会与其他变量 一起 纳入不变性条件中。

3 在访问变量时候不需要加锁。

不可变对象可以通过任何机制来发布。

事实不可变的对象必须通过安全方式来发布

可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。

安全发布的常用模式:

1 在静态初始化函数中初始化一个对象引用。

2 将对象的引用保存到volatile 类型 的域或者AtomicReferance 对象中。

3 将对象的引用保存到某个正确构造对象的final类型域中。

4 将对象的引用保存到一个由锁保护的域中。


静态

安全是因为静态初始化 由jvm 在类的初始化阶段执行,由于在jvm内部存在着同步机制,因此

通过这种方式初始化的任何对象都可以被安全发布。



在没有额外的同步情况下,任何线程都可以安全的使用被安全发布的事实不可变对象。


线程封闭:

1 局部变量:又称为栈封闭

2 ThreadLocal 类


第四章  对象的组合

前面介绍的是如何构建一个线程安全的类。

这里介绍如何将多个线程安全的类组合成更大的 线程安全组件或者程序。

判断一个类是否线程安全?

1 找出 构成对象状态的所有变量。

2 找出约束状态变量的不变形条件。

3 建立对象状态的并发访问管理策略。

并发访问管理策略:即 同步策略。

它定义了如何 在不违背对象不变形条件或后验条件的情况下对其状态的访问操作进行协同。

还规定了如何将不可变形,线程封闭与加锁机制等结合起来以维护线程的安全性,

并且规定了哪些变量由哪些锁来保护。

不变形条件与后验条件的管理:原子性与封装性。

先验条件的管理: 

1 等待与通知机制

2 使用现有类库的类:

blocking  queue 和semaphore 等。

如果一个类不是多线程安全的类,那么可以采用以下机制,使他可以在多线程环境中使用。

1 将对象封闭在 线程中。

2 通过一个锁保护该对象的所有访问。

如何让一个类变为线程安全的?

状态变量 myset 私有属性  封闭在类中。

唯一可以访问他的两个方法也被加锁了,

所以这个类是线程安全的。



3 私有锁实现:

给对象内部状态变量加锁,优点:

1 锁 被封装起来,客户代码无法得到锁。

2 客户代码可以通过共有方法访问锁,进行同步。

3 只需要验证 单个类,就知道锁是否被正确使用。

4 不会产生活跃性问题。



4 委托机制实现线程安全类:

1 两个状态变量 locations 是 本就是线程安全的。

2 unmodifiableMap 也是不可变的,所以这个类本身就不需要额外的加锁机制来实现同步了。

委托的意思就是 将当前类的加锁机制 细化到状态变量所在的类中,

状态变量是安全的了,当前类也就安全了。


 

如果委托给了多个状态变量,并且变量之间有约束条件,这个时候:

如果某个类含有符合操作,那么仅靠委托并不足以实现线程安全性在这种情况下,

这个类必须提供自己的加锁机制以保证这些符合操作都是原子操作。


发布状态变量:


在现有的线程安全类中

添加功能: 

示例如下,在线程安全对象list 所在的类中添加如果不包含就添加的功能。

三种方法:

第一种:继承原有的类,然后添加功能方法。



第二种方法

:写一个客户端类

最好的总是留在最后

,第三种:组合

第五章: 基础构建模块:


1 同步容器类:

同步容器类问题:

同步类容器如果执行复合操作,例如(如果没有就添加),需要客户端加锁。

2 并发容器:解决了复合操作的问题,效率较同步容器有所提高。



他们实现的原理是什么?



新增的并发容器类有哪些?

1 :Jdk 1.5 里:

1 ) 新增了queue 和 blockingQueue 两个接口队列


blockingQueue 接口的实现:




2)新增了ConcurrentMap  ,ConcurrentHashMap  代替同步Map  HashMap

ConcurrentSkipListMap 和 ConcurrentSkipListSet  分别代替 同步的SortedMap 和 SortedSet 

实现并发的原理:



3) 新增了 copyOnWriteArrayList 代替 list

4) 新增了deque  和blockDeque 


BlockingQueue 适合生产者----消费者  模式。

BlockingDeque 适合工作密取模式。


3 阻塞和中断


1)线程阻塞类型,如何重新唤起?




Future.get()获取执行结果,如果这些阻塞方法被中断,那么它将努力提前结束阻塞状态。


2) 如何中断一个线程,Thread 提供了interrupt方法。

当A线程调用interrupt 方法中断B线程的时候,B线程如果正在执行,它会找到一个适合自己停止的地方,然后停止正在执行的任务,

如果B线程正处于阻塞状态,比如说B线程正在sleep 或者因为take 或者put 或其他原因

而处于阻塞状态,这个时候 B 线程的代码就会抛出一个InterruptedException ,

B线程原有的代码里应该有处理这个异常的代码,一般处理这个异常有两种方法:


如上图所示,线程捕捉到了interruptedException ,这个时候

线程想正常退出,需要自己调用interrupt 方法使自己退出。

3)阻塞和中断的关系?

程序阻塞是不可以避免的,是因为外部资源,或者时间问题,造成的阻塞。

但是阻塞的久了,不想线程继续阻塞,怎么办,需要中断机制interrupt(),让线程停止。

线程阻塞的久了,如果另外一个线程的资源释放了,可以调用 notify() 方法,

唤醒阻塞线程。



4 同步工具类:


它是指那些可以根据自己的状态来协调线程的控制流的类。

例如BlockingQueue和BlockingDeque和信号量(Semaphore)和栈栏(Barrier)和 闭锁(Latch) 


1)闭锁作用:确保某些活动直到其他活动都完成后才继续执行。

2)futureTask

3)semaphore 信号量


4) 栈栏barrier

实例

最优化的缓存实现:

总结:

1 为什么要多线程? 为了高效利用cpu,高效的完成任务。

2 多线程之间会共享类,如何实现一个线程安全的类?同步,volatile ,atom 和显示锁。

3 已经提供好的工具类在concurrent 包里。

4 哪些工具类可以协调线程执行? blockingqueue blockDeque 信号量Semaphore

,countDownLatch ,cylicBarria,

5 线程创建消耗大量资源,如何避免?线程池executor

6 对于消耗大量时间的任务怎么办? 使用 future  callerable 

她们之间的关系:在线程中会有线程安全的类,类中会包含任务,线程会放到线程池中管理。

7   线程池中的线程,也可以用blockingqueue blockDeque 信号量Semaphore

,countDownLatch ,cylicBarria  来协调,如何协调?


这些工具可以声明为 final 的,然后作为参数,传递到线程中,执行任务的之前,或者之后,

就可以使用 工具协调线程了。


第六章  任务执行

任务通常是一些抽象的且离散的工作单元。

多任务的优点

1 简化程序的组织结构

2 提供一种自然的事物边界来优化错误恢复过程

3 提供一种自然的并行结构来提升并发性。

任务边界:以独立的客户请求为边界。

线程处理任务的方式:

1 串行处理

2 建立多个线程 分别处理

如果线程过多会有缺点:

1 线程生命周期的开销非常高。

2 过多的线程等待处理会导致内存资源消耗          

3 稳定性(如果线程过多,会导致内存溢出) 

Executor 框架:


任务是一组逻辑工作单元,而线程则是使任务异步执行的机制。

如下例所示:任务是 handleRequest(connect);

线程是task ;在task 的run 方法里运行任务。



但是任务类型太多太多了,所以就把任务封装到了Runnable 的run方法里,以便统一任务类型。

这样 Executor 调用execute 方法就可以把任务(Runnable )  提交给执行线程了。


如下例所示:task 里封装了实际的任务,task 作为一个参数传递到了exec 的执行线程池里了。


 

上图所示还有另外一个要点: 

Executor 将任务的提交 与 执行 解耦开来,所以只要更换一个Executor具体实现,就可以改变服务器处理任务的行为。改变Executor 实现或者配置所带来的影响要远远小于改变任务提交方式带来的影响。通常Executor的配置是一次性的,因此在部署阶段可以完成,而提交任务的代码却在不断地扩散到整个程序中,增加了修改的难度。


 

 

Executor具体实现:





Executor执行周期,

下图代码是带有执行周期的 实例。





Future Executor callable 使用:



 

CompletionService 接口的出现是为了方面 执行完多个任务后,获取结果用的。、





 

指定时间内

获取结果答案:如果获取到了,返回正确答案,否则抛出timeoutException  




 


第七章:取消与关闭 

本章

学习目的:安全 结束 线程或者任务的方法。

 



下面是例子:


 

由此出现了

第二种 取消  协作 机制:中断



第八章:

1 线程池调优

1) 线程池大小 根据cpu 个数设置。

2)除了Executors 的静态方法 newFixedThreadPool 和newCachedThreadPool newSingleThreadPoolExecutor newScheduledThreadPool 提供的线程池以外,还可以 使用 ThreadPoolExecutor 自定义各种类型的线程池。

构造函数如下:



管理队列任务:

有限的线程池中会限制可并发的执行的任务数量,如果任务过多,就会导致线程池里的线程不够用,怎么办?

threadPoolExecutor 提供了一个blockingQueue 来保存等待执行的任务。








饱和策略:


 

实现一种平缓的性能降低。




线程池扩展,添加日志统计等可以 继承 threadPoolExecutor





9 死锁:

1 死锁的原因:








 

10 显示锁:

比较 :





显示锁使用:







 


定时所:



 


 



 


 



 读写所使用 :





 

11 synchronized 

1 同步对象:

 一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

     二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

     三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

     四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

2  同步类:

在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。

Class Foo 

{

    public synchronized static void methodAAA()   // 同步的static 函数 

    { 

        //…. 

    }

    public void methodBBB() 

    {

       synchronized(Foo.class)   // class literal(类名称字面常量)

    } 

}

3 同步代码块:

同步块,示例代码如下:

public void method3(SomeObject so)

{

    synchronized(so)

    { 

       //….. 

    }

}

 


 什么情况 需要加锁?

两个线程会操作同一个对象,并且会改变对象的状态。



 


 


 


 



















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值