线程同步、队列和锁、同步方法及同步块、Lock

线程同步

多个线程操作同一个资源

并发:同一个对象被多个线程同时操作

队列和锁

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问 冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制 synchronized. ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待, 使用后释放锁即可 . 存在以下问题
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引 起性能问题;
    • 如果一个优先级高的线程等待—个优先级低的线程释放锁 会导致优先级倒 置,引起性能问题.
  • 三大不安全案列
    • 不安全的买票
 package com.cjp.syn;
    
    //不安全的买票
    //不安全 有负数或者买到相同的票
    public class UnsafeBuyTicket {
        public static void main(String[] args) {
            BuyTicket buyTicket = new BuyTicket();
            new Thread(buyTicket,"老师").start();
            new Thread(buyTicket,"学生").start();
            new Thread(buyTicket,"校长").start();
        }
    }
    
    class BuyTicket implements Runnable{
        private int ticketNums = 10;   //外部停止方式
        boolean flag = true;
        @Override
        public void run() {
            //买票
            while (flag){
                buy();
            }
        }
    
        private void buy(){
            //判断是否有票
            if(ticketNums<=0){
                flag = false;
                return;
            }
            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //买票
            System.out.println(Thread.currentThread().getName()+"-->买到了第"+ticketNums--+"张票");
        }
    }
  • 不安全的取钱
package com.cjp.syn;
    
    //不安全的取钱
    //两个人去银行取钱
    public class UnsafeBank {
        public static void main(String[] args) {
            Account account = new Account(100,"结婚基金");
            Drawing you =new Drawing(account,50,"你");
            Drawing girlFriend =new Drawing(account,100,"girlFriend");
            you.start();
            girlFriend.start();
        }
    }
    
    //账户
    class Account{
        int money;//余额
        String name;//卡名
    
        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
    
    //银行 模拟取款
    class Drawing extends Thread{
        Account account;//账户
        int drawingMoney;//取了多少钱
        int nowMoney;//现在手里有多少钱
    
        public Drawing(Account account,int drawingMoney,String name){
            super(name);
            this.account = account;
            this.drawingMoney = drawingMoney;
            
        }
    
        //取钱
        @Override
        public void run() {
            //锁的对象就是变化的量,需要增删改的对象
          sychronized(account){
            //判断有没有钱
            if (account.money-drawingMoney<0);{
                System.out.println(Thread.currentThread().getName() + "账户余额不足,取不了");
                return;
            }
    
            //卡内余额 = 余额 - 取出的钱
            account.money = account.money-drawingMoney;
            //你手里的钱
            nowMoney = nowMoney+drawingMoney;
    
            System.out.println(account.name+"余额为:"+account.money);
            //Thread.currentThread().getName()=this.getName()
            System.out.println(this.getName()+"手里的钱:"+nowMoney);
          }
        }
    
    
    }
  • 线程不安全集合
 package com.cjp.syn;
    
    import java.util.ArrayList;
    import java.util.List;
    
    //线程不安全的集合
    public class UnsafeList {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(list.size());
    
        }
    }

输出结果:

9996

同步方法及同步块

