对象及变量的并发访问(案例加总结)--学习笔记


前言

什么是时候会出现线程不安全?

多个线程同时访问同一个对象中一个全局实例变量,会出现"非线程安全",不同环境不同结果,接下来带大家分析各种同步与异步的问题。


提示:以下是本篇文章正文内容,下面案例可供参考

一、synchronized的使用?

1.0.1 实际变量非线程安全及解决

代码如下(示例):

服务层

public class Service {
    private int num=0;
    public void addI(String username){
        try {
            if (username.equals("a")){
                num=100;
                System.out.println("a set over!");
                    Thread.sleep(2000);
            }else{
                num=200;
                System.out.println("b set over!");
            }
            System.out.println(username+" num="+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

两个线程

public class ThreadA extends  Thread{
    private Service numRef;
    public ThreadA(Service numRef){
        super();
        this.numRef=numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}
public class ThreadB extends Thread{
    private Service numRef;
    public ThreadB(Service numRef){
        super();
        this.numRef=numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}

测试类

public class Run {
    public static void main(String[] args) {
        Service service =new Service();
        ThreadA a=new ThreadA(service);
        a.start();
        ThreadB b=new ThreadB(service);
        b.start();
    }
}

运行结果如下:

出现了 “非线程安全” 覆盖值的情况
在这里插入图片描述
解决1:

改变服务类,上一把synchronized锁

public class Service {
    private int num=0;
   synchronized public void addI(String username){
        try {
            if (username.equals("a")){
                num=100;
                System.out.println("a set over!");
                    Thread.sleep(2000);
            }else{
                num=200;
                System.out.println("b set over!");
            }
            System.out.println(username+" num="+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

线程进入时用synchronized声明的方法时就上锁,方法执行完成后自动解锁,那么下一个线程才会进入synchronized声明的方法里,实现同步运行,线程安全。
在这里插入图片描述
解决2:

改变服务类,将全局实例变量变成私有的

public class Service {
   synchronized public void addI(String username){
    int num=0;
        try {
            if (username.equals("a")){
                num=100;
                System.out.println("a set over!");
                    Thread.sleep(2000);
            }else{
                num=200;
                System.out.println("b set over!");
            }
            System.out.println(username+" num="+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

两个线程成异步执行自己改变自己的变量,互不影响
在这里插入图片描述
解决3:

不改变服务类,多创建一个对象

public class Run {
    public static void main(String[] args) {
        Service service =new Service();
        Service service1=new Service();
        ThreadA a=new ThreadA(service);
        a.start();
        ThreadB b=new ThreadB(service1);
        b.start();
    }
}

运行结果如下:

多个线程访问多个对象的实例变量,线程与对象一对一的关系,实现线程安全。
运行结果

总结:多个线程对共享资源有写操作时,必须同步,非共享资源时,可以进行异步(保证线程安全)。
同步的单词为synchronized ,异步为asynchronized

1.0.2 当类中存在同步方法与非同步方法时(synchronized锁的是对象,而非方法)

代码如下(示例):

服务层

public class MyObject {
   synchronized public void methodA(){
        try {
            System.out.println("begin methodA threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("A end endTime="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  synchronized  public void methodB(){
        try {
            System.out.println("begin methodB threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("B end endTime="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

两个线程

public class ThreadA extends Thread{
    private MyObject object;
    public ThreadA(MyObject object){
        super();
        this.object=object;
    }
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}
public class ThreadB extends Thread{
    private MyObject object;
    public ThreadB(MyObject object){
        super();
        this.object=object;
    }
    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

运行类

public class Run {
    public static void main(String[] args) {
        MyObject object=new MyObject();
        ThreadA a=new ThreadA(object);
        a.setName("A");
        ThreadB b=new ThreadB(object);
        b.setName("B");
        a.start();
        b.start();
    }
}

运行结果如下:

synchronized锁的是对象,而非方法,所以当A线程执行,对象上锁,B线程等待,当A线程执行完毕,释放锁,B线程才能执行methodB方法,所以是同步执行。
在这里插入图片描述
当methodA带synchronized,而methodB不带synchronized的情况。

代码如下(示例):
只改变服务层,其他不变
服务层

public class MyObject {
   synchronized public void methodA(){
        try {
            System.out.println("begin methodA threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("A end endTime="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 public void methodB(){
        try {
            System.out.println("begin methodB threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("B end endTime="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

A线程持有了对象锁,但是B线程可以以异步的方式访问非synchronized类型的方法。在这里插入图片描述

1.0.3 synchronized锁重入

概念:自己可以再次获取自己内部的锁,支持继承的环境中。
代码如下(示例):

服务层

public class Service {
  synchronized public void service1(){
      try {
          System.out.println("service1");
          Thread.sleep(10000);
          service2();
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  }
  synchronized public void service2(){
      try {
          System.out.println("service2");
          Thread.sleep(10000);
          service3();
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  }
   synchronized public void service3(){
       try {
           System.out.println("service3");
           Thread.sleep(10000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
    synchronized public void service4(){
        System.out.println("service4");
    }
}

两个线程类

public class MyThread1 extends Thread{
    private  Service service;
    public MyThread1(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        super.run();
        service.service4();
    }
}
public class MyThread2 extends Thread{
    private  Service service;
    public MyThread2(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        super.run();
        service.service1();
    }
}

测试类

public class test1 {
    public static void main(String[] args) {
        Service service=new Service();
        MyThread2 myThread=new MyThread2(service);
        myThread.start();
        MyThread1 myThread1=new MyThread1(service);
        myThread1.start();
    }
}

运行结果如下:

在一个 synchronized方法/块的内部调用本类其他的synchronized方法/块时,是永远可以得到锁的。
在这里插入图片描述

1.0.4 用同步代码块解决同步方法的弊端(运行效率)

用synchronized声明的方法
代码如下(示例):

服务类

public class Taak {
    private String getData1;
    private String getData2;
   synchronized public  void doLongTimeTack(){
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            getData1="长时间处理任务从远程返回值1 threadName="+Thread.currentThread().getName();
            getData2="长时间处理任务从远程返回值2 threadName="+Thread.currentThread().getName();
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class CommonUtils {
    public static long beginTime1;
    public static long endTime1;
    public static long beginTime2;
    public static long endTime2;
}

两个线程类

public class MyThread1 extends Thread{
    private Taak taak;
    public MyThread1(Taak taak){
        this.taak=taak;
    }
    @Override
    public void run() {
        super.run();
        CommonUtils.beginTime1=System.currentTimeMillis();
        taak.doLongTimeTack();
        CommonUtils.endTime1=System.currentTimeMillis();
    }
}
public class MyThread2 extends Thread{
    private Taak taak;
    public MyThread2(Taak taak){
        this.taak=taak;
    }
    @Override
    public void run() {
        super.run();
        CommonUtils.beginTime2=System.currentTimeMillis();
        taak.doLongTimeTack();
        CommonUtils.endTime2=System.currentTimeMillis();
    }
}

测试类

public class test {
    public static void main(String[] args) {
        Taak taak=new Taak();
        MyThread1 myThread1=new MyThread1(taak);
        myThread1.start();
        MyThread2 myThread2=new MyThread2(taak);
        myThread2.start();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long beginTime=CommonUtils.beginTime1;
        if (CommonUtils.beginTime2<CommonUtils.beginTime1){
            beginTime=CommonUtils.beginTime2;
        }
        long endTime=CommonUtils.endTime1;
        if (CommonUtils.endTime2>CommonUtils.endTime1){
            endTime=CommonUtils.endTime2;
        }
        System.out.println("耗时:"+((endTime-beginTime)/1000));
    }
}

运行结果如下:

用synchronized修饰的方法,是同步执行,保证了线程安全,但是效率低下,解决此问题,运用synchronized代码块
在这里插入图片描述
解决:

改变服务类,使用同步代码块

public class Taak {
    private String getData1;
    private String getData2;
   public  void doLongTimeTack(){
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
         String  privateGetDate1="长时间处理任务从远程返回值1 threadName="+Thread.currentThread().getName();
            String  privateGetDate2="长时间处理任务从远程返回值2 threadName="+Thread.currentThread().getName();
            synchronized (this){
                getData1=privateGetDate1;
                getData2=privateGetDate2;
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

当一个线程访问object的一个 synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized同步代码块,在保证线程安全的同时,同步异步相结合,实现大幅度提高运行效率。

在这里插入图片描述

1.0.5 synchronized代码块的同步性

在使用同步synchronized(this)代码块时要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问被阻塞,说明synchronized(this)使用的是同一个“锁”。

代码如下:

服务类

public class ObjectService {
    public void  serviceMethodA(){
        try {
            synchronized (this){
                System.out.println("A begin time="+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("A end time="+System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void  serviceMethodB(){
        synchronized (this){
            System.out.println("B begin time="+System.currentTimeMillis());
            System.out.println("B end time="+System.currentTimeMillis());
        }
    }
}

两个线程类

public class ThreadA extends Thread{
    private ObjectService objectService;
    public ThreadA(ObjectService objectService){
        super();
        this.objectService=objectService;
    }
    @Override
    public void run() {
        super.run();
        objectService.serviceMethodA();
    }
}
public class ThreadB extends Thread{
    private ObjectService objectService;
    public ThreadB(ObjectService objectService){
        super();
        this.objectService=objectService;
    }
    @Override
    public void run() {
        super.run();
        objectService.serviceMethodB();
    }
}

测试类

public class run {
    public static void main(String[] args) {
        ObjectService objectService=new ObjectService();
        ThreadA threadA=new ThreadA(objectService);
        threadA.setName("a");
        threadA.start();
        ThreadB threadB=new ThreadB(objectService);
        threadA.setName("b");
        threadB.start();
    }
}

运行结果如下:

用synchronized同步代码块同步运行
在这里插入图片描述
当然如果将一个 synchronized(this)同步代码块换成用synchronized关键字修饰的方法,结果会变吗,答案是否定的,因为 synchronized(this)同步代码块将当前对象作为锁,使用synchronized关键字修饰的serviceMethodB()同步方法是将当前方法所在类的对象作为锁,都是一把锁,所以是同步运行,改变服务类。
代码如下:

服务类

public class ObjectService {
    public void  serviceMethodA(){
        try {
            synchronized (this){
                System.out.println("A begin time="+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("A end time="+System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 synchronized   public void  serviceMethodB(){
            System.out.println("B begin time="+System.currentTimeMillis());
            System.out.println("B end time="+System.currentTimeMillis());
    }
}

运行结果如下:
在这里插入图片描述

1.0.6 synchronized(非this对象)的使用

synchronized(非this对象 x)格式的写法是将x对象本身作为“对象监视器”

当不使用 synchronized(非this对象 x)时
代码如下(示例):

服务类

public class MyOneList {
    private List list=new ArrayList();
   synchronized public void  add(String data){
        list.add(data);
    };
   synchronized  public int getSize(){
       return list.size();
   }
}

public class MyService {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try {           
                if (list.getSize() < 1) {
                    Thread.sleep(2000);
                    list.add(data);
                }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return list;
    }
}

线程类

public class MyThread1 extends Thread{
    private MyOneList list;
    public MyThread1(MyOneList list){
        this.list=list;
    }
    @Override
    public void run() {
        super.run();
        MyService myService=new MyService();
        myService.addServiceMethod(list,"A");
    }
}
public class MyThread2 extends Thread{
    private MyOneList list;
    public MyThread2(MyOneList list){
        this.list=list;
    }
    @Override
    public void run() {
        super.run();
        MyService myService=new MyService();
        myService.addServiceMethod(list,"B");
    }
}

测试类

public class test1 {
    public static void main(String[] args) {
        Service service=new Service();
        MyThread2 myThread=new MyThread2(service);
        myThread.start();
        MyThread1 myThread1=new MyThread1(service);
        myThread1.start();
    }
}

测试结果为listsize=2,因为是异步运行,第一个线程进入时延时了两秒,此时还没有进行add操作,第二个线程在延时过程中进入了该方法,所以就进行了两次add操作。

当使用使用 synchronized(非this对象 x)时。
更改MyService类

public class MyService {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try {
             synchronized (list) {
                if (list.getSize() < 1) {
                    Thread.sleep(2000);
                    list.add(data);
                }
             }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return list;
    }
}

测试结果为listsize=1,因为synchronized(非this对象 x)是将x对象本身作为“对象监视器”,第一个线程进入时获得了锁,直到执行完add操作才释放锁,此时另一个线程进入该方法,if判断为false,实现了同步运行。

1.0.7 synchronized使用的3个结论

1.当多个线程同时执行synchronized(非this对象x){}同步代码块时呈同步效果
2.当其他线程执行x对象中synchronized同步方法时呈同步效果,
还可以将synchronized关键字应用在static静态方法上,就是对当前的*.java文件对应的class类的对象进行持锁。
当然在x对象中synchronized关键字应用在static和非static是异步执行的
在x对象中synchronized关键字应用在static静态方法和synchronized(x.class)是同步运行的,因为他们都是*.java文件对应的class类的对象进行持锁。
3.当其他线程执行x对象方法里面synchronized(this)同步代码块时也呈现同步效果
*注意:如果其他线程调用x对象中不加synchronized关键字的方法还是异步调用

二、多线程的死锁?

2.0.1 死锁

java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程中,“死锁”是必须避免的,因为这会造成线程的“假死”

代码如下(示例):

服务类:

public class DealThread implements Runnable{
    public String username;
    public Object lock1=new Object();
    public Object lock2=new Object();
    public void setFlag(String username){
        this.username=username;
    }
    @Override
    public void run() {
        if (username.equals("a")){
            synchronized (lock1){
                try {
                    System.out.println("username="+username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println("按lock1---lock2代码顺序执行了");
                }
            }
        }
        if (username.equals("b")){
            synchronized (lock2){
                try {
                    System.out.println("username="+username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println("按lock2---lock1代码顺序执行了");
                }
            }
        }
    }
}

测试类

public class run {
    public static void main(String[] args) {
        try {
            DealThread dealThread=new DealThread();
            dealThread.setFlag("a");
            Thread thread1=new Thread(dealThread);
            thread1.start();
            Thread.sleep(100);
            dealThread.setFlag("b");
            Thread thread2=new Thread(dealThread);
            thread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

当setFlag赋值为a是第一个线程进入时获得lock1锁,延时100毫秒,赋值为b,第二个线程进入获取了lock2锁,lock1等待lock2锁,lock2等待lock1锁,自己持有自己的锁,又在等在对方的锁,造成假死,死锁。

在这里插入图片描述

三、并发的三大性质

1.原子性:指一组操作在执行时不能被打断,要么全部执行成功,要么全部执行失败,不能进行分割和中断。
2.可见性:A线程更改的数据B线程立马能看到。
3.有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。

3.0.1 使用volatile解决多线程的死循环(volatile的可见性)

代码如下(示例):

服务类

public class RunThread extends Thread{
    private boolean isRunning=true;
    public boolean isRunning() {
        return isRunning;
    }
    public void setRunning(boolean running) {
        isRunning = running;
    }
    @Override
    public void run() {
        super.run();
        System.out.println("进入run了");
        while (isRunning == true){ }
        System.out.println("线程被停止了!");
    }
}
    }
}

测试类

public class run {
    public static void main(String[] args) {
        try {
            RunThread thread=new RunThread();
            thread.start();
            thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

为什么会造成死循环呢?因为在启动线程时,变量private boolean isRunning=true;分别存在于公共内存及线程的私有内存中,线程运行后一直在线程的私有变量中获取 isRunning,而代码 thread.setRunning(false);虽然被执行,跟新的却是公共内存中的isRuning变量变为false,操作的是2块内存地址的数据,所以一直是死循环状态
在这里插入图片描述

这个问题其实就是私有内存中的值和公共内存中的值不同步造成的,解决这样的问题就要使用volatile关键字了,他主要作用就是当前线程访问isRunning这个变量时,强制性从公共内存中进行取值

更改服务类
代码如下(示例):

服务类

public class RunThread extends Thread{
    volatile private boolean isRunning=true;
    public boolean isRunning() {
        return isRunning;
    }
    public void setRunning(boolean running) {
        isRunning = running;
    }
    @Override
    public void run() {
        super.run();
        System.out.println("进入run了");
        while (isRunning == true){ }
        System.out.println("线程被停止了!");
    }
}
    }
}

运行结果如下:

通过使用volatile关键字,强制从公共内存中读取变量的值,增加了实例变量多个线程之间的可见性。

在这里插入图片描述

3.0.2 volatile的非原子性

非原子性是因为i++操作不是原子性的,会拆成三个步骤。
关键字volatile不支持i++运算原子性
代码如下(示例):

线程类

public class MyThread extends Thread{
   volatile public static  int count;
   private static void addCount(){
        for (int i = 0; i <100 ; i++) {
            count++;
        }
        System.out.println("count=" +count+" ----线程名称"+Thread.currentThread().getName());
    }
    @Override
    public void run() {
        super.run();
        addCount();
    }
}

测试类

public class run {
    public static void main(String[] args) {
        MyThread[] t1=new MyThread[100];
        for (int i = 0; i < 100; i++) {
            t1[i]=new MyThread();
        }
        for (int i = 0; i <100 ; i++) {
            t1[i].start();
        }
    }
}

运行结果如下:

运算结果不是10000,说明多线程环境下volatile public static int count++运算操作是非原子性的。

在这里插入图片描述
但是synchronized具有原子性和可见性
改变线程类

线程类

public class MyThread extends Thread{
    public static  int count;
   synchronized private static void addCount(){
        for (int i = 0; i <100 ; i++) {
            count++;
        }
        System.out.println("count=" +count+" ----线程名称"+Thread.currentThread().getName());
    }
    @Override
    public void run() {
        super.run();
        addCount();
    }
}

运行结果如下:
在这里插入图片描述

3.0.3 使用Atomic原子类进行i++操作实现原子性

线程类

public class AddCountThread extends Thread{
    private AtomicInteger count=new AtomicInteger(0);
    @Override
    public void run() {
        super.run();
        for (int i = 0; i <10000 ; i++) {
            //原子上增加一个当前值
        System.out.println(count.incrementAndGet());
    }
}
}

测试类

public class Run {
    public static void main(String[] args) {
        AddCountThread countThread=new AddCountThread();
        Thread t1=new Thread(countThread);
        t1.start();
        Thread t2=new Thread(countThread);
        t2.start();
        Thread t3=new Thread(countThread);
        t3.start();
        Thread t4=new Thread(countThread);
        t4.start();
        Thread t5=new Thread(countThread);
        t5.start();
    }
}

运行结果如下:

原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(atomic)类型就是一个原子操作的可用的类型,他可以在没有锁的情况下做到线程安全
在这里插入图片描述

3.0.4 总结

可见性:synchronized和volatile具有可见性。
原子性:使用synchronized具有原子性,使用Atomic原子类具有原子性
synchronized和volayile都禁止代码重排序

volatile和synchronized的使用场景是
1.当想要实现1个变量的值被更改时,让其他线程能取到最新值时,就要对变量使用volatile
2.如果多个线程对同一个对象中的同一个实例变量进行操作时,为了避免出现非线程安全,就要使用synchronized

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值