JavaSE02_Day01(中)-线程并发安全(线程同步、synchronized、同步块优化线程问题、静态线程同步)

一、线程并发安全

1.1 线程同步

       讲解线程知识点的目的,其实是为了更好的使用CPU的资源,当多条线程并发对同一临界资源进行操作时,可能由于线程切换时机的不确定,导致程序在最终的执行过程中出现了错乱,或者程序瘫痪的情况。

假如多条线程共享类中的实例变量/静态变量,就可以称之为对同一临界资源进行操作。

临界资源

临界资源是一次仅允许一个进程使用的共享资源。

各进程采取互斥的方式,实现共享的资源称作临界资源。

属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。

诸进程间采取互斥方式,实现对这种资源的共享。

临界区

每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。

不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。

多个进程涉及到同一个临界资源的的临界区称为相关临界区。

使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。

       如果说需要解决刚刚上面所阐述的线程同步安全问题,需要将异步操作变成同步操作,可以有效的去解决"抢"资源的现象发生。

 package cn.tedu.thread02;
 /**
  * 线程同步并发安全问题案例
  * @author cjn
  *
  */
 public class ThreadBad {
 
     public static void main(String[] args) {
         BandCard bandCard = new BandCard();
         
         //支付宝绑定银行卡付款
         Thread thread1 = new Thread() {
             @Override
             public void run() {
                 bandCard.getMoney();
            }
        };
         
         //微信绑定银行卡付款
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 bandCard.getMoney();
            }
        };
         //修改线程名称
         thread1.setName("支付宝");
         thread2.setName("微信");
         
         //启动线程
         thread1.start();
         thread2.start();
 
    }
 
 }
 
 /**
  * 外部银行类
  */
 class BandCard {
     //银行卡中余额
     private double money = 500;
     
     /**
      * 获取银行卡中金额的方法
      */
     public void getMoney() {
         //假设购买500元的商品,如果银行卡中金额少于500不能付钱
         if (money >= 500) {
             System.out.println(Thread.currentThread().getName() + ":支付500元成功!!!");
             //复合赋值运算符 money = money - 500;
             money -= 500;
        }
         //打印银行卡余额
         System.out.println(Thread.currentThread().getName() + money);
    }
     
 }
 

测试结果:

 支付宝:支付500元成功!!!
 微信:支付500元成功!!!
 微信0.0
 支付宝-500.0