同步方法

  • 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需 要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法 synchronized 方法 和synchronized 块.
    • 同步方法:public synchronized void method(int args) {}
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个 synchronized方法都心须获得调用该方法的对象的锁才能执行,否则线程会阻塞 方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获 得这个锁,继续执行
    • 缺陷:若将一个大的方法申明为synchronized 将会影响效率
  • 买票同步方法
   private synchronized void buy(){
              //判断是否有票
              if(ticketNums<=0){
                  flag = false;
                  return;
              }
              //模拟延时
              try {
                  Thread.sleep(100);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //买票
              System.out.println(Thread.currentThread().getName()+"-->买到了第"+ticketNums--+"张票");
          }
  

同步块

  • 同步块:synchronized (Obj){}
  • Obj 称之为 同步监视器
  • 0bj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是 这这个对象本身,或者是class[ 反射中讲解]
  • 同步监视器的执行过程
    1.第一个线程访问,锁定同步监视器,执行其中代码.
    2.第二个线程访问,发现同步监视器被锁定,无法访问.
    1. 第一个线程访问完毕,解锁同步监视器
    2. 第二个线程访问,发现同步监视器没有锁 ,然后锁定并访问
  • 银行取钱同步块
 sychronized(account){
              //判断有没有钱
              if (account.money-drawingMoney<0);{
                  System.out.println(Thread.currentThread().getName() + "账户余额不足,取不了");
                  return;
              }
      
              //卡内余额 = 余额 - 取出的钱
              account.money = account.money-drawingMoney;
              //你手里的钱
              nowMoney = nowMoney+drawingMoney;
      
              System.out.println(account.name+"余额为:"+account.money);
              //Thread.currentThread().getName()=this.getName()
              System.out.println(this.getName()+"手里的钱:"+nowMoney);
            }
  • 线程集合同步块
  synchronized (list) {
                          list.add(Thread.currentThread().getName());
                      }

CopyOnWriteArrayList

  package com.cjp.syn;
      
      import java.util.concurrent.CopyOnWriteArrayList;
      
      //测试JUC安全类型的集合
      public class TestJUC {
          public static void main(String[] args) {
              CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
              for (int i = 0; i < 10000; i++) {
                  new Thread(()->{
                      list.add(Thread.currentThread().getName());
                  }).start();
              }
              try {
                  Thread.sleep(3000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              System.out.println(list.size());
          }
      }

死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而 导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块 同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.

    package com.cjp.syn;
    
    //死锁:多个线程互相拥有对方所需要的资源,然后行成僵持。
    public class DeadLock {
        public static void main(String[] args) {
            Makeup g1 = new Makeup(0,"小红");
            Makeup g2 = new Makeup(1,"小丽");
    
            g1.start();
            g2.start();
        }
    }
    
    
    //口红
    class LipStick{
    
    }
    
    //镜子
    class Mirror{
    
    }
    
    class Makeup extends Thread{
        //需要的资源只有一份,用static来保证资源只有一份
        static LipStick lipStick = new LipStick();
        static Mirror mirror = new Mirror();
    
        int choice;//选择
        String girlName;//使用化妆品的人
    
        public Makeup(int choice,String girlName){
            this.choice = choice;
            this.girlName = girlName;
        }
    
        @Override
        public void run() {
            //化妆
            try {
                makeup();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        //化妆,互相持有对方的锁,需要拿到对方的资源
        public void makeup() throws InterruptedException {
            if (choice == 0){
                synchronized (lipStick){ //获得口红的锁
                    System.out.println(this.girlName+"获得口红的锁");
                    Thread.sleep(1000);
    
                    synchronized (mirror){ //一秒钟后想获得镜子
                        System.out.println(girlName+"获得镜子的锁");
                    }
                }
    
            }else {
                synchronized (mirror){ //获得镜子的锁
                    System.out.println(this.girlName+"获得镜子的锁");
                    Thread.sleep(2000);
    
                    synchronized (lipStick){ //一秒钟后想获得口红
                        System.out.println(girlName+"获得口红的锁");
                    }
                }
            }
        }
    }

输出结果:

小红获得口红的锁

小丽获得镜子的锁

  • 解决死锁
  public void makeup() throws InterruptedException {
              if (choice == 0){
                  synchronized (lipStick){ //获得口红的锁
                      System.out.println(this.girlName+"获得口红的锁");
                      Thread.sleep(1000);
                  }
                  synchronized (mirror){ //一秒钟后想获得镜子
                      System.out.println(girlName+"获得镜子的锁");
                  }
      
              }else {
                  synchronized (mirror){ //获得镜子的锁
                      System.out.println(this.girlName+"获得镜子的锁");
                      Thread.sleep(2000);
                  }
                  synchronized (lipStick){ //一秒钟后想获得口红
                      System.out.println(girlName+"获得口红的锁");
                  }
              }
          }

输出结果:
小红获得口红的锁
小丽获得镜子的锁
小红获得镜子的锁
小丽获得口红的锁

死锁产生的条件

•产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件: 个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥奇条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
    只要想办法破坏上述四个条件中的一个,就可以避免死锁的发生

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开 始访问共享资源之前应先获得Lock对象
  • ReentrantLock(可重入锁) 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语 义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释 放锁。
   package com.cjp.lock;
    
    import com.cjp.stop.TestStop;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestLock {
        public static void main(String[] args) {
            TestLock2 lock2 = new TestLock2();
            new Thread(lock2).start();
            new Thread(lock2).start();
            new Thread(lock2).start();
    
        }
    
    }
    
    class TestLock2 implements Runnable{
        int ticketNums = 10;
    
        //定义lock锁
        private final ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true){
                try {
                    lock.lock();//加锁
                    if(ticketNums>0){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(ticketNums--);
                    }else {
                        break;
                    }
                }finally {
                    lock.unlock();//解锁
                }
            }
        }
    }

synchronized与Lock的对比

• Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了 作用域自动释放

• Lock只有代码块锁,synchronized有代码块锁和方法锁

•使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展 性(提供更多的子类)

  • 优先使用顺序:

     •Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方 法体之外)  
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值