JUC并发编程(一)
文章目录
一. 什么是JUC
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
同属于Java.util 工具包
在业务中:用普通的线程代码 Thread(普通的线程类)
Runnable 没有返回值、效率相比入 Callable相对较低!
所以,企业中Runnable用的比较少,更多的是Callable
之前学的callable 与lock都是 java.util 包下的
二. 进程和线程回顾
进程:一个程序,QQ.exe 等等程序的集合
一个进程往往包含多个线程,至少包含一个!
Java默认有多少个线程? 两个:main线程,GC线程
对于Java而言,用Runnable,Thread,Callable 来开启线程
Java真的可以开启线程吗?
答案是:不能,本质上是调用本地方法(底层的C++),Java无法直接操作硬件
并发,并行
并发编程:并发,并行
并发:交替执行,一核cpu模拟出来多个线程,
并行:一起执行,多核cpu,多个线程可以同时执行,线程池
public class Demo01 {
public static void main(String[] args) {
//获取CPU线程数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
---------------结果---------------
8
并发编程的本质:充分利用CPU资源
线程有几个状态
//我们进入枚举类,可以看到有六个状态
public enum State {
//新建
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待,死死的等
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait 和 sleep 的区别
- 来自不同的类
wait 属于Object类 sleep 属于Thread 类- 关于锁的释放
wait会释放锁,sleep不会释放- 使用范围不同
wait必须在同步代码块中使用
sleep在任何地方都能用
三. Lock锁(重点)
1. 传统Synchronized
//基本的买票例子
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1.属性 方法
*/
public class Demo01 {
public static void main(String[] args) {
//并发:多线程操作同一资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//lambda表达式 (参数)->{代码}
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"c").start();
}
}
//资源类 oop
class Ticket{
//属性,方法
private int number =50;
//买票的方式
//synchronized 的本质:队列,锁
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余" +
number+"张票");
}
}
}
---------------结果---------------
A卖出了第50票,剩余49张票
A卖出了第49票,剩余48张票
A卖出了第48票,剩余47张票
。。。
B卖出了第3票,剩余2张票
A卖出了第2票,剩余1张票
A卖出了第1票,剩余0张票
2. Lock锁
lock锁中有三个接口
Condition 精确通知
标准Lock锁
ReadWriteLock 读写锁
lock锁中的两个用法:加锁/解锁
当我们打开ReentrantLock的源码中会发现:
默认定义了一个非公平锁
当传入一个布尔值为true则为公平锁
公平锁:非常公平,先来后到
非公平锁:可以插队(默认)
Synchronized 默认也是非公平锁
用Lock锁重写卖票实例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
public static void main(String[] args) {
//并发:多线程操作同一资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
//lambda表达式 (参数)->{代码}
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"c").start();
}
}
//资源类 oop
class Ticket2{
//属性,方法
private int number =50;
//买票的方式
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(); //解锁
}
}
}
---------------结果---------------
A卖出了第50票,剩余49张票
B卖出了第49票,剩余48张票
B卖出了第48票,剩余47张票
。。。
A卖出了第2票,剩余1张票
A卖出了第1票,剩余0张票
Synchronized 和 Lock锁的区别
- Synchronized 是Java内置的关键字,Lock是一个Java类
- Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
- Synchronized 是自动的,会自动释放锁,Lock必须手动释放锁 容易发生死锁
- Synchronized 线程1(获得锁,阻塞) 线程2(等待,死等) ,Lock锁不一定会一直等待
- Synchronized 可重入锁,不可中断,非公平,Lock可重入,可以判断锁,可设置公平与否
- Synchronized 适合锁少量的代码同步问题,Lock锁适合锁大量的同步代码块
四. 生产者和消费者
1. Synchronized 版
/**
* 线程之间的通信问题,生产者与消费者问题(等待唤醒,通知提醒)
* 线程交替执行,操作同一变量,使num=0
*/
public class Demo03 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data{ //资源类 数字num
private int num=1;
//+1
public synchronized void increment() throws InterruptedException {
if (num!=0){
//等待操作
this.wait();
}
num++;
//通知其他线程,工作完毕
System.out.println(Thread.currentThread().getName()+"=>"+num);
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (num==0){
//等待操作
this.wait();
}
num--;
//通知其他线程,工作完毕
System.out.println(Thread.currentThread().getName()+"=>"+num);
this.notifyAll();
}
}
---------------结果---------------
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
问题:如果线程超过两个,会有什么问题?
答:会出现虚假唤醒问题
应该将if改成while,防止虚假唤醒问题
/**
* 线程之间的通信问题,生产者与消费者问题(等待唤醒,通知提醒)
* 线程交替执行,操作同一变量,使num=0
*/
public class Demo03 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{ //资源类 数字num
private int num=1;
//+1
public synchronized void increment() throws InterruptedException {
while (num!=0){
//等待操作
this.wait();
}
num++;
//通知其他线程,工作完毕
System.out.println(Thread.currentThread().getName()+"=>"+num);
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (num==0){
//等待操作
this.wait();
}
num--;
//通知其他线程,工作完毕
System.out.println(Thread.currentThread().getName()+"=>"+num);
this.notifyAll();
}
}
---------------结果---------------
B=>0
A=>1
B=>0
C=>1
B=>0
A=>1
B=>0
C=>1
D=>0
A=>1
B=>0
C=>1
D=>0
A=>1
D=>0
C=>1
D=>0
A=>1
D=>0
C=>1
2.JUC版生产者消费者
要用Lock代替Synchronized ,就必须配套替换 wait 和 nodity
这就提到了之前的Condition,在JDK帮助文档中,明确介绍
代码实现:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo04 {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data2.incerment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data2.decerment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data2.incerment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data2.decerment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data2{
private int num=1;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void incerment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (num!=0){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decerment() throws InterruptedException {
lock.lock();
try {
while (num==0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
---------------结果---------------
B=>0
A=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
B=>0
A=>1
Condition的优点:精准通知和唤醒线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 不同于上一个例子
* 这次要顺序执行A B C
*/
public class Demo05 {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 5; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
private int num=1; //num=1 A, 2 B,3 C
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();
try {
while (num!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>执行了PrintA");
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()+"=>执行了PrintB");
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()+"=>执行了PrintC");
num=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
---------------结果---------------
A=>执行了PrintA
B=>执行了PrintB
C=>执行了PrintC
A=>执行了PrintA
B=>执行了PrintB
C=>执行了PrintC
A=>执行了PrintA
B=>执行了PrintB
C=>执行了PrintC
A=>执行了PrintA
B=>执行了PrintB
C=>执行了PrintC
A=>执行了PrintA
B=>执行了PrintB
C=>执行了PrintC
五. 8锁的现象
根据上面的实例,请问锁是什么,如何判断锁的是谁?
锁的内容:对象,与class模板
8锁,就是关于锁的8个问题
1、标准情况下,两个线程先打印发短信还是打电话?1/发短信2/打电话
2、 sendsms延迟4秒,两个线程先打印发短信还是打电话?1/发短信2/打电话
//问题1
//标准情况下,两个线程先打印发短信还是打电话?1/发短信2/打电话
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{ //线程A
phone.sendSms();
},"A").start();
//捕获异常
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
//问题2
//sendsms延迟4秒,两个线程先打印发短信还是打电话?1/发短信2/打电话
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{ //线程A
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
//捕获异常
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
//问题3
//新增普通线程,sendsms延迟4秒,两个线程先打印发短信还是hello?1/发短信2/hello
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{ //线程A
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone.hello();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
//捕获异常
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
//问题4
//两个对象,两个线程先打印发短信还是打电话 1/发短信2/打电话
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
//两个对象
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{ //线程A
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone2.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
//捕获异常
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
//问题5
//两个静态同步方法,两个线程先打印发短信还是打电话 1/发短信2/打电话
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{ //线程A
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone.call();
},"B").start();
}
}
class Phone{
public static synchronized void sendSms(){
//捕获异常
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
//问题6
//两个对象,两个静态同步方法,两个线程先打印发短信还是打电话 1/发短信2/打电话
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{ //线程A
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone2.call();
},"B").start();
}
}
class Phone{
public static synchronized void sendSms(){
//捕获异常
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
//问题7
//一个静态同步方法,一个普通同步方法,一个对象
//两个线程先打印发短信还是打电话 1/发短信2/打电话
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{ //线程A
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone.call();
},"B").start();
}
}
class Phone{
public static synchronized void sendSms(){
//捕获异常
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
//问题8
//一个静态同步方法,一个普通同步方法,两个对象
//两个线程先打印发短信还是打电话 1/发短信2/打电话
import java.util.concurrent.TimeUnit;
public class Demo06 {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{ //线程A
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ //线程B
phone2.call();
},"B").start();
}
}
class Phone{
public static synchronized void sendSms(){
//捕获异常
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
答案:
1、发短信 打电话
2、发短信 打电话
原因:synchronized锁的对象是方法的调用者!
两个方法用的是同一个锁(phone),谁先拿到锁谁先执行
3、hello 发短信
hello没有锁,不是同步方法,不受锁的影响
4、打电话 发短信
两个对象,两个调用者,两个锁,没有冲突
5、发短信 打电话
Phone是唯一的一个CLass对象,类一加载就有了,锁的是Class
6、发短信 打电话
两个对象的Class类模板只有一个,锁的是Class
7、打电话 发短信
8、打电话 发短信
两个锁 锁的内容不一样,没有冲突
六. 集合类不安全
1.List不安全
实例一:
//安全线程实例
import java.util.Arrays;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("1", "2", "3", "4");
list.forEach(System.out::println);
}
}
结果:
1
2
3
4
//不安全实例,及解决方案
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
//并发下,arraylist是不安全的
/*
解决方案:
1.将List<Object> list = new ArrayList<>();
修改为:List<Object> list = new Vector<>();
2.将List<Object> list = new ArrayList<>();
修改为:List<Object> list = Collections.synchronizedList(new ArrayList<>());
3.将List<Object> list = new ArrayList<>();
修改为:List<Object> list = new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 写入时复制 ,简称COW ,是计算机程序设计领域的一种优化策略
//多个线程调用的时候,资源在读取的时候是一个固定的,但是写入的时候,可能会出现覆盖操作
//相当于在写入的时候避免覆盖造成数据问题
//后面会讲 mycat
List<Object> list = new ArrayList<>();
for (int i = 1; i < 11; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,10));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
//注:JDK11 后将 COW(CopyOnWrite)修改为了synchronized
2.Set不安全
//Set 同理
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
//同样出现了java.util.ConcurrentModificationException 并发修改异常
public class SetTest {
public static void main(String[] args) {
/*
解决方法:
1.Set<String> set = new HashSet<>();
修改为:Set<String> set = Collections.synchronizedSet(new HashSet<>());
2.Set<String> set = new HashSet<>();
修改为:Set<String> set = new CopyOnWriteArraySet<>();
*/
Set<String> set = new HashSet<>();
for (int i = 1; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
HashSet的本质是什么
public HashSet() {
map = new HashMap<>();
}
add set本质就是 map ,key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Map不安全
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MapTest {
public static void main(String[] args) {
//map是这样用的吗,默认等价于什么
//不是,默认等价于new HashMap<>(16,0.75);
//加载因子,初始化容量
//方法一:Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
//方法二:Map<String, Object> map = new ConcurrentHashMap<>();
Map<String, Object> map = new HashMap<>();
for (int i=1;i<11;i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
ConcurrentHashMap的原理: