JUC并发编程
进程与线程:
-
1、什么是JUC:
java.util.concurrent
实质上是 java.util工具包
Rannable 没有返回值,效率相比于Callable相对较低! -
2、线程和进程:
进程:一个程序,一个进程包含多个线程!
java默认有几个线程:2个线程(main线程和GC线程)
线程:对于java而言thread、Runnable、Callable
java开不了线程
:java无法直接操作硬件,调用的本地方法,底层的C++。 -
3、并发、并行
并发(多线程操作同一资源)
·cpu一核,模拟出来多条线程,快速交替。
并行(多个人一起走路)
·CPU多核,多个线程可以同时执行;线程池
并发编程的本质:充分利用CPU的资源
-
1、线程的状态:
新生;NEW
运行;RUNNABLE
阻塞;BLOCKED
等待(死死的等);WAITING
超时等待;TIMED_WAITING
终止;TERMINATED -
2、wait/sleep:
·来自不同的类:wait->object;sleep->Thread。
·关于锁的释放:wait会释放锁,sleep睡觉了,抱着锁睡觉的。
·使用范围:wait必须在同步代码块中使用;sleep任意地方。
·是否需要捕获异常:wait不需要,sleep需要。
lock锁(重点)与synchronize锁:
线程就是一个单独的资源类,没有任何的附属操作。
传统的synchronize锁本质:队列,锁
- 1、lock接口:
公平锁:十分公平,可以先来后道,排队;
非公平锁:可以插队;(默认)
lock.lock()//加锁
try{
//业务代码
}
- 2、 lock三部曲:
new Reentrantlock();
lock.lock()//加锁
finally=>lock.unlock 解锁
- 3、Synchronize与Lock 的区别:
1、Synchronize内置的java关键字;Lock是一个java类。
2、Synchronize无法判断获取锁的状态;Lock可以判断是否获取到了锁。
3、Synchronize会自动释放锁;lock必须要手动释放锁!如果不释放,死锁。
4、Synchronize 线程1(获得锁,阻塞)而线程2(等待,傻傻的等);Lock锁不一定会等待下去(try)。
5、Synchronize 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)。
6、Synchronize 适合锁少量的代码同步问题;lock锁适合大量的同步代码。
锁是什么,如何判断锁的是谁:
8锁现象:
深刻理解什么是锁:
8锁就是关于锁的8个问题。
-
1、标准情况下,两个线程先打印 发短信 还是打电话:1/发短信 2/打电话;
-
2、发短信方法延时4s下,两个线程先打印 发短信 还是打电话:1/发短信 2/打电话;
-
3、增加了一个普通方法后,后面的调用普通方法,发短信方法延时4s:1/hello 2/发短信;
-
4、两个对象,两个同步方法,发短信方法延时4s:1/打电话 2/发短信;
-
5、增加两个静态的同步方法,只有一个对象,加入static关键字发短信方法延时4s:1/发短信 2/打电话;
-
6、增加两个静态的同步方法,两个对象,加入static关键字发短信方法延时4s:1/发短信 2/打电话;
-
7、一个静态的同步方法一个普通同步方法,发短信方法延时4s:1/打电话 2/发短信;
-
8、两个对象,一个静态的同步方法一个普通同步方法,发短信方法延时4s:1/打电话 2/发短信;
Synchronize锁的对象是方法的调用者,两个方法使用的是同一个锁,谁先拿到,谁就执行。
普通方法没有锁,不受锁的影响。
两个对象两个调用者,两把锁。
static 静态方法,类一加载就有了!锁的是class(全局唯一)。
小结:
- new this 具体的一个手机
- static class 唯一的一个模板
同步方法锁的是方法的调用者,静态同步方法锁的是class类模板。
生产者消费者问题:
线程交替执行 A B 操作同一个对象
单例模式、排序算法、生产者消费者、死锁。
if循环:一次性判断
使用while循环,防止虚假唤醒
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
只要是并发一定要有锁。
集合类:
集合类不安全:
-
List不安全:
1、并发下的Arraylist 不安全:Arraylist jdk1.2 出的。
解决方案:
使用 Vector jdk 1.0 出的,add方法上添加了Synchronize关键字;
使用Collections.synchronizedList(new ArrayList())解决;
使用 new CopyOnWriteArrayList解决。
底层使用transient volatile 关键字封装的数组。
CopyOnWrite:写入时复制
COW 计算机设计领域的一种优化策略。
多个线程调用的时候,list,读取的时候,固定的 写入(覆盖),在写入的时候避免覆盖,造成数据问题。
CopyOnWriteArrayList 比 Vector 好在哪里:
有Synchronize 关键字的方法效率都比较低。 -
Set不安全:
CopyOnWriteArraySet:
解决方式:
使用Collections.synchronizedSet(new HashSet())解决;
使用 new CopyOnWriteArraySet解决。
同上;
ConcurrentModificationException:并发修改异常; -
HashSet底层是什么?
底层 new HashMap,使用了kay不重复,add方法底层调用HashMap的put方法。 -
HashMap不安全:
解决方式:
使用Collections.synchronizedMap(new HashSet())解决;
使用 new ConcurrentHashMap 解决。
加载因子:
初始容量: -
Callable:
1、可以有返回值;
2、可以抛出异常;
3、方法不同,run()/call()。
怎么启动Callable:通过Runnable的实现类FutureTask(因为FutureTask的构造方法既可以传递Callable 接口 也可以传递Runnable 接口)调用对象,
FutureTask 的 get()方法可以获取到 Callable 的返回值。get方法可能会产生阻塞,把它放到最后,活着使用异步通信来处理。
有缓存;
结果可能需要等待,可能会阻塞。
常用的辅助类:
-
CountDownLatch
:(减法计数器)
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。 这是一个一次性的现象 - 计数无法重置。
原理:
countDown() // 数量-1
await() //等待计数器归0,然后向下执行
每次有线程调用countDown 数量-1 ,await就会被唤醒,继续执行。 -
CyclicBarrier
(int,Runnable):(加法计数器)
await() //等待计数器到int,然后向下执行 -
Semaphore
(默认线程数量)限流:
Semaphore(信号量)
一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。
acquire()//得到,假设如果满了,等待被释放为止。
acquire()//释放,会将当前信号量释放+1,然后唤醒等待的线程。
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
读写锁:
- 读写锁:
读可以被多线程同时读;
写的时候只能有一个线程去写。