1.2 synchronized关键字

        Java中提供了一个synchronized关键字,该关键字是同步锁,可以将某段代码变成同步操作,从而解决线程并发安全问题。

 package cn.tedu.thread02;
 /**
  * 使用同步方法解决线程并发安全问题案例
  * @author cjn
  *
  */
 public class SyncDemo01 {
     public static void main(String[] args) {
         Shop shop = new Shop();
         Thread thread1 = new Thread() {
             @Override
             public void run() {
                 shop.buy();
            }
        };
         
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 shop.buy();
            }
        };
         
         thread1.setName("孟滕滕");
         thread2.setName("苍老师");
         //启动线程
         thread1.start();
         thread2.start();
         
    }
 
 }
 
 /**
  * 外部购物类
  */
 class Shop {
     /*
      * 同步方法
      * 使用同步方法可以解决线程同步并发安全问题,相当于一个人来到店铺
      * 购物的时候是1v1服务,这个人进入店铺以后,店铺就不再对其他顾客进行服务了,
      * 只服务当前这1位顾客,虽然可以解决线程同步并发安全问题,但是程序执行的
      * 性能较慢。
      */
     public synchronized void buy() {
         try {
             Thread thread = Thread.currentThread();
             System.out.println(thread.getName() + ":正在挑选衣服......");
             Thread.sleep(2000);
             System.out.println(thread.getName() + ":正在试衣服......");
             Thread.sleep(2000);
             System.out.println(thread.getName() + ":离开试衣间,进行结账离开");
        } catch (InterruptedException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }

测试结果:

 孟滕滕:正在挑选衣服......
 孟滕滕:正在试衣服......
 孟滕滕:离开试衣间,进行结账离开
 苍老师:正在挑选衣服......
 苍老师:正在试衣服......
 苍老师:离开试衣间,进行结账离开

1.3 同步块优化线程同步问题

       锁机制:Java中提供了一种内置的锁机制,也就是同步代码块,使用同步代码块,可以在确保线程并发安全的前提下,缩小同步范围,进而有效的进行解决同步方法所导致的性能慢的问题。

 synchronized (同步监视器对象->锁对象){
     //代码
 }

       锁对象:指代的是同一个锁对象的引用,通常我们会使用this(当前对象)关键字做为锁对象。

       锁范围:使用同步代码块的时候,需要在可以解决同步安全问题的前提下,尽可能的去缩小同步范围,进而去提高整个程序软件的效率。

 package cn.tedu.thread02;
 
 public class SyncDemo02 {
 
     public static void main(String[] args) {
         Shop1 shop = new Shop1();
         Thread thread1 = new Thread() {
             @Override
             public void run() {
                 shop.buy();
            }
        };
         
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 shop.buy();
            }
        };
         
         thread1.setName("孟滕滕");
         thread2.setName("苍老师");
         //启动线程
         thread1.start();
         thread2.start();
 
    }
 
 }
 /**
  * 外部购物类
  */
 class Shop1 {
 
     public void buy() {
         try {
             Thread thread = Thread.currentThread();
             System.out.println(thread.getName() + ":正在挑选衣服......");
             Thread.sleep(2000);
             /*
              * 同步监视器对象必须是同一对象,
              * 这个对象可以是Java中的任何对象,一般使用this表示当前对象。
              * 目的就是确保多个线程进行访问的是“同一个”资源
              */
             synchronized (this) {
                 System.out.println(thread.getName() + ":正在试衣服......");
                 Thread.sleep(2000);
            }
 
             System.out.println(thread.getName() + ":离开试衣间,进行结账离开");
        } catch (InterruptedException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }

测试结果:

 孟滕滕:正在挑选衣服......
 苍老师:正在挑选衣服......
 苍老师:正在试衣服......
 苍老师:离开试衣间,进行结账离开
 孟滕滕:正在试衣服......
 孟滕滕:离开试衣间,进行结账离开

       如果测试的过程中,控制台显示苍老师试衣服和孟滕滕试衣服的输出语句挨着的情况,请注意查看,是苍老师试衣服离开试衣间的输出语句和孟滕滕正在试衣服的输出语句同时出现在控制台,这种情况是没有任何逻辑问题的,只不过是控制台打印的顺序问题。

1.4 静态线程同步

       比如当前类中声明的方法是一个静态方法,我们仍然可以对静态方法进行加锁,静态方法也会具备同步功能,此时锁的对象就不再是类的对象,而是类对象,就是每个类都有的唯一的类对象,就是在JVM进行加载类的时候,会进行实例化Class实例用于表示这个类。Class的实例可以通过类名.class的方法进行获取。

类的对象与类对象:

 (1)synchronized锁方法案例

 package cn.tedu.thread02;
 
 public class SyncDemo03 {
 
     public static void main(String[] args) {
         Thread thread1 = new Thread() {
             @Override
             public void run() {
                 Shop2.buy();
            }
        };
         
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 Shop2.buy();
            }
        };
         
         thread1.setName("孟滕滕");
         thread2.setName("苍老师");
         //启动线程
         thread1.start();
         thread2.start();
 
    }
 
 }
 /**
  * 外部购物类
  */
 class Shop2 {
 
     public static synchronized void buy() {
         try {
             Thread thread = Thread.currentThread();
             System.out.println(thread.getName() + ":正在挑选衣服......");
             Thread.sleep(2000);
 
             System.out.println(thread.getName() + ":正在试衣服......");
             Thread.sleep(2000);
             
             System.out.println(thread.getName() + ":离开试衣间,进行结账离开");
        } catch (InterruptedException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }

测试结果:

 苍老师:正在挑选衣服......
 苍老师:正在试衣服......
 苍老师:离开试衣间,进行结账离开
 孟滕滕:正在挑选衣服......
 孟滕滕:正在试衣服......
 孟滕滕:离开试衣间,进行结账离开

(2)synchronized案例优化
package cn.tedu.thread02;
 
 public class SyncDemo03 {
 
     public static void main(String[] args) {
         Thread thread1 = new Thread() {
             @Override
             public void run() {
                 Shop3.buy();
            }
        };
         
         Thread thread2 = new Thread() {
             @Override
             public void run() {
                 Shop3.buy();
            }
        };
         
         thread1.setName("孟滕滕");
         thread2.setName("苍老师");
         //启动线程
         thread1.start();
         thread2.start();
 
    }
 
 }
 /**
  * 外部购物类
  */
 class Shop3 {
 
     public static void buy() {
         try {
             Thread thread = Thread.currentThread();
             System.out.println(thread.getName() + ":正在挑选衣服......");
             Thread.sleep(2000);
             /*
              * 当前锁的对象是类对象,
              * 类对象可以通过类名.class进行获取
              * 在此处是不能书写this关键字的,因为
              * 静态方法中没有this
              */
             synchronized (Shop3.class) {
                 System.out.println(thread.getName() + ":正在试衣服......");
                 Thread.sleep(2000);
            }
             
             System.out.println(thread.getName() + ":离开试衣间,进行结账离开");
        } catch (InterruptedException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
 }

测试结果:

 苍老师:正在挑选衣服......
 苍老师:正在试衣服......
 苍老师:离开试衣间,进行结账离开
 孟滕滕:正在挑选衣服......
 孟滕滕:正在试衣服......
 孟滕滕:离开试衣间,进行结账离开


posted @ 2021-06-29 23:39  Coder_Cui  阅读(103)  评论(0编辑  收藏  举报