(5)Java多线程之ArrayList和SimpleDateFormat

1.引言

      在前两篇博客中主要记录了多线程编程中存在的线程安全问题,我们可以通过加锁的方式实现线程同步。在JDK中给我们提供了多种API,这些API 有一部分是非线程安全的。比如:ArrayListSimpleDateFormat,在本篇博客中就主要介绍一下这两种API存在的线程安全问题,以及应该如何去解决这种线程安全问题。

2.ArrayList线程安全问题

2.1 ArrayList存在的线程安全问题

  • 首先我们先看一个代码,(从线程中给数组arr添加一个值10
import java.util.List;

public class MyThread extends Thread {
    private List arr;

    public MyThread(List arr) {
        this.arr = arr;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(500);
            arr.add("10");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
  • 我们启动十个线程
import java.util.ArrayList;
import java.util.List;


public class app {
    public static void main(String[] args) throws InterruptedException {
        List arr=new ArrayList();
        for(int i=0;i<10;i++)
        {
            MyThread td=new MyThread(arr);
            td.start();
        }
        Thread.sleep(5000);
        System.out.println(arr.size());
    }

}
  • 运行结果(出现问题)

这里写图片描述

      将代码运行之后,和我们预期的不太一样,我们认为,arr数组的size为10,并且arr的前十个元素是10,但是呢?arr数组的前两个数值却为null,这个bug的产生的原因便是:ArrarList这个集合类是线程不安全的,add方法并没有添加synchronized关键字,按照我们上一篇博客中说的,如果想要add数组线程安全,给add方法添加一个synchronized关键字即可,但是我们拿不到ArrarList源码,这就导致了我们不可以直接去更改add的原方法。
      如何去解决这个问题呢?接下来我这里提供两种方法解决线程安全问题。

2.2 通过内部类解决线程安全问题(这里以add方法为例)

  • 首先我们新建一个类叫做,MyCollection(用于装饰ArrayList)
import java.util.List;
class MyCollection{
    public static  List synList(List list){

        return new MyList(list);
    }

    private static class MyList implements List{
        private List list;
        MyList(List list){  
            this.list = list;   
        }

        public synchronized boolean add(Object obj){
                return list.add(obj);
        }
        // .....还有一些List需要实现的方法
    }
}
  • 然后线程类不修改,我们的main函数变成了这样
import java.util.ArrayList;
import java.util.List;


public class app {
    public static void main(String[] args) throws InterruptedException {
        List arr=MyCollection.synList(new ArrayList());

        for(int i=0;i<10;i++)
        {
            MyThread td=new MyThread(arr);
            td.start();
        }
        Thread.sleep(5000);
        System.out.println(arr.size());
    }

}
  • 运行结果

这里写图片描述

其实这里的方法也叫做装饰设计模式,不继承arrarylist类,但是我修改了ArrayList里面的方法原有的逻辑(即:将线程不安全的add方法修改为了线程安全的)

2.3 通过JDK提供我们的api将ArrayList修改为线程安全的

  • 我们刚刚新建了一个类叫做MyCollection,其实JDK提供了一个类Collections,可以直接将ArrayList转换成线程安全的
List arr=Collections.synchronizedList(new ArrayList());

      原理和我们上面写的类似,将ArrayList变成了线程安全的。

3.SimpleDateFormat存在的线程安全问题

      SimpleDateFormatArrayList一样,同样是线程非安全的,但是不同的是SimpleDateFormat是一个工具类,ArrayList是一个集合类,集合往往存储的是数据,工具类主要是用来处理数据,那么对于工具类,处理线程安全问题有着更加简单的方法。

3.1 SimpleDateFormat线程非安全

首先我们看一下SimpleDateFormat这个类是如何出现线程非安全的。

  • 创建我们的线程类
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class MyThread extends Thread {
    private SimpleDateFormat sdf;
    private String datestring;

    public MyThread(SimpleDateFormat sdf,String datestring) {
        this.sdf=sdf;
        this.datestring=datestring;
    }

    @Override
    public void run() {
        try {
            //将传进来的字符串转换成date对象
            Date date=sdf.parse(datestring);
            //将传进来的date对象重新转换成String
            String result=sdf.format(date).toString();
            //按理说,这里应该是一样的字符串
            System.out.println(datestring+":"+result);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
  • main函数中的代码
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;


public class app {
    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        String [] s=new String[]{
                "1999-10-11",
                "1999-10-12",
                "1999-10-13",
                "1999-10-14",
                "1999-10-15"
        };

        for(int i=0;i<5;i++)
        {

            MyThread td=new MyThread(sdf,s[i]);
            td.start();
        }
        Thread.sleep(5000);
    }

}
  • 运行结果(注意 我们的代码是没错误的)

这里写图片描述

这种问题的产生也是线程的非安全性产生的,如果我们想和ArrayList一样,就需要去改造parse方法,但是注意。这里是工具类,又不是数据,我们有着更加简单的方法解决这种线程问题。

3.2 使用多个SimpleDateFormat对象

  • 我们将main函数中改成这样
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;


public class app {
    public static void main(String[] args) throws InterruptedException {

        String [] s=new String[]{
                "1999-10-11",
                "1999-10-12",
                "1999-10-13",
                "1999-10-14",
                "1999-10-15"
        };

        for(int i=0;i<5;i++)
        {
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
            MyThread td=new MyThread(sdf,s[i]);
            td.start();
        }
        Thread.sleep(5000);
    }

}
  • 运行结果:没有问题了

这里写图片描述

4.总结

在此篇博客中主要介绍了:

  • 如果是工具类的线程非安全性问题,我们如何解决?(当无法修改源码的时候)
  • 如果是数据集合类的线程非安全性问题,我们应该如何去解决?(当无法修改源码的时候)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值