文章基于【狂神说Java】JUC并发编程最新版通俗易懂_哔哩哔哩_bilibili 视频总结而来
目录
1. 什么是 JUC
2. 线程和进程
3.Lock锁
4.生产者和消费者问题
5.Conditional
6. 锁问题
1. 什么是 JUC
juc就是 java并发包
java.util.concurrent JUC
java.util.concurrent.atomic 原子性
java.util.concurrent.locks lock锁
java.util.function 函数式方法
Runnable:没有返回值,效率相比Callable相对较低
2. 线程和进程
进程:
- 一个程序,本质是程序的集合,可执行。
- 一个进程往往包含多个线程,至少一个。
- JAVA默认有两个线程 main GC
线程: 开了一个进程,对JAVA而言:Thread,Runnable,Callable
“java真的可以开启线程吗”
我们new一个thread:
public static void main(String[] args) {
new Thread().start();
}
我们进入start方法可以看到:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this); //把当前方法加入一个线程组
boolean started = false;
try {
start0(); //调用start0方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
可以看到 start 方法是一个安全的被synchronized修饰的方法。核心是调用start0方法。
private native void start0();
可以看大start0是一个被native修饰的方法,也就是本地方法。
本地方法实际调用了底层的C++方法,java无法直接操作硬件
并发和并行
并发编程: 并发、并行
并发(多个线程操作同一个资源)
????????一核心CPU模拟出来的多条线程,快速交替
并行(多个人一起走)
????????多核心CPU多个线程同时执行;线程池、
public class Test1 {
public static void main(String[] args) {
// 获取处理器核心数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质: 充分运用CPU的资源
线程有几个状态
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
可以看到一共有六个状态:
1. 新生 NEW
1. 运行 RUNNABLE
1. 阻塞 BLOCKED
1. 等待,死等 WAITING
1. 超时等待 TIMED_WAITING
1. 中止 TERMINATED
wait 和 sleep的区别:
1. 类不同:
????????????????wait 是 object类
????????????????sleep 是 Thread类
2. 关于锁的释放
????????wait会释放锁,sleep不会释放
3. 适用范围不同
????????wait必须在同步代码块中
????????sleep可以在任何地方
4. 需要捕获异常
????????wait不需要捕获异常
????????sleep可能会发生超时等待的情况,需要捕获异常
3.Lock锁
传统synchronize锁
package com.demo;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName SaleTicket.java
* @Description 买票的例子
* 线程就是一个单独的资源类,没有任何附属
* 1. 属性
* 2. 方法
* @createTime 2022年04月27日 10:11:00
*/
public class SaleTicket {
public static void main(String[] args) {
//并发:多线程操作同一个资源类
Ticket ticket = new Ticket();
//@FunctionInterface 函数式接口: 在jdk1.8后使用lambda表达式
//lambda表达式: (参数)->{ 代码 },名字
new Thread(()->{
for (int i = 0; i <60 ; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <60 ; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <60 ; i++) {
ticket.sale();
}
},"C").start();
}
/**
* 资源类OOP
*/
static class Ticket{
private int number = 50;
public void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+number--+"张票。"+" 余票:"+number);
}
}
}
}
上面的代码会出现线程并发问题,也就是争抢线程的时候,顺序完全乱掉
?这时候,最简单的方式就是加synchronized同步锁,来达到顺序执行的目的
static class Ticket{
private int number = 30;
// synchronized 原理就是排队,
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+number--+"张票。"+" 余票:"+number);
}
}
}
?
Lock
Lock lock = new ReentrantLock();
我们看到ReentrantLock的源码:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
????????可以看到,无参构造的时候,进来的是一个非公平锁,如果传参,可以根据Boolean值来确定创建的锁是公平锁还是非公平锁
公平锁:
????????十分公平,先来后到
非公平锁:
????????不公平,可以插队
修改代码使用Lock加锁,一样可以实现加锁的功能 :
static class Ticket2{
private int number = 30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock(); //加锁
try {
//业务代码
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+number--+"张票。"+" 余票:"+number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock(); //解锁
}
}
}
synchronize锁和lock的区别
-
synchronize 是java内置的关键字,lock 是一个java类 -
synchronize 无法判断锁的状态,lock 就可以判断是否获取到了锁 -
synchronize 可以自动释放锁,lock 需要手动释放,不释放会出现死锁 -
synchronize 线程1(获得锁,阻塞)、线程2(等待),Lock 锁不一定会等待。可以通过 lock.tryLock() 方法尝试获取锁 -
synchronize 可重入锁,非公平;lock, 可重入锁,可判断锁,非公平锁(可以自己设置种类); -
synchronize 适合锁少量的代码同步问题,lock 适合锁大量的代码
4.生产者和消费者问题
生产者和消费者的synchronize问题
package com.demo.pc;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName A.java
* @Description 线程通信问题:
* A,B操作同一个线程: 通知唤醒,等待唤醒
* @createTime 2022年04月27日 13:24:00
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"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()+"=>"+number);
//通知线程,操作完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (number==0){
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知线程,操作完毕
this.notifyAll();
}
}
如果有4个线程呢,就会出现虚假唤醒问题
解决办法: 等待应该总是出现在循环中,if 改为 while判断
package com.demo.pc;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName A.java
* @Description 线程通信问题:
* A,B操作同一个线程: 通知唤醒,等待唤醒
* @createTime 2022年04月27日 13:24:00
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"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()+"=>"+number);
//通知线程,操作完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (number==0){
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知线程,操作完毕
this.notifyAll();
}
}
5.Conditional
package com.demo.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName A.java
* @Description 线程通信问题:
* A,B操作同一个线程: 通知唤醒,等待唤醒
* @createTime 2022年04月27日 13:24:00
*/
public class A {
@SuppressWarnings("AlibabaAvoidManuallyCreateThread")
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <30 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
class Data{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try {
while (number != 0){
//等待操作
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知线程,操作完毕
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public synchronized void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){
//等待操作
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知线程,操作完毕
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
可以得到和synchronize一样的结果 。
conditional 的优势,可以精准通知和唤醒线程
package com.demo.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName B.java
* @Description A执行完调用B,B执行完调用C,C执行完调用A
* @createTime 2022年04月27日 17:20:00
*/
public class B {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printC();
}
},"C").start();
}
static class Data3{
private final Lock lock = new ReentrantLock();
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
private final Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();
try {
//判断 -》执行 -》通知
while (number!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"A");
number++;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"B");
number++;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"C");
number=1;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
通过监视器指定唤醒
6. 锁问题
Q1:以下代码是会先发短信还是先打电话
package com.demo.pc;
import java.util.concurrent.TimeUnit;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Lock8.java
* @Description 8锁,就是锁的8个问题
* @createTime 2022年04月28日 10:58:00
*/
public class Lock8 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone.phoneCall();
},"B").start();
}
}
class Phone{
public synchronized void sendMessage(){
System.out.println("Send message");
}
public synchronized void phoneCall(){
System.out.println("call");
}
}
Q2:如果我们让sendMessage延迟4秒,先发短信还是打电话
package com.demo.pc;
import java.util.concurrent.TimeUnit;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Lock02.java
* @Description TODO
* @createTime 2022年04月28日 14:28:00
*/
public class Lock02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone.phoneCall();
},"B").start();
}
}
class Phone2 {
public synchronized void sendMessage() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Send message");
}
public synchronized void phoneCall() {
System.out.println("call");
}
}
????????Q1和Q2都是锁的问题,我们使用synchronize关键字, 锁的对象是方法的调用者(对象锁:phone),也就是我们new的phone对象,不论是phoneCall还是sendMessage,都拿到的是同一把锁,所以谁先拿到,谁先执行。
Q3: 我们增加一个普通方法hello方法,然后让线程B去调用hello,是先发短信还是hello
package com.demo.pc;
import java.util.concurrent.TimeUnit;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Lock8.java
* @Description 8锁,就是锁的8个问题
* @createTime 2022年04月28日 10:58:00
*/
public class Lock8 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone{
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Send message");
}
public synchronized void phoneCall(){
System.out.println("call");
}
//这里没有锁
public void hello(){
System.out.println("hello");
}
}
因为hello没有锁,不受锁的影响,所以先打印出hello,再打印的SendMessage
Q4:我们新建一个对象phone2,用第一个对象去调用发短信方法,第二个对象去调用打电话方法,哪一个先执行?
package com.demo.pc;
import java.util.concurrent.TimeUnit;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Lock03.java
* @Description TODO
* @createTime 2022年04月28日 14:44:00
*/
public class Lock03 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(() -> {
phone.sendMessage();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone2.phoneCall();
}, "B").start();
}
}
class Phone3 {
public synchronized void sendMessage() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Send message");
}
public synchronized void phoneCall() {
System.out.println("call");
}
}
????????这时候两个对象,所以有两把锁,phong和phong2拿到的是不一样的锁,所以就按实现之星,先打电话,再发短信
Q5: 如果我们把打电话和发短信的方法换成静态方法,会先执行哪一个?
package com.demo.pc;
import java.util.concurrent.TimeUnit;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Lock.java
* @Description TODO
* @createTime 2022年04月28日 14:52:00
*/
public class Lock04 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
new Thread(() -> {
phone.sendMessage();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone.phoneCall();
}, "B").start();
}
}
class Phone4 {
public static synchronized void sendMessage() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Send message");
}
public static synchronized void phoneCall() {
System.out.println("call");
}
}
????????我们使用static修饰,类一加载的时候就有了,所以synchronize锁的是这个class模板。在我们用static修饰方法的时候,我们的 Class Phone4就只有一个class对象,所以两个调用拿到的是同一把锁。
Q6: 两个对象,phone和phone2,phone调用发短信方法,phone2调用打电话方法,会先发短信还是先打电话?
package com.demo.pc;
import java.util.concurrent.TimeUnit;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Lock.java
* @Description TODO
* @createTime 2022年04月28日 14:52:00
*/
public class Lock04 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(() -> {
phone.sendMessage();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone2.phoneCall();
}, "B").start();
}
}
class Phone4 {
public static synchronized void sendMessage() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Send message");
}
public static synchronized void phoneCall() {
System.out.println("call");
}
}
????????我们使用static修饰,synchronize是锁住了这个Class模板,所以及时是两个对象,但是拿到的还是同一把锁,所以是下发短信再打电话
Q7: 一个静态同步方法,一个普通同步方法hello,一个对象调用,先发短信还是先hello
package com.demo.pc;
import java.util.concurrent.TimeUnit;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Lock5.java
* @Description TODO
* @createTime 2022年04月28日 15:03:00
*/
public class Lock5 {
public static void main(String[] args) {
Phone5 phone = new Phone5();
new Thread(() -> {
phone.sendMessage();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
class Phone5 {
public static synchronized void sendMessage() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Send message");
}
public static synchronized void phoneCall() {
System.out.println("call");
}
public synchronized void hello(){
System.out.println("hello");
}
}
????????会先执行hello,再执行发短信,因为static所得是class模板,普通同步方法所得是调用对象,不是一个锁所以先执行了hello
总结:
锁去锁住的东西一般分为两种,一种是new出来的对象,一种是static的,也就是类一开始就加载出来的class模板对象。锁是否执行,需要注意是不是同一个对象,同一把锁。
|