java之线程同步
死锁
死锁的四个必要条件:
-
互斥条件:一个资源每次只能被一个进程使用
-
请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放
-
不可剥夺条件:对已获得资源,在未使用完前,不可被强制剥夺
-
循环等待条件:互相持有对方的资源保持不放,导致多个进程循环等待。
注:只要想办法突破其中一个或多个条件就能避免死锁的发生。
//死锁:互相持有对方的资源,僵持着
public class DeadLock {
public static void main(String[] args) {
MakeUp girl1=new MakeUp(0,"丑小鸭");
MakeUp girl2=new MakeUp(1,"白雪公主");
girl1.start();
girl2.start();
}
}
//口红
class LipStick{ }
//镜子
class Mirror{ }
//化妆
class MakeUp extends Thread{
//化妆需要口红和镜子
LipStick lipStick=new LipStick();
Mirror mirror=new Mirror();
int choice;
public MakeUp(int choice,String name){
super(name);//传入线程名字
this.choice=choice;
}
public void makeup() throws InterruptedException {
//开始化妆
//开始 A拿了口红,B拿了镜子
//之后 A又想要镜子,B又想要口红
//最后 A拿着口红又想要镜子,B拿着镜子又想要口红
//双方僵持不下,导致死锁
if(this.choice==0){
//拿着口红还要拿镜子
synchronized (lipStick){
System.out.println(this.getName()+"获得口红");
Thread.sleep(1000);
synchronized (mirror){
System.out.println(this.getName()+":我还想要镜子");
}
}
}else {
synchronized (mirror){
System.out.println(this.getName()+"获得镜子");
Thread.sleep(2000);
synchronized (lipStick){
System.out.println(this.getName()+":我还想要口红");
}
}
}
}
@Override
public void run() {
super.run();
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
解决办法:使用完资源后放下再去拿其他资源
if(this.choice==0){
//用完口红后释放,再去拿镜子
synchronized (lipStick){
System.out.println(this.getName()+"获得口红");
Thread.sleep(1000);
}
synchronized (mirror){
System.out.println(this.getName()+":我还想要镜子");
}
}else {
synchronized (mirror){
System.out.println(this.getName()+"获得镜子");
Thread.sleep(2000);
}
synchronized (lipStick){
System.out.println(this.getName()+":我还想要口红");
}
}
三大线程不安全案例使用同步机制解决
同步就是让多个线程排队使用资源,就不会出现资源抢夺。
由于每个线程都有自己的工作内存,控制不当就容易造成数据混乱。
每个对象都有一把锁,同步就是拿到对象的锁,等线程使用完对象后,紧接着下一个线程
- 同步代码块:synchronized (object){}
- 同步方法:public synchronized void test(){}
//不安全取钱
public class AcountTh {
public static void main(String[] args) {
Account account=new Account(200,"医保基金");
drawMoney d1=new drawMoney(account,100,"老婆");
drawMoney d2=new drawMoney(account,50,"老公");
d1.start();
d2.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡号
public Account( int money,String name){
this.money=money;
this.name=name;
}
}
//模拟取款
class drawMoney extends Thread{
private Account account;
private int outMoney;//要取出的钱
private int nowMoney;//手里的钱
private String name;//谁取得钱
public drawMoney(Account account,int outMoney,String name){
super(name);
this.account=account;
this.outMoney=outMoney;
}
//取钱
@Override
public void run() {
synchronized (account) {//拿到account的锁,让线程可以排队使用
if (this.account.money - this.outMoney < 0) {
System.out.println(this.getName() + ":钱不够了");
return;
}//Thread.currentThread().getName() = this.getName()
try {
Thread.sleep(1000);//放大问题的发生
} catch (InterruptedException e) {
e.printStackTrace();
}
this.account.money = this.account.money - this.outMoney;//更新余额
this.nowMoney = this.nowMoney + this.outMoney;
System.out.println(this.getName() + ":取了" + nowMoney);
System.out.println(this.getName() + ":卡里还有" + this.account.money);
}
}
}
//不安全的买票
//线程不安全,有负数
public class BuyTicket {
public static void main(String[] args) {
Station station=new Station();
new Thread(station,"幸运的你").start();
new Thread(station,"可怜的我们").start();
new Thread(station,"可恶的黄牛党").start();
}
}
class Station implements Runnable{
//车票数
private int tickets=10;
boolean flag=true;//设置停止线程的标志位
@Override
public synchronized void run() {//synchronized默认锁的是this
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void buy() throws InterruptedException {
if(tickets<=0){
System.out.println("票卖完了");
flag=false;
}
Thread.sleep(100);//模拟延时放大问题的发生性
System.out.println(Thread.currentThread().getName()+"买了第"+tickets--);
}
}
//不安全的集合----List
//会导致多个线程添加到同一个空间
public class UnSafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list=new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
synchronized (list) {//如果不将list锁住,多个线程可能会被同时添加到list的同一个空间
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}
线程安全的集合
//juc(java.util.concurrent并发包)安全类型
public class JUCtest {
public static void main(String[] args) throws InterruptedException {
//CopyOnWriteArrayList是写好的线程安全的,而list是线程不安全的
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}
加锁的两种方式
加锁是为了拿到对象的锁,避免线程抢夺资源,实现同步
显示加锁:使用juc包下的ReentrantLock(可重入锁)创建一把锁,再使用lock()方法,最后使用unlock()关闭锁
隐式加锁:使用同步代码块或同步方法,上面已经介绍了这种方法。
//显示加锁测试
public class testLock {
public static void main(String[] args) {
buyTicket buy=new buyTicket();
new Thread(buy,"我").start();
new Thread(buy,"你").start();
new Thread(buy,"他").start();
}
}
//显示加锁,使用juc并发编程中的lock
/*
private final ReentrantLock lock=new ReentrantLock();//创建一把可重入锁
public void run() {
lock.lock();//加锁
try{
要锁的代码块
}finally {
lock.unlock();//释放锁
}
}
* */
class buyTicket implements Runnable{
private int tickets=10;
boolean flag=true;
private final ReentrantLock lock=new ReentrantLock();//创建一把可重入锁
@Override
public void run() {
while (flag) {
lock.lock();//加锁
try{
if(tickets>0){
System.out.println("抢到了第"+tickets--+"张票");
}
else {
flag=false;
}
}finally {
lock.unlock();//释放锁
}
}
}
}
synchronized与lock的对比
- lock是显示锁,需要手动开启与关闭,而synchronized是隐式锁,出了作用域后自动释放
- synchronized可以锁住方法块也能锁住代码块,而lock只能锁住代码块
- 使用lock可以减少JVM调度线程的时间
- 使用建议:lock>synchronized代码块>synchronized方法
线程通信之生产者,消费者问题
生产者生产了产品,消费者才能消费产品,但是如何让消费者知道有产品可以消费了。
又如何让生产者知道产品消费完了,需要生产产品了呢?
这就要线程之间相互通信,涉及两个重要方法:wait()和notify All()
-
第一种解决方案:管程法------使用缓冲池作为容器存放产品
-
第二种解决方案:信号灯法------使用标志位
话不多说上代码
//生产者消费者模型-----线程通信之管程法:使用缓冲区
//模型分析:生产者、消费者、缓冲区、产品
//生产者将产品放入缓冲区后,通知消费者取
//消费者取完产品后,通知生产者生产产品
public class testPC {
public static void main(String[] args) {
ProContainer container=new ProContainer();
Productor productor=new Productor(container,"母鸡生产鸡场");
Customer customer=new Customer(container,"肯德基");
productor.start();
customer.start();
}
}
//生产者
class Productor extends Thread{
ProContainer container;
public Productor(ProContainer container,String name){
super(name);//传入生产者姓名
this.container=container;
}
@Override
public void run() {
super.run();
//生产产品
for (int i = 0; i < 100; i++) {
try {
container.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"生产了第"+i+"号鸡");
}
}
}
//消费者
class Customer extends Thread{
ProContainer container;
public Customer(ProContainer container,String name){
super(name);
this.container=container;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
try {
Chicken chicken = container.pop();
System.out.println(this.getName()+"拿到了第"+chicken.id+"号鸡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品,假设生产鸡
class Chicken{
int id;
public Chicken(int id){
this.id=id;
}
}
//缓冲区
class ProContainer{
//容器大小,缓冲池最多放10个产品
Chicken[]chickens=new Chicken[10];
int count=0;//计数
//生产者生产鸡
public synchronized void push(Chicken chicken) throws InterruptedException {
//如果容器已满,生产者等待
if(count==chickens.length){
this.wait();
}//否则就通知消费者,已经做好了一个产品
this.notifyAll();
chickens[count]=chicken;
count++;
}
//消费者消费
public synchronized Chicken pop() throws InterruptedException {
//如果没有产品就通知消费者等待
if(count==0){
this.wait();
}
//如果容器有产品,就通知生产者已经消费了一个产品
count--;
Chicken chicken = chickens[count];
this.notifyAll();//唤醒其他处于等待的线程
return chicken;
}
}
//生产者消费者模型-----线程通信之信号灯法:使用标志位
public class testPC2 {
public static void main(String[] args) {
TV tv=new TV("闪光的乐队");
Actor actor=new Actor(tv);
Watcher watcher=new Watcher(tv);
actor.start();
watcher.start();
}
}
//生产者--演员
class Actor extends Thread{
TV tv;
public Actor(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
tv.perform();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i%2==0){
System.out.println("抖音:记录美好生活");//广告植入
}
}
}
}
//消费者--观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
tv.wathch();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品--电视
class TV{
String performName;//电视名
boolean flag=false;//true:有节目播放,false:没有节目播放
public TV(String performName){
this.performName=performName;
}
//演员表演节目
public synchronized void perform() throws InterruptedException {
//有节目播放,就演员等待
if(!flag){
this.wait();
}
//没有节目就通知演员表演节目
this.notifyAll();
System.out.println("表演了"+performName);
this.flag=!this.flag;
}
//观众给观看节目
public synchronized void wathch() throws InterruptedException {
//没有节目播放,就观众等待
if(flag){
this.wait();
}
//有节目就通知观众观看
this.notifyAll();
System.out.println("播放了"+performName);
this.flag=!this.flag;
}
}
线程池
作用:避免频繁的创建和销毁,实现重复利用,好比共享单车
资源消耗低,提高响应速度,方便线程的管理
通过Executors创建ExecutorService,使用excute方法开启线程
public class testPool {
public static void main(String[] args) {
//创建线程池---通过Executors创建ExecutorService,使用excute方法开启线程
ExecutorService pool= Executors.newFixedThreadPool(10);//线程池数量
//执行线程
pool.execute(new myTh());
pool.execute(new myTh());
pool.execute(new myTh());
pool.execute(new myTh());
pool.execute(new myTh());
pool.execute(new myTh());
//关闭连接
pool.shutdown();
}
}
class myTh implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