java并发(1)

hashmap效率高单线程不安全,hashTable效率低但线程安全html

由于hashTable使用synchronized来保证线程安全,因此效率十分低,好比线程1使用put插入数据时,线程2既不能使用put插入,也不能使用get获取。java

锁分段技术

HashTable容器在竞争激烈的并发环境下表现出效率低下的缘由,是由于全部访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不一样数据段的数据时,线程间就不会存在锁竞争,从而能够有效的提升并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分红一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其余段的数据也能被其余线程访问。有些方法须要跨段,好比size()和containsValue(),它们可能须要锁定整个表而而不只仅是某个段,这须要按顺序锁定全部段,操做完毕后,又按顺序释放全部段的锁。这里“按顺序”是很重要的,不然极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,而且其成员变量实际上也是final的,可是,仅仅是将数组声明为final的并不保证数组成员也是final的,这须要实现上的保证。这能够确保不会出现死锁,由于得到锁的顺序是固定的。算法

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素, 每一个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。数据库

concurrentHashMap转自https://www.cnblogs.com/ITtangtang/p/3948786.html编程

并发编程的缺点

时间片是CPU分配给各个线程的时间,由于时间很是短,因此须要CPU不断地切换线程,让咱们以为多个线程是同时运行的。并且每次切换时都须要把当前的状态保存起来,以便下次切换过来时能够恢复到先前执行时的状态,而这个切换很是损耗性能,过于频繁反而没法发挥出多线程的优点。一般减小上下文切换能够采用无锁并发编程,CAS算法,使用最少的线程和使用协程。数组

  • 无锁并发编程:能够参照concurrentHashMap锁分段的思想,不一样的线程处理不一样段的数据,这样在多线程竞争的条件下,能够减小上下文切换的时间。安全

  • CAS算法,利用Atomic下使用CAS算法来更新数据,使用了乐观锁,能够有效的减小一部分没必要要的锁竞争带来的上下文切换多线程

  • 使用最少线程:避免建立不须要的线程,好比任务不多,可是建立了不少的线程,这样会形成大量的线程都处于等待状态并发

  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换ide

因为上下文切换也是个相对比较耗时的操做,因此在"java并发编程的艺术"一书中有过一个实验,并发累加未必会比串行累加速度要快。 可使用Lmbench3测量上下文切换的时长 vmstat测量上下文切换次数

线程安全

多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的状况,一旦产生死锁就会形成系统功能不可用。

public class DeadLockDemo {
    private static String resource_a = "A";
    private static String resource_b = "B";

    public static void main(String[] args) {
        deadLock();
    }

    public static void deadLock() {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_a) {
                    System.out.println("get resource a");
                    try {
                        Thread.sleep(3000);
                        synchronized (resource_b) {
                            System.out.println("get resource b");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_b) {
                    System.out.println("get resource b");
                    synchronized (resource_a) {
                        System.out.println("get resource a");
                    }
                }
            }
        });
        threadA.start();
        threadB.start();

    }
}

在上面的这个demo中,开启了两个线程threadA, threadB,其中threadA占用了resource_a, 并等待被threadB释放的resource _b。threadB占用了resource _b正在等待被threadA释放的resource _a。所以threadA,threadB出现线程安全的问题,造成死锁。一样能够经过jps,jstack证实这种推论:

"Thread-1":
  waiting to lock monitor 0x000000000b695360 (object 0x00000007d5ff53a8, a java.lang.String),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000000000b697c10 (object 0x00000007d5ff53d8, a java.lang.String),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at learn.DeadLockDemo$2.run(DeadLockDemo.java:34)
        - waiting to lock <0x00000007d5ff53a8(a java.lang.String)
        - locked <0x00000007d5ff53d8(a java.lang.String)
        at java.lang.Thread.run(Thread.java:722)
"Thread-0":
        at learn.DeadLockDemo$1.run(DeadLockDemo.java:20)
        - waiting to lock <0x00000007d5ff53d8(a java.lang.String)
        - locked <0x00000007d5ff53a8(a java.lang.String)
        at java.lang.Thread.run(Thread.java:722)

Found 1 deadlock.

如上所述,彻底能够看出当前死锁的状况。

那么,一般能够用以下方式避免死锁的状况:

  1. 避免一个线程同时得到多个锁;
  2. 避免一个线程在锁内部占有多个资源,尽可能保证每一个锁只占用一个资源;
  3. 尝试使用定时锁,使用lock.tryLock(timeOut),当超时等待时当前线程不会阻塞;
  4. 对于数据库锁,加锁和解锁必须在一个数据库链接里,不然会出现解锁失败的状况

因此,如何正确的使用多线程编程技术有很大的学问,好比如何保证线程安全,如何正确理解因为JMM内存模型在原子性,有序性,可见性带来的问题,好比数据脏读,DCL等这些问题(在后续篇幅会讲述)。而在学习多线程编程技术的过程当中也会让你收获颇丰。

相关文章
相关标签/搜索