视频地址:遇见狂神说的个人空间_哔哩哔哩_bilibili
1.什么是juc?juc就是java.util.concurrent下的包,专门处理多线程开发。
学习该课程需要对java语言和java多线程编程有一定的了解
2.进程和线程进程:程序执行的过程。一个程序往往可以包含多个线程(至少包含一个)
线程:是进程的一个执行单元,是进程内调度的实体,比进程更小的独立运行的基本单位。比如一个开启了一个网易云音乐,显示歌词,可能就是一个线程处理的。
java默认有两个线程:GC线程,main线程。
java开启线程的方式
1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法
3.实现Callable接口,重写call方法
那么java真的可以开启线程吗?
我们知道java在开启一个线程是,需要使用线程类中的start方法
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { //调用start0方法 start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } private native void start0();//native关键子是去调用本地方法库中c++的方法
我们发现,他底层调用了start0方法,这个方法有一个native关键字。
如果不熟悉native关键字的含义可以去了解下JVM方面的知识。
3.并发和并行并发:学习过 *** 作系统的我们都知道cpu是在多个线程之间来回切换,这就是并发,它只是在逻辑上同时发生,其实只是cpu在多个线程之间快速的切换,我们人眼分辨不出来,实际上并不是同时发生。
并行:多个cpu同时 *** 作多个线程,所以这是逻辑上和实际上都是同时发生。这就是并发和并行的区别
并发编程的本质:充分利用cpu资源
说到线程,那么线程有几个装态呢?
我们从 *** 作系统上来说,线程是有5个状态的
1.新建状态
2.就绪状态
3.运行状态
4.阻塞状态
5.死亡状态
那么java中线程的状态是这样的吗?我们通过观察源码发现,java中线程是有6种状态的。
1.新建状态
2.运行状态
3.阻塞状态
4.等待状态
5.超时等待状态
6.死亡状态
口说无凭,我把源码贴在了下方
public enum State { //这是官方源码,不过我将一些注释删除掉了。 //新建 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待 WAITING, //超时等待 过期不候 TIMED_WAITING, //终止 TERMINATED; }
在面试种我们常常会被问到wait和sleep的区别,那么我们来总结一下吧?
- wait方法是来自Object类的,sleep方法是来自Thread类的,我们后面会学习TimeUnit工具类,来进行线程的休眠
- wait方法会释放锁资源,而sleep方法不会释放锁资源
- wait方法必须写在synchronized同步代码块中,但是sleep方法可以在任何地方使用
- 我在网上看其他博客中提到,wait方法不需要捕获异常,sleep方法需要捕获,然后我下去在验证的时候发现wait方法也是需要捕获异常的
我们在前面学习到了,让一个方法变成同步方法需要在方法上面加上synchronized关键字,那么今天我们来学习一种新的方式Lock锁(可重入锁)
首先Lock是一个接口,它下面有一些实现类ReentrantLock,ReentranReadWriteLock(读写锁)等
我们先来看下以前我们解决买票例子的代码吧
package com.mc; public class Day1 { public static void main(String[] args) { B b = new B(); new Thread(()->{ for (int i = 0; i < 60; i++) { b.getNumber(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 60; i++) { b.getNumber(); } },"B").start(); } } class B { private int number = 1; //使用Synchronized关键字,解决并发问题 public synchronized void getNumber() { if(number <= 50) { System.out.println(Thread.currentThread().getName()+"卖出第"+(number++)+"张票"); } } }
这样做我们只知道了这个方法加了锁,却不知道,锁的状态,它是给整个方法都加上了锁,如果我想只对代码中一部分代码加锁怎么办?
Lock锁就是为了解决这些问题的
首先Lock锁也是一个可重入锁,那么我们来看下Lock锁是怎样使用的
package com.mc; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo1 { public static void main(String[] args) { A a = new A(); new Thread(()->{ for (int i = 0; i < 40; i++) { a.getNumber(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 40; i++) { a.getNumber(); } },"B").start(); } } class A { private int number = 30; public void getNumber() { //新建锁 Lock lock = new ReentrantLock(); try{ //加锁 lock.lock(); //业务,买票 if(number > 0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票"); } }catch (Exception e) { e.printStackTrace(); }finally { //解锁 lock.unlock(); } } }
这样是不是可以看出锁的状态了呢?也可以实现局部加锁了
其实在官方文档中,都明确写了这种锁如何使用
所以我们需要学会查阅官方文档
Lock锁还有一个特殊的地方,Synchronized,必须等待一个方法执行完,才会释放锁,但是Lock锁有一个tryLock()方法可以去尝试获取锁
当然Lock锁还有很多用法,比如Synchronized默认是一个非公平锁,那么Lock锁呢?
public ReentrantReadWriteLock() { this(false); } public ReentrantReadWriteLock(boolean fair) { //可以看到Lock锁可以实现公平锁,在创建时传递一个boolean值为true就可以新建出来了 sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); }
那我们来总结一下Lock锁和Synchronized的区别吧。
1.首先Synchronized是一个关键字,Lock锁是一个类。
2.Synchronized无法获取锁的状态,但是Lock锁可以获得锁的状态
3.synchronized 线程1(获得锁、阻塞) 线程2(等待);Lock锁不一定会 lock.tranlock()尝试获得锁
4.synchronized关键字不需要手动释放锁,但是lock锁必须手动释放锁。
5.synchronized是可重入锁,非公平的,lock锁是可重入锁,可以公平也可以不公平(默认非公平)
6.Synchronized适合少量的代码同步问题,Lock适合锁大量的同步代码!
有一个疑问,关于锁是谁,对于lock锁我们很好分辨,但是对于synchronized呢,我们如何去判断,这里大家可以去练习一下8锁问题,小提示,谁调用方法,谁就是锁。
生产者消费者问题synchronized是怎样写的
package com.mc; //生产者消费者问题synchronized public class Demo2 { public static void main(String[] args) { Resource resource = new Resource(); new Thread(()->{ for(int i=0;i<20;i++){ try { resource.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for(int i=0;i<20;i++){ try { resource.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for(int i=0;i<20;i++){ try { resource.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for(int i=0;i<20;i++){ try { resource.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class Resource { private int number = 0; //生产者 public synchronized void increment() throws InterruptedException { //判断 while (number != 0){ wait(); } //业务 number++; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } //消费者 public synchronized void decrement() throws InterruptedException { while (number==0){ wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } }
注意:有的小伙伴在判断条件时,使用if条件,但是这样有可能会引发问题,有些面试官也特别爱问这一点。官方文档也给我们解释了为什么使用while而不是if
那接下来看看我们Lock版的。
在看Lock版之前,我们发现synchronized是使用了synchronized和wait以及notifyAll来完成的,那么我们lock怎样解决的呢?
我们lock中有一个方法可以获取一个condition对象,这个对象中有两个方法来解决这个问题
package com.mc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo4 { public static void main(String[] args) { Data1 data1 = new Data1(); new Thread(()->{ for (int i = 0; i < 10; i++) { data1.a(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data1.b(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data1.c(); } },"C").start(); } } class Data1{ private int number = 1; private Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); public void a(){ lock.lock(); try { while (number!=1){ condition1.await(); } System.out.println(Thread.currentThread().getName()+"=>"+number); number = 2; condition2.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void b(){ lock.lock(); try { while (number!=2){ condition2.await(); } System.out.println(Thread.currentThread().getName()+"=>"+number); number = 3; condition3.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void c(){ lock.lock(); try { while (number!=3){ condition3.await(); } System.out.println(Thread.currentThread().getName()+"=>"+number); number = 1; condition1.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }
这块我们使用了精确唤醒 A->B->C->A 这就是lock版的强大
集合不安全首先我们在平时使用的arraylist,hashset,hashmap都是线程不安全的,这点我们在平时学习的时候也都知道。
当然我们也可以使用一些集合安全的类来解决它
如vector、hashtable
或者使用工具类Collection.synchronizedxxxx(list or set or map)方法来让集合变安全。
那么我们juc是干什么的,是并发编程,那么他如何来解决这些问题呢?
首先juc下面有一些集合类来解决这些问题
1.CopyOnWriteArrayList
写入时赋值 CopyOnWrite
核心思想是,如果有多个调用者同时要求相同的资源(如内存或者磁盘上的数据存储),它们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本给调用者,而其他调用者所见到的最初资源任然保持不变。这过程对其他的调用者都是透明的。此做法主要的优点是如果调用者没有修改资源,就不会有副本被创建,因此多个调用者只是读取 *** 作时可以共享同一份资源
可以理解为,读的时候不加锁,只要在写的时候才会加锁,并赋值一份新的文件,让该线程去 *** 作
2.CopyOnWriteArraySet
3.ConcurrentHashMap
底层使用了CAS,和Synchronized来解决线程安全问题
使用了volatile来保证可见性和禁止指令重排的。
CAS和volatile后面会讲
就是解决这些问题的,并且效率更高
我在这里只介绍两种,有兴趣的朋友可以下去自己研究。
参考:狂神说juc并发编程视频
视频地址:遇见狂神说的个人空间_哔哩哔哩_bilibili
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)