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());
    }
}
posted @   阿落小世界  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示