Java并发(一)线程安全

1. 什么是线程安全和非安全

什么是线程安全和非安全
如上图所示,所谓线程安全就是指在多个线程同时访问一个公共对象,不会因为多个线程并发读写,造成数据错误的情况。

比如:同时启动100个线程,对一个list进行add 100个数据操作,对于非安全对象list在执行过程中,会有并发写的情况,造成数据丢失。

public class Test {
    public static void main(String [] args){
        // 用来测试的List  
        List<String> data = new ArrayList<>();
        // 用来让主线程等待100个子线程执行完毕  
        CountDownLatch countDownLatch = new CountDownLatch(100);
        // 启动100个子线程  
        for(int i=0;i<100;i++){
            SampleTask task = new SampleTask(data,countDownLatch);
            Thread thread = new Thread(task);
            thread.start();
        }
        try{
            // 主线程等待所有子线程执行完成,再向下执行  
            countDownLatch.await();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        // List的size  
        System.out.println(data.size());
    }
}
class SampleTask implements Runnable {
    CountDownLatch countDownLatch;
    List<String> data;
    public SampleTask(List<String> data,CountDownLatch countDownLatch){
        this.data = data;
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        // 每个线程向List中添加100个元素  
        for(int i = 0; i < 100; i++)
        {
            data.add("1");
        }
        // 完成一个子线程  
        countDownLatch.countDown();
    }
}
7次测试输出:
9998
10000
10000
ArrayIndexOutOfBoundsException
10000
9967
9936

2. 哪些对象是线程安全

#线程安全线程非安全
ListVector,Stack,CopyOnWriteArrayList,SynchronizedList(类似Vector)ArrayList
MapHashTable(摒弃),ConcurrentHashMap,SynchronizedMapHashMap,TreeMap
SetSynchronizedSetHashSet,TreeSet
QueueBlockingQueue,ConcurrentLinkedQueue
StringStringBufferStringBuilder

3. 如何灵活使用线程安全和非安全对象

Vector性能比ArrayList低,因此在单线程或者多线程内部使用时,尽量用ArrayList,Vector主要用于多线程操作共享变量,当然,对ArrayList增加锁关键字synchronized,也可以自己实现线程安全。

4. 线程安全的遍历

无论是线程安全还是非安全,在并发操作时,进行遍历操作,都会出现ConcurrentModificationException异常。

public static void main(String[] args) {

    // 初始化一个list,放入5个元素
    final List<Integer> list = new ArrayList<>();
    for(int i = 0; i < 5; i++) {
        list.add(i);
    }

    // 线程一:通过Iterator遍历List
    new Thread(new Runnable() {
        @Override
        public void run() {
            for(int item : list) {
                System.out.println("遍历元素:" + item);
                // 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();

    // 线程二:remove一个元素
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            list.remove(4);
            System.out.println("list.remove(4)");
        }
    }).start();
}
运行结果: 
遍历元素:0 
遍历元素:1 
list.remove(4) 
Exception in thread “Thread-0” java.util.ConcurrentModificationException

4.1 解决方法1-锁操作

如何解决并发遍历的情况,在遍历过程中,对对象进行锁操作。

synchronized (list) {
    for(int item : list) {
        System.out.println("遍历元素:" + item);
        // 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4.2 解决方法2-CopyOnWriteArrayList

CopyOnWriteArrayList是java.util.concurrent包中的一个List的实现类

使用CopyOnWriteArrayList可以线程安全地遍历,因为如果另外一个线程在遍历的时候修改List的话,实际上会拷贝出一个新的List上修改,而不影响当前正在被遍历的List。

public static void main(String[] args) {

    // 初始化一个list,放入5个元素
    final List<Integer> list = new CopyOnWriteArrayList<>();
    for(int i = 0; i < 5; i++) {
        list.add(i);
    }

    // 线程一:通过Iterator遍历List
    new Thread(new Runnable() {
        @Override
        public void run() {
            for(int item : list) {
                System.out.println("遍历元素:" + item);
                // 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();

    // 线程二:remove一个元素
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            list.remove(4);
            System.out.println("list.remove(4)");
        }
    }).start();
}
运行结果:

遍历元素:0 
遍历元素:1 
list.remove(4) 
遍历元素:2 
遍历元素:3 
遍历元素:4

从上面的运行结果可以看出,虽然list.remove(4)已经移除了一个元素,但是遍历的结果还是存在这个元素。由此可以看出被遍历的和remove的是两个不同的List。

4.3 解决方法3-Java8的List.forEach

List.forEach方法是Java 8新增的一个方法,主要目的还是用于让List来支持Java 8的新特性:Lambda表达式。

由于forEach方法是List的一个方法,所以不同于在List外遍历List,forEach方法相当于List自身遍历的方法,所以它可以自由控制是否线程安全。

我们看线程安全的Vector的forEach方法源码:

public synchronized void forEach(Consumer<? super E> action) {
    ...
}

可以看到Vector的forEach方法上加了synchronized来控制线程安全的遍历,也就是Vector的forEach方法可以线程安全地遍历。

public static void main(String[] args) {

    // 初始化一个list,放入5个元素
    final List<Integer> list = new Vector<>();
    for(int i = 0; i < 5; i++) {
        list.add(i);
    }

    // 线程一:通过Iterator遍历List
    new Thread(new Runnable() {
        @Override
        public void run() {
            list.forEach(item -> {
                System.out.println("遍历元素:" + item);
                // 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }).start();

    // 线程二:remove一个元素
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            list.remove(4);
            System.out.println("list.remove(4)");
        }
    }).start();
}
运行结果: 

遍历元素:0 
遍历元素:1 
遍历元素:2 
遍历元素:3 
遍历元素:4 
list.remove(4)

这个和方法1采用锁操作得到的结果是一样的,都是先锁住对象,遍历完成再进行其他操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值