JUC并发编程-线程和进程、Synchronized 和 Lock、生产者和消费者问题

1、什么是JUC

源码 + 官方文档 面试高频问!

java.util 工具包、包、分类

业务:普通的线程代码 Thread Runnable

Runnable 没有返回值、效率相比入 Callable 相对较低!


2、线程和进程

线程、进程,如果不能使用一句话说出来的技术,不扎实!

进程:一个程序,QQ.exe Music.exe 程序的集合;

一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程? 2 个 mian、GC

线程:开了一个进程 Typora,写字,自动保存(线程负责的)

对于Java而言:Thread、Runnable、Callable
Java 真的可以开启线程吗? 开不了

并发、并行

并发编程 :并发、并行
并发(多线程操作同一个资源)

  • CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替
    并行(多个人一起行走)
  • CPU 多核 ,多个线程可以同时执行; 线程池

并发编程的本质:充分利用CPU的资源

线程有几个状态

java 复制代码
public enum State {
	// 创建
	NEW,
	// 运行
	RUNNABLE,
	// 阻塞
	BLOCKED,
	// 等待,死死地等
	WAITING,
	// 超时等待
	TIMED_WAITING,
	// 终止
	TERMINATED;
}

wait/sleep 区别

1、来自不同的类

wait => Object

sleep => Thread
2、关于锁的释放

wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
3、使用的范围是不同的

wait:必须在同步代码块中

sleep 可以再任何地方睡
4、是否需要捕获异常

wait 不需要捕获异常

sleep 必须要捕获异常

3、Lock锁(重点)

传统 Synchronized

java 复制代码
package com.kuang.demo01;
// 基本的卖票例子
import java.time.OffsetDateTime;
Lock 接口
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1、 属性、方法
*/
public class SaleTicketDemo01 {
	public static void main(String[] args) {
		// 并发:多线程操作同一个资源类, 把资源类丢入线程
		Ticket ticket = new Ticket();
		// @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 }
		new Thread(()->{
			for (int i = 1; i < 40 ; i++) {
				ticket.sale();
			}
		},"A").start();
		new Thread(()->{
			for (int i = 1; i < 40 ; i++) {
				ticket.sale();
			}
		},"B").start();
		new Thread(()->{
			for (int i = 1; i < 40 ; i++) {
				ticket.sale();
			}
		},"C").start();
	}
}
// 资源类 OOP
	class Ticket {
		// 属性、方法
		private int number = 30;
		// 卖票的方式
		// synchronized 本质: 队列,锁
		public synchronized void sale(){
			if (number>0){{System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
		}
	}
}

Lock 接口

可以在ReentrantLock中设置公平和非公平锁

公平锁 :十分公平:可以先来后到
非公平锁:十分不公平:可以插队 (默认)

Lock三部曲

1. new ReentrantLock();
2. lock.lock(); // 加锁
3. finally=> lock.unlock(); // 解锁

测试效果一样

Synchronized 和 Lock 区别

1.Synchronized 内置的Java关键字Lock 是一个Java类

2.Synchronized 无法判断获取锁的状态Lock 可以判断是否获取到了锁

3.Synchronized 会自动释放锁lock 必须要手动释放锁!如果不释放锁,死锁

4.Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下 去

5.Synchronized 可重入锁不可以中断的非公平Lock可重入锁,可以 判断锁非公平(可以 自己设置)

6.Synchronized 适合锁少量的代码同步问题Lock 适合锁大量的同步代码

4、生产者和消费者问题

1)Synchronzied 版本

面试的:单例模式、排序算法、生产者和消费者、死锁

防止虚假唤醒要用while,不能用if

java 复制代码
package com.marchsoft.juctest;


public class ConsumeAndProduct {
    public static void main(String[] args) {
        Data data = new Data();

        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();
    }
}

class Data {
    private int num = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        // 判断等待
        if (num != 0) {
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        // 通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        // 判断等待
        if (num == 0) {
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        // 通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

效果:

2)存在问题(虚假唤醒)

问题,如果有四个线程,会出现虚假唤醒

理解文档

重点理解if和while的线程唤醒问题

解决方式 ,if 改为while即可,防止虚假唤醒

结论:就是用if判断 的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码 ,而如果使用while 的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件如果不成立再执行while代码块之后的代码块,成立的话继续wait

这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后

​ 拿两个加法线程A、B来说,比如A先执行,执行时调用了wait方法,那它会等待,此时会释放锁,那么线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行。如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行。

3)JUC版

java 复制代码
package com.marchsoft.juctest;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockCAP {
    public static void main(String[] args) {
        Data2 data = new Data2();

        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 Data2 {
    private int num = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    // +1
    public  void increment() throws InterruptedException {
        lock.lock();
        try {
            // 判断等待
            while (num != 0) {
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 +1 执行完毕
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }

    // -1
    public  void decrement() throws InterruptedException {
        lock.lock();
        try {
            // 判断等待
            while (num == 0) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 +1 执行完毕
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }
}
随机执行
Condition的优势

精准的通知和唤醒的线程!

如何指定线程执行的顺序? 我们可以使用Condition来指定通知进程~

java 复制代码
package com.marchsoft.juctest;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;



public class ConditionDemo {
    public static void main(String[] args) {
        Data3 data3 = new Data3();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printA();
            }
        },"A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printB();
            }
        },"B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printC();
            }
        },"C").start();
    }

}
class Data3 {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1; // 1A 2B 3C

    public void printA() {
        lock.lock();
        try {
            // 业务代码 判断 -> 执行 -> 通知
            while (num != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "==> AAAA" );
            num = 2;
            condition2.signal();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            // 业务代码 判断 -> 执行 -> 通知
            while (num != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "==> BBBB" );
            num = 3;
            condition3.signal();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            // 业务代码 判断 -> 执行 -> 通知
            while (num != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "==> CCCC" );
            num = 1;
            condition1.signal();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

测试效果:

JUC并发编程-线程和进程、Synchronized 和 Lock、生产者和消费者问题 的学习笔记到此完结,笔者归纳、创作不易,大佬们给个3连再起飞吧

相关推荐
程序员一点2 天前
Python并发编程(1)——Python并发编程的几种实现方式
python·多线程·并发编程·多进程
程序猿进阶9 天前
New major version of npm available! 8.3.1 -> 10.8.3 报错
java·开发语言·前端·设计模式·npm·node.js·并发编程
小乖兽技术14 天前
C#开发基础之单例模式下的集合数据,解决并发访问读写冲突的问题
单例模式·c#·线程安全·读写冲突·并发访问
lazy★boy19 天前
JUC学习笔记(一)
juc
小乌龟不会飞19 天前
【Linux系统编程】用互斥量和信号量加锁STL容器,避免并发问题
c++·线程安全·stl容器··信号量·互斥量
lazy★boy20 天前
JUC学习笔记(三)
juc
程序猿进阶1 个月前
线程的六种状态
java·开发语言·数据库·缓存·thread·并发编程·线程状态
程序猿进阶1 个月前
ThreadLocal 释放的方式有哪些
java·开发语言·性能优化·架构·线程池·并发编程·threadlocal
小小工匠1 个月前
J.U.C Review - 常见的通信工具类解析
juc·countdownlatch·exchanger·phaser·semaphore·cyclicbarrier
程序猿进阶1 个月前
JVM 锁的种类
java·开发语言·jvm·职场和发展·性能优化·架构·并发编程