浅谈Java并发运行中的一些安全问题

共 6694字,需浏览 14分钟

 ·

2022-08-27 12:37

b8e723f3a0705608b1ec17dd0e46a975.webp程序员的成长之路互联网/程序员/技术/资料共享  关注


阅读本文大概需要 6 分钟。

来自:https://blog.csdn.net/weixin_47600732

1.什么是多线程并发运行安全问题?

当多个线程并发操作一个数据时,由于线程操作的时间不可控的原因,可能会导致操作该数据时的过程没有按照程序设计的执行顺序运行,导致操作后数据出现混乱,严重时可导致系统瘫痪。

2.用synchronized修饰的方法

当一个方法用synchronized修饰,那么该方法变为“同步方法“多个线程不能同时进入方法内容运行的,必须时有顺序的一个一个运行,这样就能避免并发安全问题。案例:抢豆豆事件(使豆豆的剩余量不能为负数)
    public class SyncDemo {
 public static void main(String[] args) {
  Table table=new Table();
  Thread t1=new Thread(){
   public void run(){
    while(true){
     int n=table.getBean();
     Thread.yield();
     System.out.println(Thread.currentThread().getName()+",豆豆还剩"+n);
    }
   }
  };
  
  Thread t2=new Thread(){
   public void run(){
    while(true){
     int n=table.getBean();
     Thread.yield();
     System.out.println(Thread.currentThread().getName()+",豆豆还剩"+n);
    }
   }
  };
  
  t1.start();
  t2.start();
 }
}


class Table{
 private  int bean=10;
 public synchronized int getBean(){
  if(bean==0){
   throw new RuntimeException("没有豆豆了");
  }
  /*
   * yield()将导致线程从运行状态转到就绪状态,但是可能没有效果
   * 作用时暂停当前正在执行的线程对象(放弃当前拥有的cpu资源),
   * 并执行其他线程。
   */

  Thread .yield();//模拟切换线程
  return bean--;
 }
}
运行结果:当豆豆剩余量为0时,程序抛出异常。如果不加锁的话,程序有一定的几率会跳过豆豆为0,这个条件,而一直运行下去。

3.同步块

1.有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高效率。2.同步块
    synchronized(同步监视器){
  //需要同步运行的代码片段
}
3.同步块可以更灵活准确的锁定需要同步运行的代码片段,这样可以有效缩小同步范围提高并发效率,但是需要注意,必须保证多个线程看到同步监视器对象是同一个对象才可以。案例:模拟商场买衣服
    public class Syncdemo2 {
 public static void main(String[] args) {
  Shop shop=new Shop();
  Thread t1=new Thread(){
   public void run(){
    shop.buy();
   }
  };
  
  
  Thread t2=new Thread(){
   public void run(){
    shop.buy();
   }
  };
  
  t1.start();
  t2.start();
 }
}


