一、什么是JUC?
java原生的并发包一些常用工具类
二、进程线程基础回顾?
1、什么是进程和线程
进程:java.exe
线程:打字,自动保存…
总结:一个进程包含多个线程,一个进程至少有一个线程,java程序至少有两个线程:GC,Main
2、什么是并发和并行
并发:多个线程操作同一资源,交替执行的过程。
并行:多个线程同时执行,只能在多核CPU下完成。
总结:使用多线程或者并发编程的目的是为了提高效率,让CPU一直工作,得到最高处理性能。
3、线程有几种状态(通过源码分析,6种)
Q:java可以创建线程吗? A:不行!
public enum State {
//新建
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//延迟等待
TIMED_WAITING,
//终止
TERMINATED;
}
4、wait和sleep区别
注:休眠使用TimeUnit.SECONDS.sleep,和Thread.sleep没区别,单位精确!
区别 | wait | sleep |
---|---|---|
类不同 | Object | Thread |
是否释资源 | 释放(睁眼谁) | 不释放(抱着锁睡) |
使用范围不同 | wait和notify一组,一般在线程通信的时候用 | sleep就是一个单独的方法,在哪都可以用 |
关于异常 | 可以不用捕获 | sleep需要捕获异常 |
三、Lock锁?
1、企业级开发需要遵循
- 架构:高内聚,低耦合
- 套路:线程操作资源类,资源类是单独的
1>、传统多线程
//反例!!!禁用
public class demo0011 {
public static void main(String[] args){
//代理模式,Ticket1被Thread代理了后启动 Ticket1被强绑定,无法复用
// 1、不满足高内聚低耦合 2、不满足线程操作资源类
new Thread( new Ticket1(), "A").start();
}
}
//单独的资源类 方式 属性 高类聚 低耦合
class Ticket1 implements Runnable{
@Override
public void run() {
}
}
//正例 满足1、高内聚 低耦合 2、线程操作资源类
public class demo01 {
public static void main(String[] args){
Ticket ticket = new Ticket();//资源类
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <= 10; i++) {
ticket.saleTiket();
}
}
}, "A").start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <= 10; i++) {
ticket.saleTiket();
}
}
}, "B").start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <= 10; i++) {
ticket.saleTiket();
}
}
}, "C").start();
}
}
//单独的资源类 只有方法,属性 可复用
class Ticket{
private int number = 30;
public synchronized void saleTiket(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"线程卖出第:"+(number--)+" 还剩:"+number);
}
}
}
2>、使用JUC后多线程(优化)
/**
* 学习了JUC后
* lock锁+lambda表达式
*/
public class demo02 {
public static void main(String[] args){
Ticket2 ticket = new Ticket2();
//lambda表达式 (参数)->{具体的方法实现}
new Thread(()->{for (int i = 0; i < 20; i++) {ticket.saleTiket();}}, "A").start();
new Thread(()->{for (int i = 0; i < 20; i++) {ticket.saleTiket();}}, "B").start();
new Thread(()->{for (int i = 0; i < 20; i++) {ticket.saleTiket();}}, "C").start();
}
}
//单独的资源类 方式 属性 高类聚 低耦合
class Ticket2{
private int number = 30;
//ReentrantLock 可重入锁 默认非公平锁
private Lock lock = new ReentrantLock();
public synchronized void saleTiket(){
lock.lock();
try {
if(number>0){
System.out.println(Thread.currentThread().getName()+"线程卖出第:"+(number--)+" 还剩:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
2、synchronized和lock锁区别
区别 | synchronized | lock |
---|---|---|
表现 | 是一个关键字 | 是一个对象 |
是否可尝试获取锁 | 无法尝试获取锁 | 可以尝试获取锁,判断 |
释放锁 | 会自动释放(a线程结束,b线程异常,也会释放) | 需要手动释放,否则死锁 |
阻塞是否一直等待 | 线程A(获得锁如果阻塞)线程B(等待,一直等待) | 尝试获取锁,失败后就放弃 |
是否公平锁 | 一定是非公平 | 默认是非公平,可以通过参数设置为公平 |
适用场景 | 适合代码量较小的同步问题 | 代码量特别大,使用lock实现精准控制 |
3、什么是可重入锁(ReentrantLock)?
可重入锁是如果有多个锁,只要进最外面的锁,就会获得里面的所有锁,比如:ReentrantLock是可重入锁
举例理解:回家 进门 里面还有(卧室门,厨房门,书房门…)
4、什么是公平锁和非公平锁?
公平锁:公平(只能排队,后面的线程无法插队)。
非公平锁:不公平(插队,后面的线程可以插队)。
ReentrantLock默认是非公平锁,synchronized是非公平锁。
5、其他小结?
- lock可尝试获取锁
- lock锁上锁一定要解锁且一定要包try catch,在final里解锁,否则死锁。
四、生产者消费者问题?
注:线程之间本来不能通信,但是有时候需要线程之间可以协调操作,变引出生产者消费者问题
1、传统的synchronized版?
1.两个线程使用if (反例 禁用)
/**
* 第一个:反例 使用if
* 传统的生产者消费者 synchronized
* 有两个线程 A B 有一个初始值0
* 实现两个线程交替执行 对该变量+1 -1 交替执行20次
* if判断会引起终端和虚假唤醒
* 此处是两个线程可以交替执行 但是如果是多个线程呢?
*/
public class demo03 {
public static void main(String[] args) throws InterruptedException{
Data data = new Data();
//lambda表达式 (参数)->{具体的方法实现}
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "B").start();
}
}
//单独的资源类 方式 属性 高类聚 低耦合
//线程之间通信 判断 执行 通知
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
if(number!=0){//判断
this.wait();
}
number ++;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
public synchronized void decrement() throws InterruptedException {
if(number==0){//判断
this.wait();
}
number --;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
}
2.上例如果是4个线程呢可以实现交替执行吗?不能 if引起虚假唤醒
代码:
/**
* 第二个:反例 使用if
* 传统的生产者消费者 synchronized
* 有两个线程 A B C D 有一个初始值0
* 实现两个线程交替执行 对该变量+1 -1 交替执行10次
* if判断会引起终端和虚假唤醒
* 此处是4个线程可以交替执行 不能交替执行 因为if值判断一次引起虚假唤醒
* 期望结果:A B C D交替打印 1 0 1 0
*/
public class demo03 {
public static void main(String[] args) throws InterruptedException{
Data data = new Data();
//lambda表达式 (参数)->{具体的方法实现}
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "D").start();
}
}
//单独的资源类 方式 属性 高类聚 低耦合
//线程之间通信 判断 执行 通知
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
if(number!=0){//判断
this.wait();
}
number ++;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
public synchronized void decrement() throws InterruptedException {
if(number==0){//判断
this.wait();
}
number --;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
}
结果
3. if引起虚假唤醒,官方推荐使用while(if和while区别)
代码
/**
* 第三个:使用while
* 传统的生产者消费者 synchronized
* 有两个线程 A B C D 有一个初始值0
* 实现两个线程交替执行 对该变量+1 -1 交替执行10次
* if判断会引起终端和虚假唤醒
* 此处是4个线程可以交替执行
* 期望结果:A B C D交替打印 1 0 1 0
*/
public class demo03 {
public static void main(String[] args) throws InterruptedException{
Data data = new Data();
//lambda表达式 (参数)->{具体的方法实现}
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "D").start();
}
}
//单独的资源类 方式 属性 高类聚 低耦合
//线程之间通信 判断 执行 通知
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
while(number!=0){//判断
this.wait();
}
number ++;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
public synchronized void decrement() throws InterruptedException {
while(number==0){//判断
this.wait();
}
number --;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
}
结果:虽然是实现AC打1 BD打0 但是不是我们期望的 ABCD顺序打印
总结:wait和notifyAll不能实现精准唤醒通知
2、JUC版?
- 新版的写法(JUC挂钩)
注:一个Condition只绑定一个锁
- synchronized和lock版本对比
3.实现精准访问
/**
* JUC版本 使用lock和Condition实现精准访问
* 有三个线程 A B C 有一个初始值1
* 期望结果:A 打印5次 B打印10次 C打印15次 交替以ABC顺序打印
*/
public class demo04 {
public static void main(String[] args) throws InterruptedException{
Data2 data = new Data2();
//lambda表达式 (参数)->{具体的方法实现}
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.pint5();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.pint10();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.pint15();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}, "C").start();
}
}
//单独的资源类 方式 属性 高类聚 低耦合
//线程之间通信 判断 执行 通知
class Data2{
private int number = 1;// A1 B2 C3
private Lock lock = new ReentrantLock();
//实现精准访问
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void pint5() throws InterruptedException{
lock.lock();
//判断
try {
while (number != 1){
condition1.await();
}
//执行 5次
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 2;
//通知2干活
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void pint10() throws InterruptedException {
lock.lock();
try {
while(number!=2){
condition2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void pint15() throws InterruptedException {
lock.lock();
try {
while (number!=3){
condition3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number =1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
五、8锁现象?
1、8锁问题总结?
- 被synchronized修饰的方法,锁的对象是方法的调用者。
- 被static修饰的方法,锁的对象是class模板对象,全局唯一
2、8锁问题8个样例?
- 样例1:
/**
* 八锁问题场景一:两个synchronized 方法
* 两个方法调用的对象是同一个 所以先调用的先执行
* 结果:先输出sendEmail
*/
public class demo01 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.sendSms();
},"B").start();
}
}
class Phone{
public synchronized void sendEmail(){
System.out.println("sendEmail");
}
public synchronized void sendSms(){
System.out.println("sendSms");
}
}
- 样例2:
/**
* 八锁问题场景二:两个synchronized 方法 其中一个延迟
* 两个方法调用的对象是同一个 所以先调用的先执行
* 结果:先输出sendEmail
*/
public class demo02 {
public static void main(String[] args) throws InterruptedException {
Phone2 phone = new Phone2();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.sendSms();
},"B").start();
}
}
class Phone2{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);//延迟4秒
System.out.println("sendEmail");
}
public synchronized void sendSms(){
System.out.println("sendSms");
}
}
- 样例3:
/**
* 八锁问题场景三:一个phone 一个synchronized 方法 一个普通方法
* sendSms是普通方法,不是同步方法,不受锁的影响
* 结果:先输出sendSms
*/
public class demo03 {
public static void main(String[] args) throws InterruptedException {
Phone3 phone = new Phone3();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.sendSms();
},"B").start();
}
}
class Phone3{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);//只要此处时间大于上面main里休眠时间 一定是先执行sendSms 如果小于则先执行sendEmail
System.out.println("sendEmail");
}
//普通方法(没有synchronized 没有static 修改)
public void sendSms(){
System.out.println("sendSms");
}
}
- 样例4:
/**
* 八锁问题场景四:两个phone
* synchronized修饰的方法,锁的对象是锁的调用者,两个phone 所以两个锁,不受影响
* 结果:先输出sendSms
*/
public class demo04 {
public static void main(String[] args) throws InterruptedException {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
try {
phone1.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.sendSms();
},"B").start();
}
}
class Phone4{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public synchronized void sendSms(){
System.out.println("sendSms");
}
}
- 样例5:
/**
* 八锁问题场景五:一个phone 两个static方法
* static修饰的方法,锁的对象是方法调用者的class模板对象,这个则全局唯一,所以这里是同一个锁(先调用先执行)不是synchronized原因
* 结果:先输出sendEmail
*/
public class demo05 {
public static void main(String[] args) throws InterruptedException {
Phone5 phone = new Phone5();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.sendSms();
},"B").start();
}
}
class Phone5{
public static synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public static synchronized void sendSms(){
System.out.println("sendSms");
}
}
- 样例6:
/**
* 八锁问题场景六:两个phone 两个static方法
* static修饰的方法,锁的对象是方法调用者的class模板对象,这个则全局唯一,所以这里是同一个锁(先调用先执行)不是synchronized原因
* 结果:先输出sendEmail
*/
public class demo06 {
public static void main(String[] args) throws InterruptedException {
Phone6 phone1 = new Phone6();
Phone6 phone2 = new Phone6();
new Thread(()->{
try {
phone1.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.sendSms();
},"B").start();
}
}
class Phone6{
public static synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public static synchronized void sendSms(){
System.out.println("sendSms");
}
}
- 样例7:
/**
* 八锁问题场景七:一个phone 一个普通同步方法 一个静态同步方法
* static修饰的方法,锁的对象是方法调用者的class模板对象,这个则全局唯一
* synchronized修饰的方法,锁的对象是方法调用者
* 所以这是两个锁
* 结果:先输出sendSms
*/
public class demo07 {
public static void main(String[] args) throws InterruptedException {
Phone7 phone = new Phone7();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.sendSms();
},"B").start();
}
}
class Phone7{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public static synchronized void sendSms(){
System.out.println("sendSms");
}
}
- 样例8:
/**
* 八锁问题场景八:两个phone 一个静态同步方法 一个普通同步方法
* static修饰的方法,锁的对象是方法调用者的class模板对象,这个则全局唯一
* synchronized修饰的方法,锁的对象是方法调用者
* 所以这是两个锁
* 结果:先输出sendSms
*/
public class demo08 {
public static void main(String[] args) throws InterruptedException {
Phone8 phone1 = new Phone8();
Phone8 phone2 = new Phone8();
new Thread(()->{
try {
phone1.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.sendSms();
},"B").start();
}
}
class Phone8{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public static synchronized void sendSms(){
System.out.println("sendSms");
}
}
3、小结?
- new this 调用的这个对象,是一个具体的对象
- static class 唯一的一个模板
六、不安全的集合类?
注:只要是并发环境,集合都不安全(List , Set , Map)
1、List
/**
* ArrayList.add()是没有加锁的 不安全的方法
* ConcurrentModificationException 并发修改异常
*解决办法:
* 1、List<Object> list = new Vector<Object>(); 使用Vector Vector.add()是加锁的(synchronized) 弊端:Vector jdk1.0就出现了 效率低
* 2、List<Object> list = Collections.synchronizedList(new ArrayList<>()); 使用工具类将ArrayList转换为同步的
* 3、List<Object> list = new CopyOnWriteArrayList<>(); 使用JUC
* 什么是CopyOnWrite 写入时赋值 读写分离 第二个线程过来写入的时候复制一个
*/
public class unsafeList {
public static void main(String[] args) {
//List<Object> list = new ArrayList();
//List<Object> list = new Vector<Object>();
//List<Object> list = Collections.synchronizedList(new ArrayList<>());
List<Object> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(()-> {
list.add(UUID.randomUUID().toString().substring(0, 3));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
1、CopyOnWriteArrayList:写入时复制(是一种思想COW),读写分离 ,当多个调用者同时需要一个资源,就会出现指针的概念,第二个线程过来写入的时候复制一个。
2、CopyOnWriteArrayList的add方法源码:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
2、Set
/**
* Set<Object> set = new HashSet<Object>(); ConcurrentModificationException并发修改异常
* 解决:
* 1、Set<Object> set = Collections.synchronizedSet(new HashSet<Object>());
* 2、Set<Object> set = new CopyOnWriteArraySet();
*/
public class unSafeSet {
public static void main(String[] args) {
//HashSet底层就是一个hashMap
//Set<Object> set = new HashSet<Object>();
//Set<Object> set = Collections.synchronizedSet(new HashSet<Object>());
Set<Object> set = new CopyOnWriteArraySet();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
3、Map
/**
* 1、工作中new HashMap<String, String>()这么用的吗 不是 默认容量是16 超过16要扩容 需要消耗时间
* 2、 加载因子 0.75f 容量(默认16)
* 3、HashMap底层数据结构:链表+红黑树
* 并发编程下使用 Map<String, String> map = new HashMap<String, String>(); 引起ConcurrentModificationException并发修改异常
* 解决方法:
* 1、Map<String, String> map = Collections.synchronizedMap(new HashMap<String,String>());
* 2、Map<String, String> map = new ConcurrentHashMap<String, String>();
*/
public class unSafeMap {
public static void main(String[] args) {
//工作中
//Map<String, String> map = new HashMap<String, String>();
//Map<String, String> map = Collections.synchronizedMap(new HashMap<String,String>());
Map<String, String> map = new ConcurrentHashMap<String, String>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,3));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
hashMap小结:
1、工作中new HashMap<String, String>()这么用的吗 不是 默认容量是16 超过16要扩容 需要消耗时间
2、 加载因子 0.75f 容量(默认16)
3、HashMap底层数据结构:链表+红黑树