java并发相关

java并发相关

对java并发的误解

  • 并发总能改进性能(真相:并发在CPU有很多空闲时间时能明显改进程序的性能,但当线程数量较多的时候,线程间频繁的调度切换反而会让系统的性能下降)
  • 编写并发程序无需修改原有的设计。(真相:目的与时机的解耦往往会对系统结构产生巨大的影响)
  • 在使用Web或EJB容器时不用关注并发问题。(真相:只有了解了容器在做什么,才能更好的使用容器)

正解

  • 编写并发程序会在代码上增加额外的开销
  • 正确的并发是非常复杂的,即使对于很简单的问题
  • 并发中的缺陷因为不易重现也不容易被发现
  • 并发往往需要对设计策略从根本上进行修改

并发编程的原则和技巧

  • 单一职责原则:分离并发相关代码和其他代码(并发相关代码有自己的开发、修改和调优生命周期)。
  • 限制数据作用域:两个线程修改共享对象的同一字段时可能会相互干扰,导致不可预期的行为,解决方案之一是构造临界区,但是必须限制临界区的数量。
  • 使用数据副本:数据副本是避免共享数据的好方法,复制出来的对象只是以只读的方式对待。Java 5的java.util.concurrent包中增加一个名为CopyOnWriteArrayList的类,它是List接口的子类型,所以你可以认为它是ArrayList的线程安全的版本,它使用了写时复制的方式创建数据副本进行操作来避免对共享数据并发访问而引发的问题。
  • 线程应尽可能独立:让线程存在于自己的世界中,不与其他线程共享数据。有过Java Web开发经验的人都知道,Servlet就是以单实例多线程的方式工作,和每个请求相关的数据都是通过Servlet子类的service方法(或者是doGet或doPost方法)的参数传入的。只要Servlet中的代码只使用局部变量,Servlet就不会导致同步问题。Spring MVC的控制器也是这么做的,从请求中获得的对象都是以方法的参数传入而不是作为类的成员,很明显Struts 2的做法就正好相反,因此Struts 2中作为控制器的Action类都是每个请求对应一个实例。

java 5之前的并发编程

  • java的线程模型建立在抢占式线程调度的基础上,也就意味着
    • 所有线程可以很容易的共享同一进程中的对象。
    • 能够引用这些对象的任何线程都可以修改这些对象。
    • 为了保护数据,对象可以被锁住。

java 5的并发编程

  • Doug Lea在java 5中提供了他的杰作java.util.concurrent包

    • 更好的线程安全的容器
    • 线程池和相关的工具类
    • 可选的非阻塞解决方案
    • 显示的锁和信号量机制
  • 原子类

    • Java 5中的java.util.concurrent包下面有一个atomic子包,其中有几个以Atomic打头的类,例如AtomicInteger和AtomicLong。它们利用了现代处理器的特性,可以用非阻塞的方式完成原子操作
  • 显示锁

    • 基于synchronized关键字的锁机制有以下问题
      • 锁只有一种类型,而且对所有同步操作都是一样的作用
      • 锁只能在代码块或方法开始的地方获得,在结束的地方释放
      • 线程要么得到锁,要么阻塞,没有其他的可能性
    • java 5对锁进行了重构
      • 可以添加不同类型的锁,例如读取锁和写入锁。
      • 可以在一个方法中加锁,在另一个方法中解锁。
      • 可以使用tryLock方式尝试获得锁,如果得不到锁可以等待、回退或者干点别的事情,当然也可以在超时之后放弃操作。
  • CountDownLatch

    • CountDownLatch是一种简单的同步模式,它让一个线程可以等待一个或多个线程完成它们的工作从而避免对临界资源并发访问所引发的各种问题。
  • ConcurrentHashMap

    • ConcurrentHashMap是HashMap在并发环境下的版本,大家可能要问,既然已经可以通过Collections.synchronizedMap获得线程安全的映射型容器,为什么还需要ConcurrentHashMap呢?因为通过Collections工具类获得的线程安全的HashMap会在读写数据时对整个容器对象上锁,这样其他使用该容器的线程无论如何也无法再获得该对象的锁,也就意味着要一直等待前一个获得锁的线程离开同步代码块之后才有机会执行。实际上,HashMap是通过哈希函数来确定存放键值对的桶(桶是为了解决哈希冲突而引入的),修改HashMap时并不需要将整个容器锁住,只需要锁住即将修改的“桶”就可以了。
    • ConcurrentHashMap还提供了原子操作的方法
      • putIfAbsent:如果还没有对应的键值对映射,就将其添加到HashMap中
      • remove:如果键存在而且值与当前状态相等,则用原子方式移除该键值对
      • replace:替换掉映射中元素的原子操作
  • CopyOnWriteArrayList

    • CopyOnWriteArrayList是ArrayList在并发环境下的替代品。CopyOnWriteArrayList通过增加写时复制语义来避免并发访问引起的问题,也就是说任何修改操作都会在底层创建一个列表的副本,也就意味着之前已有的迭代器不会碰到意料之外的修改。这种方式对于不要严格读写同步的场景非常有用,因为它提供了更好的性能。
    • 可以使用下面代码实验CopyOnWriteArrayList
        import java.util.ArrayList;
        import java.util.List;
        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        
        class AddThread implements Runnable {
            private List<Double> list;
            
            public AddThread(List<Double> list) {
                this.list = list;
            }
            
            @Override
            public void run() {
                for(int i = 0; i < 10000; ++i) {
                    list.add(Math.random());
                }
            }
        }
        
        public class Test05 {
            private static final int THREAD_POOL_SIZE = 2;
        
            public static void main(String[] args) {
                List<Double> list = new ArrayList<>();
                ExecutorService es = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
                es.execute(new AddThread(list));
                es.execute(new AddThread(list));
                es.shutdown();
            }
        }
    
        List<Double> list = new CopyOnWriteArrayList<>();
    
  • Queue

    • 队列是一个无处不在的美妙概念,它提供了一种简单又可靠的方式将资源分发给处理单元(也可以说是将工作单元分配给待处理的资源,这取决于你看待问题的方式)。实现中的并发编程模型很多都依赖队列来实现,因为它可以在线程之间传递工作单元。
    • Java 5中的BlockingQueue就是一个在并发环境下非常好用的工具,在调用put方法向队列中插入元素时,如果队列已满,它会让插入元素的线程等待队列腾出空间;在调用take方法从队列中取元素时,如果队列为空,取出元素的线程就会阻塞。
  • Callable接口、Future接口、FutureTask接口

    • get():获取结果。如果结果还没有准备好,get方法会阻塞直到取得结果;当然也可以通过参数设置阻塞超时时间
    • cancel():在运算结束前取消
    • isDone():可以用来判断运算是否结束

java 7的并发编程

  • TransferQueue

    • 它比BlockingQueue多了一个叫transfer的方法,如果接收线程处于等待状态,该操作可以马上将任务交给它,否则就会阻塞直至取走该任务的线程出现。可以用TransferQueue代替BlockingQueue,因为它可以获得更好的性能。
  • fork/join(分支/合并)框架

    • 它可以实现线程池中任务的自动调度,并且这种调度对用户来说是透明的。为了达到这种效果,必须按照用户指定的方式对任务进行分解,然后再将分解出的小型任务的执行结果合并成原来任务的执行结果。这显然是运用了分治法(divide-and-conquer)的思想。
  • java 7之后,默认的数据排序算法不再是快速排序(双枢轴快速排序),新的排序算法叫TimSort,它是归并排序和插入排序的混合体,TimSort可以通过分支合并框架充分利用现代处理器的多核特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值