class Shop{
 public void buy(){
  try {
   String name=Thread.currentThread().getName();
   System.out.println(name+"选衣服");
   Thread.sleep(2000);
   synchronized (this) {
    System.out.println(name+"试衣服");
    Thread.sleep(2000);
   }
   System.out.println(name+"结账走人");
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
    
 }
}
运行结果:
    Thread-1选衣服
Thread-0选衣服
Thread-1试衣服
Thread-1结账走人
Thread-0试衣服
Thread-0结账走人
分析:两个线程不会同时在试衣服,因为在试衣服环节设置了同步块,这使线程在运行试衣服环节时变得有序。

4.使用Synchronized修饰静态方法

1.静态方法若使用了Synchronized修饰后,那么方法一定具有同步效果2.静态方法的对象是当前类的对象3.class类的每一个实例用于表达jvm加载一个类,当jvm加载一个类时后就会实例化一个class的实例用于表示它,每一个类在jvm都有且只有一个class的实例,所以静态方法锁的就是当前类对应的class的实例。
    public class Syncdemo3 {
 public static void main(String[] args) {
  Thread t1=new Thread(){
   public void run(){
    Foo.dosome();
   }
  };
  
  
  Thread t2=new Thread(){
   public void run(){
    Foo.dosome();
   }
  };
  
  t1.start();
  t2.start();
 }
}


class Foo{
 public synchronized static void dosome(){
  try {
   String name=Thread.currentThread().getName();
   System.out.println(name+"正在运行dosome方法");
   Thread.sleep(3000);
   System.out.println(name+"运行结束》》");
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

5.互斥锁

使用synchronized锁定多段代码,而锁的对象相同时,这些代码片段之间就是互斥锁,多个线程不能同时执行这些方法。
    public class Syncdemo4 {
 public static void main(String[] args){
  Eoo eoo=new Eoo();
  Thread t1=new Thread(){
   public void run(){
    eoo.test01();
   }
  };
  
  Thread t2=new Thread(){
   public void run(){
    eoo.test02();
   }
  };
  t1.start();
  t2.start();
 }
}


class Eoo{
 public synchronized void test01(){
  String name=Thread.currentThread().getName();
  System.out.println(name+"正在运行1方法");
  try {
   Thread.sleep(3000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  System.out.println(name+"1运行完毕");
 }
 
 public synchronized void test02(){
  String name=Thread.currentThread().getName();
  System.out.println(name+"正在运行2方法");
  try {
   Thread.sleep(3000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  System.out.println(name+"2运行完毕");
  
 }
}
运行结果:
    Thread-0正在运行1方法
Thread-01运行完毕
Thread-1正在运行2方法
Thread-12运行完毕
结果分析:因为两个线程锁的对象相同,因此,当一个线程运行它的方法时,另一个线程不会一起运行运行它的方法,直到,一个线程运行结束后,他才可以进入这个类,来运行他的方法。

6.死锁现象

线程都是保持着自己的锁,但是都是等待对方来释放锁,就出现互相”僵持“的情况,导致程序不会继续向后运行。
    public class Syncdemo5 {
 public static void main(String[] args) {
  Poo p=new Poo();
  Thread t1=new Thread(){
   public void run(){
    p.method1();
   }
  };
  
  Thread t2=new Thread(){
   public void run(){
    p.method2();
   }
  };
  t1.start();
  t2.start();
 }
}


class Poo{
 Object A=new Object();
 Object B=new Object();
 
 public void method1(){
  String name=Thread.currentThread().getName();
  synchronized (A) {
   try {
    System.out.println(name+"A正在运行。。。");
    Thread.sleep(3000);
    System.out.println(name+"A运行完毕");
    method2();
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 }
 
 
 public void method2(){
  String name=Thread.currentThread().getName();
  synchronized (B) {
   try {
    System.out.println(name+"B正在运行。。。");
    Thread.sleep(3000);
    System.out.println(name+"B运行完毕");
    method1();
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 }
}
运行结果:
    Thread-0A正在运行。。。
Thread-1B正在运行。。。
Thread-0A运行完毕
Thread-1B运行完毕
结果分析:虽然有输出结果,但是,程序并没有运行结束,两个线程都等待着对方来释放锁,而僵持不下。

7.wait()和sleep()的区别

1.wait是object类中的方法,sleep是Thread中的方法2.最主要的是sleep方法调用后,并没有释放锁,使得线程仍然可以同步控制,sleep不会让出出系统资源,sleep方法可以在任何地方使用,而wait必须在synchronized方法或者synchronized 块中使用,否则会抛出异常(java.lang.IllegalMonitorStateException),wait方法不仅让出cpu,还会释放已占有的同步资源;3.sleep必须捕获异常,而waitnofifynofifyAll不需要捕获异常。4.sleep是让某个线程暂时运行一段时间,其控制范围是由当前线程决定的,主动权在自己手里,而wait是由某个确定的对象来调用,主动权在某个对象手里。
    public class WaitDemo {
 public static void main(String[] args) {
  Thread t1=new ThreadMy01();
  Thread t2=new ThreadMy02();
  
  t1.start();
  t2.start();

  
 }
}

class ThreadMy01 extends Thread{
 public static StringBuilder str=new StringBuilder();
 public void run(){
  String name=Thread.currentThread().getName();
  synchronized (str) {
   for(int i=0;i<5;i++){
    try {
     str.wait(300);//完全释放锁
     //Thread.sleep(300);//不释放锁
     str.append('a');
     System.out.println(name+str);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }  
  }
 }
}


//唤醒wait状态
class ThreadMy02 extends Thread{
 public void run(){
  synchronized (ThreadMy01.str) {
   for(int i=0;i<2;i++){
    try {
     Thread.sleep(2000);
     System.out.println("888");
    } catch (Exception e) {
     e.printStackTrace();
    }
   }
   //唤醒wait()状态
   ThreadMy01.str.notify();
  }
 }
}
运行结果:
    888
888
Thread-0a
Thread-0aa
Thread-0aaa
Thread-0aaaa
Thread-0aaaaa
结果分析:在运行以上代码时sleep虽然睡眠2秒,但是wait并没有 执行,说明sleep不会让出系统资源。线程的生命周期: db6d3768ca1a53930e5eaa6aa6f0691c.webp <END>

推荐阅读:

所有程序员都应该知道的 6 个软件开发步骤

无网络环境,如何部署Docker镜像?

    
        互联网初中高级大厂面试题(9个G)
      
    

内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper......等技术栈!

戳阅读原文领取!                                   朕已阅  d0981ea077912828ec2d25a4d590cecc.webp

浏览 41
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报