首页 > 其他分享 >线程同步和死锁

线程同步和死锁

时间:2022-11-11 11:22:53浏览次数:48  
标签:同步 synchronized accout int 死锁 线程 new public

线程同步

线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

synchronized锁

为了保证多线程并发安全,在访问时加入锁机制synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

线程不安全例子

  • 不安全的买票

    public class unsafeByTickets {
        public static void main(String[] args) {
            BuyTickets station = new BuyTickets();
            //2、三个线程买票
            new Thread(station,"小明").start();
            new Thread(station,"小红").start();
            new Thread(station,"老师").start();
        }
    }
    
    //1、首先要有一个买票的类
    class BuyTickets implements Runnable{
        //票
        private int tickets = 10;
        boolean flag = true;
        @Override
        public void run() {
            while (flag){
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private void buy() throws InterruptedException {
            if(tickets <= 0){
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName()+"买了第"+tickets--+"票");
            Thread.sleep(100);
        }
    }
    

    执行结果:线程不安全

  • 不安全的取钱

    public class UnsafeBank {
        public static void main(String[] args) {
            Accout accout = new Accout("结婚基金",100);
    
            //两个进程取同一个账户里面的钱
            new Bank(accout,100,new Person("小王",0)).start();
            new Bank(accout,50,new Person("小红",0)).start();
        }
    }
    
    //1、账户类
    class Accout {
        private  int accoutMoney;//账户里的钱
        private String name;//账户名
    
        public Accout(String name, int accoutMoney) {
            this.name = name;
            this.accoutMoney = accoutMoney;
        }
    
        public int getAccoutMoney() {
            return accoutMoney;
        }
        public void setAccoutMoney(int accoutMoney){
            this.accoutMoney = accoutMoney;
        }
    }
    
    //2、取款人
    class Person{
        private String name;//姓名
        private int personMoney;//手里的钱
    
        public Person(String name,int personMoney){
            this.name = name;
            this.personMoney = personMoney;
        }
    
        public String getName() {
            return name;
        }
    
        public int getPersonMoney() {
            return personMoney;
        }
    
        public void setPersonMoney(int personMoney) {
            this.personMoney = personMoney;
        }
    }
    
    //3、银行类,可以直接继承Thread
    class Bank extends Thread{
        Accout accout;
        int drawingMoney;//取的钱
        Person person;
    
        @Override
        public void run() {
            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //判断账户里面的钱够不够取
            if(drawingMoney > accout.getAccoutMoney()){
                System.out.println(person.getName()+"钱不够了");
                return;
            }
    
            accout.setAccoutMoney(accout.getAccoutMoney() - drawingMoney);
            person.setPersonMoney(person.getPersonMoney()+drawingMoney);
            System.out.println(person.getName()+"取钱后,账户里的钱:" +accout.getAccoutMoney());
            System.out.println(person.getName()+"手里的钱:"+person.getPersonMoney());
        }
    
        //构造函数
        public Bank(Accout accout,int drawingMoney,Person person){
           this.accout = accout;
           this.drawingMoney = drawingMoney;
           this.person = person;
        }
    
    }
    

    执行结果:两人都可以成功取钱,线程不安全

  • 不安全的集合:arraylistt为例子

    import java.util.ArrayList;
    
    public class UnsafeList {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            System.out.println(list.size());
        }
    }
    

    结果:list容量不足10000,说明有多个线程操作了同一个list位置

同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制——synchronized关键字,它包括两种用法:synchronized方法和synchronized块

    public synchronized void method(int args){}//同步方法
    
  • synchronized方法控制对“对象"的访问,每个对象对应一把锁,每个synchronized方法必须获得该方法的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

    缺陷:若将一个大的方法申明为synchronized将会影响效率

  • 方法里面需要修改的内容才需要锁,锁得太多,浪费资源

同步块

  • 同步块:synchronized(obj){}

  • obj称为同步监视器

    • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法得同步监视器就是this这个对象本身,或者是class
  • 同步监视器得执行过程

    1、第一个线程访问,锁定同步监视器,执行其中代码

    2、第二个线程访问,发现同步监视器被锁定,无法访问

    3、第一个线程访问完毕,解锁同步监视器

    4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问

线程安全例子

  • 取票安全

    ```java
    public class unsafeByTickets {
        public static void main(String[] args) {
            BuyTickets station = new BuyTickets();
            //2、三个线程买票
            new Thread(station,"小明").start();
            new Thread(station,"小红").start();
            new Thread(station,"老师").start();
        }
    }
    
    //1、首先要有一个买票的类
    class BuyTickets implements Runnable{
        //票
        private int tickets = 10;
        boolean flag = true;
        @Override
        public void run() {
            while (flag){
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        //同步方法
        private synchronized void buy() throws InterruptedException {
            if(tickets <= 0){
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName()+"买了第"+tickets--+"票");
            Thread.sleep(100);
        }
    }
    ```
    
  • 取钱安全

    public class UnsafeBank {
        public static void main(String[] args) {
            Accout accout = new Accout("结婚基金",100);
    
            //两个进程取同一个账户里面的钱
            new Bank(accout,100,new Person("小王",0)).start();
            new Bank(accout,50,new Person("小红",0)).start();
        }
    }
    
    //1、账户类
    class Accout {
        private  int accoutMoney;//账户里的钱
        private String name;//账户名
    
        public Accout(String name, int accoutMoney) {
            this.name = name;
            this.accoutMoney = accoutMoney;
        }
    
        public int getAccoutMoney() {
            return accoutMoney;
        }
        public void setAccoutMoney(int accoutMoney){
            this.accoutMoney = accoutMoney;
        }
    }
    
    //2、取款人
    class Person{
        private String name;//姓名
        private int personMoney;//手里的钱
    
        public Person(String name,int personMoney){
            this.name = name;
            this.personMoney = personMoney;
        }
    
        public String getName() {
            return name;
        }
    
        public int getPersonMoney() {
            return personMoney;
        }
    
        public void setPersonMoney(int personMoney) {
            this.personMoney = personMoney;
        }
    }
    
    //3、银行类,可以直接继承Thread
    class Bank extends Thread{
        Accout accout;
        int drawingMoney;//取的钱
        Person person;
    
        @Override
        public void run() {
            //如果使用同步方法,监视器对象默认是Bank,没有效果
            //同步代码块,监视器对象是accout
            synchronized (accout) {
                //模拟延时
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                //判断账户里面的钱够不够取
                if (drawingMoney > accout.getAccoutMoney()) {
                    System.out.println(person.getName() + "钱不够了");
                    return;
                }
    
                accout.setAccoutMoney(accout.getAccoutMoney() - drawingMoney);
                person.setPersonMoney(person.getPersonMoney() + drawingMoney);
                System.out.println(person.getName() + "取钱后,账户里的钱:" + accout.getAccoutMoney());
                System.out.println(person.getName() + "手里的钱:" + person.getPersonMoney());
            }
        }
    
        //构造函数
        public Bank(Accout accout,int drawingMoney,Person person){
           this.accout = accout;
           this.drawingMoney = drawingMoney;
           this.person = person;
        }
    
    }
    
  • 集合安全

    import java.util.ArrayList;
    
    public class UnsafeList {
        public static void main(String[] args) throws InterruptedException {
            ArrayList<String> list = new ArrayList<String>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    synchronized (list){
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
            //加上延时,不然可能线程没操作完
            Thread.sleep(1000);
            System.out.println(list.size());
        }
    }
    

Lock锁

Lock锁介绍

  • java提供了更强大的线程同步机制——通过显式定义(synchronized是隐式的)同步锁对象来实现同步。同步锁使用Lock对象充当。

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源前应先获得Lock对象。

  • ReentrantLock类(可重入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

  • 格式:

    class A{
        private final ReentrantLock lock = new ReentantLock();
        public void m(){
            lock.lock();//加锁
            try{
                //需要加锁的代码
            }
            finally{
                lock.unlock;//解锁
            }
        }
    }
    

synchronized和Lock对比

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

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

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

优先使用顺序:Lock>同步代码块>同步方法

死锁

死锁定义

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

例子:

public class DeadLock {
    public static void main(String[] args) {
        //4、两个女孩互相持有资源并僵持
        new MakeUp("白雪公主",0).start();
        new MakeUp("灰姑娘",1).start();
    }
}

//1、先准备两份资源:镜子和口红
class Mirror{
}
class Lipstick{

}
//2、化妆类
class MakeUp extends Thread{
    //使用static保证每个资源只有一份
    //记得new一下,因为static必须初始化,否则会NullPointerException
    static Mirror mirror = new Mirror();
    static Lipstick lipstick = new Lipstick();

    private String girlName;
    private int ID;

    MakeUp(String girlName,int ID){
        this.girlName = girlName;
        this.ID = ID;
    }

    //3、准备一个化妆方法
    private void makeUp() throws InterruptedException {
        if(ID == 0){
            //一个同步块里面有两个锁,就可能发生死锁
            synchronized (mirror){//获得镜子的锁
                Thread.sleep(1000);
                System.out.println(girlName+"获得镜子的锁");
                synchronized (lipstick){//获得口红的锁
                     System.out.println(girlName+"获得口红的锁");
                }
            }  
        }else {
            synchronized (lipstick){//获得口红的锁
                Thread.sleep(1000);
                System.out.println(girlName+"获得口红的锁");
                synchronized (mirror){//获得镜子的锁
                    System.out.println(girlName+"获得镜子的锁");
                }
            }
        }
    }
    @Override
    public void run() {
        try {
            makeUp();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:死锁

解决:不让一个同步块里面有两个锁

public class DeadLock {
    public static void main(String[] args) {
        //4、两个女孩互相持有资源并僵持
        new MakeUp("白雪公主",0).start();
        new MakeUp("灰姑娘",1).start();
    }
}

//1、先准备两份资源:镜子和口红
class Mirror{
}
class Lipstick{

}
//2、化妆类
class MakeUp extends Thread{
    //使用static保证每个资源只有一份
    //记得new一下,因为static必须初始化,否则会NullPointerException
    static Mirror mirror = new Mirror();
    static Lipstick lipstick = new Lipstick();

    private String girlName;
    private int ID;

    MakeUp(String girlName,int ID){
        this.girlName = girlName;
        this.ID = ID;
    }

    //3、准备一个化妆方法
    private void makeUp() throws InterruptedException {
        if(ID == 0){
            synchronized (mirror){//获得镜子的锁
                Thread.sleep(1000);
                System.out.println(girlName+"获得镜子的锁");
            }
            synchronized (lipstick){//获得口红的锁
                System.out.println(girlName+"获得口红的锁");
            }
        }else {
            synchronized (lipstick){//获得口红的锁
                Thread.sleep(1000);
                System.out.println(girlName+"获得口红的锁");
            }
            synchronized (mirror){//获得镜子的锁
                System.out.println(girlName+"获得镜子的锁");
            }
        }
    }
    @Override
    public void run() {
        try {
            makeUp();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:避免死锁

死锁避免方法

发生死锁的四个必要条件:互斥;请求与保持;不抢占;循环等待

只要破坏其中任意一个或多个条件就可以避免死锁发生

标签:同步,synchronized,accout,int,死锁,线程,new,public
From: https://www.cnblogs.com/xiluoluo/p/16879973.html

相关文章

  • 读者-写者(多线程)
    1.描述操作系统中“读者-写者”问题,理解问题的本质,提交你理解或查找到的文本资料问题描述:多个进程访问一个共享的数据区读者(读进程)只能读数据,写者(写进程)只能写数据......
  • Java多线程 CompletionService和ExecutorCompletionService
    目录​​一、说明​​​​二、理解​​​​三、实现​​​​1.使用Future​​​​2.使用ExecutorCompletionService​​​​3.take()方法​​​​4.poll()方法​​​​5.pol......
  • Java多线程 Callable和Future
    目录​​一、说明​​​​二、理解​​​​三、实现​​​​1.实现接口​​​​2.执行线程​​一、说明Java提供了三种创建线程的方法实现​​Runnable​​接口继承​​T......
  • Java多线程 Future和FutureTask的区别
    目录​​一、说明​​​​二、理解​​​​三、实现​​​​1.实现接口​​​​2.使用Future​​​​3.使用FutureTask​​一、说明Future和FutureTask的关系Future是一个......
  • Java多线程 ThreadPoolExecutor-RejectedExecutionHandler拒绝执行策略
    目录​​一、说明​​​​二、理解​​​​三、实现​​​​1.AbortPolicy​​​​2.DiscardPolicy​​​​3.DiscardOldestPolicy​​​​4.CallerRunsPolicy​​​​5.自......
  • Java多线程 线程池Executor框架
    目录​​一、说明​​​​二、理解​​​​Executor​​​​ExecutorService​​​​Executors​​​​三、实现​​​​1.newSingleThreadExecutor​​​​2.newFixedThr......
  • centos7自建yum仓库同步阿里云yum源
    背景:内网环境服务器不能直接安装工具或服务,可以用一台外网服务器同步阿里云的yum仓库,作为本地仓库搭建本地yum仓库编辑yum配置文件,开启缓存使用功能,设置缓存路径cp/e......
  • 定位java程序中占用cpu最高的线程堆栈信息
    找出占用cpu最高的线程堆栈信息在java编码中,有时会因为粗心导致cpu占用较高的情况,为了避免影响程序的正常运行,需要找到问题并解决。这里模拟一个cpu占用较高的场景,并尝试......
  • 【操作系统】03-处理机调度与死锁
    计算机操作系统——调度与死锁目录计算机操作系统——调度与死锁第三章处理机调度与死锁3.1处理机调度的层次和调度算法的目标3.1.1处理机调度层次3.1.2处理机调度算......
  • Executor 线程池原理
    线程池的创建publicThreadPoolExecutor(intcorePoolSize,//核心线程数intmaximumPoolSize,//最大线程数longkeepAli......