并发的理解
聊关于并发首先得知道,为什么会有并发这个概念?
在操作系统中有两个核心的概念:进程与线程,进程是操作系统资源分配和调度的单位,有着独立的内存空间并且进程间的数据相互隔离,进程的崩溃不会影响另一个进程;线程是进程中的执行单元(简而言之假设一个进程中的多个功能,功能就是线程),线程共享进程的内存,并且他们可以相互通信;
当操作系统中并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行;具体表现为同一时刻多个线程在访问同一个资源,多个线程对一个点。
在java中,可以使用synchronized关键字和Lock来解决并发带来的问题线程不安全问题;举例一个经典案例卖票问题
public class Ticket {
private int count; // 票的总数
public Ticket(int count) {
this.count = count;
}
// 卖票方法
public void sellTicket() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出一张票,剩余票数: " + --count);
} else {
System.out.println("票已售罄");
}
}
public int getCount() {
return count;
}
}
当我们创建三个线程时会发现,小飞机1和三卖的都是第9张票这就不对,出现的原因就是因为,在卖第9张票的时候,1,3飞机一起进了卖票的方法,此时都是9编号,那么怎么解决呢,加锁,让所有的人卖的票必须要有有钥匙才能把卖票盒子打开,将这个数据保护起来。
1 Synchronized 关键字使用(面试问)
2.Lock接口
public class LIticket {
private int number = 30;
//可重入
private final ReentrantLock lock = new ReentrantLock(false);
//非公平锁 线程饿死 效率高
//公平锁 线程不饿死 效率低
public void sale() {
lock.lock();
try {
if (number <= 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:" + number);
} finally {
lock.unlock();
}
}
}
3.线程间的通信
Synchronized 通信
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
package com.example.juc.sync;
/**
* ClassName:Thred1
* Package:com.example.juc.sync
* Description:
*
* @Author:小飞机
* @Create:2025/5/23 - 15:38
* @Version: v1.0
*/
class Share{
private int number = 0;
//+1
public synchronized void add() throws InterruptedException {
//1.判断
if (number != 0) {
this.wait();
}
//2. 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3.通知
this.notifyAll();
}
public synchronized void dec() throws InterruptedException {
//1.判断 虚假唤醒 在哪里睡在哪里醒
if (number == 0) {
this.wait();
}
//2. 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3.通知
this.notifyAll();
}
}
public class Thred1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.dec();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
注意使用wait方法时,千万不要放在一个if中,线程是在哪里休眠就在哪里苏醒,他如果抢到了锁,他并不会去看是否是操作前后的,而是直接进行操作(也就是虚假唤醒)
Lock通信
解释一下Lock的实现类并没有实现通信的方法,也没有wait notifyall的方法,但是Condition类实现,,接下来根据文档的示例,你肯定也能模仿出来awai()==wait,notifyall=signall(文档中signal不是唤醒全部),我知道现在你只需要知道,这个类怎么样实例化,请看代码
package com.example.juc.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.prefs.Preferences;
/**
* ClassName:Thred2
* Package:com.example.juc.lock
* Description:
*
* @Author:小飞机
* @Create:2025/5/23 - 15:54
* @Version: v1.0
*/
class Share{
private int number =0;
private static Lock lock = new ReentrantLock();
private static Condition condition =lock.newCondition();
public void increase(){
lock.lock();
try {
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"==="+number);
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public void decrease(){
lock.lock();
try {
//解决虚假唤醒问题
while (number==0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"==="+number);
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
public class Thred2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
share.increase();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
share.decrease();
}
}, "B").start();
}
}
这里Condition还有一个非常强大的功能,定制化的通信,Condition相当于一个钥匙,当我在一个线程中A中调用线程B的singal,此时B线程将从等待状态变为锁定状态,进入锁池中竞争锁,并等待获取cpu权限,例如我现在要让abc顺序分别打印,a打印完通知b,b打印完通知c,循环如此,记得要用标识位子去标识当前可以打印的线程,避免其他线程抢到锁后就开始打印
package com.example.juc.lock;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ClassName:Thred3
* Package:com.example.juc.lock
* Description:
*
* @Author:小飞机
* @Create:2025/5/23 - 16:04
* @Version: v1.0
*/
class ShareResource {
//定义标识位
private int flags = 1;
//锁
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5() throws InterruptedException {
lock.lock();
try {
while (flags != 1) {
c1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
}
flags = 2;
//唤醒c2
c2.signal();
}finally {
lock.unlock();
}
}
public void print10() throws InterruptedException {
lock.lock();
try {
while (flags != 2) {
c2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
}
flags = 3;
//唤醒c3
c3.signal();
}finally {
lock.unlock();
}
}
public void print15() throws InterruptedException {
lock.lock();
try {
while (flags != 3) {
c3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
}
flags = 1;
//唤醒c1
c1.signal();
}finally {
lock.unlock();
}
}
}
public class Thred3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 0; i < 15; i++) {
try {
shareResource.print5();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 15; i++) {
try {
shareResource.print10();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 15; i++) {
try {
shareResource.print15();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
}
}
4.集合的线程安全
ArryList是线程安全的吗?
不是,从他的方法中可以并没有对他进行锁
多线程下的添加和修改可能会出现异常
怎么办,可以使用他的安全类,Vector,Collections,CopyOnWriteArrayList
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// List<String> list =new Vector<>();
// List<String> list = Collections.synchronizedList();
// 写时拷贝 并发读,不会锁住写,不会锁住读 并发写,不会锁住写,会锁住读
// CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
System.out.println(list);
}, "Thread" + i).start();
}
}
简单总结一下:
1.线程安全与线程不安全集合
集合类型中存在线程安全与线程不安全的两种,常见例如:
ArrayList ----- Vector
HashMap -----HashTable
但是以上都是通过 synchronized 关键字实现,效率较低
2.Collections 构建的线程安全集合3.java.util.concurrent 并发包下 CopyOnWriteArrayList CopyOnWriteArraySet 类型,通过动态数组与线程安全两个方面保证线程安全