1.体会条件

感受机型:魅族MX5

经验系统:Android 5.1 / Flyme OS 5.1.9.0A

app版本:壹心理 v4.1.1

体验日:2016.10.11

心得人数:搬砖的小哪吒

2.4. get

ConcurrentHashMap的get操作,源码如下:

public V get(Object key) {
    Segment<K,V> s; // manually integrate access methods to reduce overhead
    HashEntry<K,V>[] tab;
    int h = hash(key);  // 对key求hash
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;   // key对应的Segment的数组下标
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&  // 先找对应的Segment。volatile读语义保证内存的可见性
        (tab = s.table) != null) {
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);       // 再在Segment中找对应的HashEntry
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

get操作不待加锁,通过volatile保证可见性,如果又出put并发操作多HashEntry,由于是当链表头部添加(头插法),不会见指向get造成影响。

3.出品一定

“来访者心事的讲话,咨询师的成人阶梯”:壹心理聚集国内心理学人,为需要心理援助的口供有趣、有好、便捷、实用的在线解决方案的心理服务平台。

3.3. put

put源码如下:

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    // 求key的hash值,两次hash,使hash值能均匀分布
    int hash = spread(key.hashCode()); 
    int binCount = 0;
    // 迭代Node[] table数组
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 1) 如果table为空,则初始化,ConcurrentHashMap构造方法未初始化Node数组,而是在put中实现,属于懒汉式初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // 2) 如果table不为空,根据hash值计算得到key在table中的索引i,如果table[i]为null,使用CAS方式新建Node节点(table[i]为链表或红黑树的首节点)
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin,不进行加锁
        }
        // 3) 如果table[i]不为空,并且hash值为MOVED(-1),表明该链表正在进行transfer扩容操作,帮助扩容完成
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 4) 以上条件都不满足,也就是存在hash冲突。对链表或红黑树的头节点进行加锁操作(不再是segment,进一步减少了线程冲突)
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    // hash值>0,表示该节点是链表结构。只需向后遍历即可
                    if (fh >= 0) {      
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 如果在链表中找到值为key的节点,按onlyIfAbsent判断是否替换value
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            // 没有找到值为key的节点,直接新建Node并加入链表尾部
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }  
                    // 如果首节点为红黑树结构,putTreeValue存放key-value对
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 如果链表节点数>8,则将链表结构转换为红黑树结构
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // size加1,会检查当前size是否超过sizeCtl,如果是则触发transfer扩容操作
    addCount(1L, binCount);
    return null;
}

约莫流程也:

  • 使table为空,没有初始化,则优先经initTable()初始化
  • 如果table[i]呢null,没有hash冲突,则CAS新建节点到table[i]
  • 如若table在扩容中,则等待扩容结束还操作
  • 倘若有hash冲突,则以synchronized对Node首节点加锁来担保线程安全,此时发2种植办法:
    • 1)如果是链表形式,则根据key是否是来判定,如果key存在,则覆盖value;key不存在,则新建Node插入到链表尾端
    • 2)如果是吉黑树形式,就随红黑树的布局插入
  • 末了判链表长度是否 >8?如果盖,则将链表转换为吉利黑树结构
  • put成功,则透过addCount方法统计size,并检查是否需要扩容

下面看initTable的源码:

sizeCtl是控制ConcurrentHashMap的支配标示符,用来决定初始化和扩容操作的,其不同的义如下:

  • 负数:代表在拓展初始化和扩容操作
  • -1:正在初始化
  • -N:有 N – 1 只线程正在展开扩容操作
  • 正数或0:hash表还没吃初始化,这个数值表示初始化或生同样不行扩容的大大小小

    private final Node[] initTable() {

    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        // sizeCtl < 0表示其他线程正在初始化,当前线程挂起
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        // CAS 将 sizeCtl置为 -1,表示正在初始化
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];   // 初始化
                    table = tab = nt;
                    sc = n - (n >>> 2);    // 下次扩容的阀值,等于 0.75*n
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
    

    }

3.职能体验分析

4. 参考

http://www.cnblogs.com/study-everyday/p/6430462.html

http://blog.csdn.net/u010723709/article/details/48007881

http://blog.csdn.net/jianghuxiaojin/article/details/52006118

1.效能方面

问答模块:加入脱其次涂鸦确认,提示用户保存的弹窗提示,同时于个人核心建立“我之草稿”。

文章

1)加入有导航功用,可以是按钮设计或双击上方推荐栏来回到首页最原始状态;

2)优化文章的归类。不宜过多过密切,用户害怕思考。

FM: 1)增加离线下载功能;2)增加定时关闭功能。

另外,补充某些前文没有提到的盘算。可能与产品规划或公司资源有关,目前壹心理上“工具”属性较弱,我个人认为好参考工具类制品,国内目前举行的于好之是“心潮减压”,另外一缓健康医疗类产品“春雨医生”也投入了工具属性的效能——计步器。因此引出一起新职能:

减压:1)加入冥想引导声音,完成每次冥想记录下来并生成多少显现,可享用。

2)开发记录心率变化的意义,对于心率高的用户可推送减压相关文章要FM。

3.4. get

get的源码如下:

/**
 * Returns the value to which the specified key is mapped,
 * or {@code null} if this map contains no mapping for the key.
 *
 * <p>More formally, if this map contains a mapping from a key
 * {@code k} to a value {@code v} such that {@code key.equals(k)},
 * then this method returns {@code v}; otherwise it returns
 * {@code null}.  (There can be at most one such mapping.)
 *
 * @throws NullPointerException if the specified key is null
 */
public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 计算两次hash
    int h = spread(key.hashCode());    
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {        // 读取首节点Node元素
        // 如果首节点Node.key相等,返回value
        if ((eh = e.hash) == h) {  
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // 树的find()来查找
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 在链表中遍历查找
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
} 

(五)如果自己是PM

立即等同有的主要回顾前文的提议,按照效益、交互、视觉三面进行汇总:

1. 前言

HashMap是非线程安全之,在多线程访问时从没一起机制,并发场景下put操作可能致同数组下的链表形成闭环,get时候出现死循环,导致CPU利用率接近100%。

HashTable是线程安全之,使用synchronized锁住满table的法门来保证并发访问下的线程安全,但效率也比较低下。因为线程1调为此HashTable的put同步方法时,线程2底put或get等办法则进入阻塞状态,所以竞争更为狂,效率进一步小。

ConcurrentHashMap是支撑大起、高吞吐量的线程安全之Map实现。下面会由此看 ConcurrentHashMap
在 JDK1.7 和 JDK1.8 的源码来询问其的演化过程。

(2)用户特征

图片 1

所在分布(来自:百度指数)

图片 2

年纪、性别分布(来自:百度指数)

于百度指数面临可以清楚的顾,关注壹心理的人群重点汇集在北上广平线城市,用户年龄20-29春秋数量过50%,以男性为主。

而且以于官方透漏产品数量明白,用户主要汇集在18-28岁,从大学生顶工作5年左右的白领。

双方数据基本吻合,因此可获取壹心理的用户画像:年龄阶段在18-28年份,从大学生及工作5年左右底一线城市白领人群,以男性为主。

用户特征:相对年轻、压力很、迷茫、急需改变现状

3. ConcurrentHashMap在JDK1.8蒙受之改善

3.视觉方面

咨询师介绍页

1)擅长内容做精简至1-2独领域,最多未可知超过3单,将略长情标签化并召开对比色处理还是将背景做高斯模糊处理;

2)“预约”按钮颜色改呢刺激人欲望的革命;

测试页:运用更加活跃大胆之页面布局及配色。至于具体使用哪种,鉴于时间和力量问题暂时无法为有合理之提议。

总的看,中国心理咨询市场潜力巨大,政策规范也也行业的生发展提供有益的泥土,国民羞于表达的包含特征,咨询供需双方信息的莫对称,这样的行业非常适合互联网的参与,做成平台型产品。同时,相比多竞品,壹心理在心理健康行业切入时间比较早,行业的聚积是彼重要优势,只要提高路径是,未来发展前景看好。

当下是自我首发以简书上的率先篇产品体验报告,鉴于时间以及涉的贫乏,同时针对需要的优先级、目标用户之把握和任何业务流程的知情尚浅,因此若一旦本文出现不当之处,还向各位大神不(hu)吝(xiang)赐(shang)教(hai)。相应地,未经我同意,不得随意转载到任何渠道(万一真的让转载了吧,岂不是将协调的短暴露于周边百姓大众眼前。。。害羞脸.gif)


有关作者,搬砖的有些呀吒,16应届毕业生,爱理性分析的伪文艺青年一样朵,近年来迷上知乎,玩过新媒体运营,立志在互联网就长长的路上走及黑,求活、运营坑ing…有招人的也(没有底讲话我得会儿再来问一样一体…)

2.5. size

计算ConcurrentHashMap的 size 是一个妙不可言之问题,因为当算的时光,还见面在起的插数据,可能引致计算产生的size和骨子里的size有出入。

JDK1.7遭受先后以了一定量独方案:

率先种方案:先采用无加锁之模式先品尝遍历两不好ConcurrentHashMap计算size,如果简单不良遍历过程被有segment中之modCount的同凡同样的,则足以认为凡事计算过程中的Map没有发生变化(添加或删除HashEntry节点),返回size。

仲种植方案:如果第一种方案免抱(Map发生了组织转变),就被每个Segment加锁,然后计算ConcurrentHashMap的size,解锁,最后回来。

public int size() {
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K,V>[] segments = this.segments;   
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum;         // sum of modCounts
    long last = 0L;   // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            // 如果for循环达到RETRIES_BEFORE_LOCK,则表示前民几次累计的modCount都不相等,其他线程并发修改ConcurrentHashMap导致数据结构一直在改变。
            // 降级为依次对Segment进行加锁,此时其他线程改变数据结构就会阻塞等待
            if (retries++ == RETRIES_BEFORE_LOCK) {    
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {      // 循环每个Segment,累加Segment中的count和modCount
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            if (sum == last)    // 如果当前sum和之前计算的sum相等,即各Segment累加的modCount相等。就可以认为两次for循环间没有其他线程修改内部数据结构。直接返回size
                break;
            last = sum;     // last等于最近一次计算的sum值
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) { // 如果加锁了,最终解锁
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

(3)关于“壹心理学院”功能模块

乘在线教育的起来,壹心理也顺势推出在线课程功能——壹心理学院。相对于咨询服务之高价,在线课程为必然水准及满足了发轻心理问题还囊中羞涩之用户之要求,加上网络课程的灵敏和自主性,是由此线及铸就市场,实现逐步商业表现的一个特别适用的不二法门。课程按照用户热门关注之圈子分成收费及免费两种,根据官方数据称,壹心理学院推出的率先只春秋单课《从0到1科目:如何变成平等叫作心理咨询师》,此系列课上丝预售三独月内,已出售出2800大抵单订单,收入280基本上万。

图片 3

听课需求所设更之5个步骤

以及国内在线教育产品“网易公开课”的操作步骤相同,从进来及找寻再至结尾之听课只发5独流程,已经属于最为简单的水准。

以视觉设计达到,利用明显的色块和毛玻璃效果。课程分类清晰,整个体验十分好。

只要说只要以鸡蛋里挑骨头的语,我想单独会于壹心理学院首页的科目分类模块做点小改变。问题详情以及改进建议参考下图:

图片 4

课分类问题以及改善建议

3.1. 改进

改进1:取消segment分段锁,直接动用 transient volatile
Node<K,V>[] table;
来保存数组。采用table数组元素作为锁,从而实现对各级一行数开展加锁,进一步缩减了冲突的概率。

改善2:将原先table数组 + 链表的数据结构,变更为table数组 + 链表 / 红黑树
的结构,同HashMap在JDK1.8底数据结构的改善。优化为红黑树的裨益是:当一个链表长度过长时,查询有节点的年月复杂度为O(N),而当链表长度逾8时,将链表转化为吉利黑树,查询节点的时空复杂度可以降为O(logN),从而升级了性。

精益求精3:并发控制以synchronized和CAS,使用synchronized替换ReetrantLock。在ConcurrentHashMap中可以望众多底 U.compareAndSwapXXX,通过CAS算法实现无锁化修改值的操作,降低了锁之习性消耗。CAS的构思是连的较时外存中的变量值和所指定的变量值是否等,如果当,则接受指定的改动值;否则,拒绝操作,类似与开展锁。

数据结构如下:

图片 5

2)视觉&交互对比

完全来拘禁,壹心理的制品视觉设计算不达标理想,只能说中规中矩。结合竞品“我心”的视觉设计,下面就对“心理测试”功能做一个简练的可比

图片 6

竞品视觉&交互对比

壹心理:测试页显示只发一个,由臻及下独家测试分类模块、推荐模块。分类形式视觉呈现采取流行的”icon+文字”表达,测试推荐列表则动用左图右文的形式。

自己心头:2只页面,首屏是擅自推荐,后面是一定给壹心理的测试页。与壹心理不同的是,测试页上多滚动banner,下方推荐列表则应用相反的“左和右图”方案。

视觉:我心里之引荐页走大色片+图文的简要小清新作风,下方的若隐若现的微图有鼓舞用户点击的私欲。

交互:选择测试出少栽交互方式,一凡是反正滑动大图推荐,二凡以江湖小图区域附近点击,离点击最近的略微图就弹起,这时上方之大图推荐紧随着滑动到相应之测试,非常幽默。当然,滑动大图推荐,下方的微图为会见进行滑动,规则也先行点击先滑动完毕。

是因为心理测试自己有所非常强的玩乐与散播性,利用视觉手法和交互来营造轻松诙谐氛围,不失为一栽好产品传播之不过借鉴途径。

3.5. size

每当JDK1.8本被,对于size的测算,在扩容和addCount()时就于处理了。JDK1.7凡当调用时才去算。

以帮统计size,ConcurrentHashMap提供了baseCount和counterCells两个帮扶变量和CounterCell辅助类:

@sun.misc.Contended static final class CounterCell {
    volatile long value;
    CounterCell(long x) { value = x; }
}

//ConcurrentHashMap中元素个数,但返回的不一定是当前Map的真实元素个数。基于CAS无锁更新
private transient volatile long baseCount;

private transient volatile CounterCell[] counterCells;  // 部分元素变化的个数保存在此数组中

通过合baseCount和
counterCells数组中的数码,即可得到元素的到底个数。size源码如下:

public int size() {
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
            (int)n);
}

final long sumCount() {
    CounterCell[] as = counterCells; CounterCell a;
    long sum = baseCount;
    if (as != null) {
        // 遍历,累加所有counterCells.value
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

(2)“咨询”功能

图片 7

“咨询”功能用户以流程

2.2. 结构

ConcurrentHashMap:下是ConcurrentHashMap中之数成员及构造函数源码:

构造函数主要做了有限件事:1)参数的校验;2)table初始化长度

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> 
        implements ConcurrentMap<K, V>, Serializable { 
    /**
     * Mask value for indexing into segments. The upper bits of a
     * key's hash code are used to choose the segment.
     */
    final int segmentMask;

    /**
     * Shift value for indexing within segments.
     */
    final int segmentShift;

    /**
     * The segments, each of which is a specialized hash table.
     */
    final Segment<K,V>[] segments;

    // 构造函数
    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        // 找到第一个 >= concurrencyLevel 的 2次方数,作为后续 Segment数组大小
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; // 上文中通过比较concurrencyLevel而计算出的ssize作为数组大小
        // 使用的是StoreStore内存屏障,而不是较慢的StoreLoad内存屏障(使用在volatile写操作上)。实现写入不会被JIT重新排序指令,性能虽然提升,但写结果不会被立即看到。
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] 
        this.segments = ss;
    }
}

具有成员还是final修饰的,保证线程的安发布。segmentMask 和 segmentShift
主要是为着稳定段。

concurrencyLevel 参数表示要并发的改动 ConcurrentHashMap
的线程数量,用于决定 Segment 的数据,具体方法是经过找到第一只
>= concurrentcyLevel 的 2的次方数作为 Segment
数组的大小。默认值为16,Segment数组大小也为16,如果安 concurrentcyLevel
= 100,那么 Segment 数组大小则为128。

Segment:每个Segment相当给ConcurrentHashMap的一个子
hash表,Segment继承了ReetrantLock,为了方便使用加锁之职能,如lock,tryLock等。数据成员如下:

static final class Segment<K,V> extends ReentrantLock implements Serializable {   
        transient volatile HashEntry<K,V>[] table;
        transient int count;
        transient int modCount;
        transient int threshold;
        final float loadFactor;
 }

table:链表数组,每个数组元素是一个hash链表的脑袋。table是volatile的,使得每次都能够读博到新型的table值而无需要一起。

count:Segment中元素的数。每次修改操作做了组织及之改变,如添加/删除节点(更新节点的value不算结构及的变动),都使创新count值。

modCount:对table的构造进行改动的次数。

threshold:若Segment里的因素数量超过此价,则就见面针对Segment进行扩容。

loadFactor:负载因子,threshold = capacity * threshold。

HashEntry:Segment中之类似,代表 hash 链表中的一个节点,源码如下:

static final class HashEntry<K,V> { 
    final int hash;
    final K key;
    volatile V value;
    volatile HashEntry<K,V> next;
}

2.用户使用流程图

作为同缓慢轻问诊类APP,其主导职能应该是以化解用户“改善心态“和“心事倾诉”两点的求为主,从壹心理的出品结构得以看来将“问答”和“咨询”两片要作用在一级菜单,可见定位于确切。下面来将起用户使用的流水线来感受壹心理的“问答”和“咨询”功能。

2.1. 数据结构和沿分段

HashTable在竞争剧烈的产出环境面临效率低下的缘由是:访问HashTable的线程都竞争同一把锁。

ConcurrentHashMap却允许多独修改操作并发的进展,其要是以了锁分段技术:将容器内的数据分为一段子段(Segment)的来储存,每段可以用作一个稍微的HashTable,每段数据还分配一拿锁。当一个线程占用锁访问这无异于段数据经常,其他线程可以看其他段的数据。那么当多线程并发访问容器内不同锁锁住的数码时,线程间即不设有锁竞争,从而有效之升官效率。

ConcurrentHashMap的数据结构如下:

图片 8

ConcurrentHashMap 由 Segment 数组和 HashEntry 数组组成。一个
ConcurrentHashMap 里含有一个 Segment 数组,Segment 的组织及 HashMap
类似,是同栽数组和链表结构。一个 Segment 里带有一个 HashEntry 数组,每个
HashEntry 用于存储 key-value 键值对数据,同时对下一个 HashEntry
节点。当定位 key 在 ConcurrentHashMap 中之职时,需要先经过同浅 hash
定位到 Segment 的职务,然后在 hash 定位及指定的HashEntry。

Segment 是同样栽而重入锁 ReentrantLock,在 ConcurrentHashMap
里去锁的角色,每个 Segment 守护在一个HashEntry 数组里的元素,当对
HashEntry
数组的数码开展改动时,必须首先取得它对应之Segment锁。而大多读操作是勿加锁之,但是
size()、containsValue() 遇到并发修改竞争时索要全表加锁。

2)苹果端

APP
STORE的总榜(免费榜)都是一个综合性较强的指标,据坊间流传的说教,影响AppStore总榜排名之连带要素:

a.单位时内的下载量,评论数,评论星级,刷榜权重,热词搜索覆盖度,卸载率,当天以时长等

b.关键词搜索覆盖度——是指app能让展现的追寻关键词越多、权重越充分,排名就见面更为靠前(刷榜权重,如果用为发现发生刷榜行为,会被苹果降级)

图片 9

壹心理近三独月排名

为此,苹果端的总榜/免费棒的排行会比生程度及影响一款APP的被欢迎程度。下面我选壹心理和另外三独竞品的近期老三个月的排行变化数据:

图片 10

数码来源于:ASO100

由于各下分类不尽相同,在正规健美类别有:壹心理和暖心理,在治病项目有我心和壹点灵,但不论是正常的对待或是混排,我们且可看来壹心理的排行最依赖前都平静,基本以100-200中间有点幅度摆动,属于较不错的程度。

2. ConcurrentHashMap在JDK1.7遇之统筹

(1)行业分析

1)市场需求量巨大,等待时爆发

每当华夏,有1200万人数来不同程度之心理疾病,当他俩用规范心理援助的早晚却未晓得哪来赖谱的思医师,也就是说,目前国内缺乏使得之渠道让公众连接专业的思想服务人口。

因为抑郁症患者也例,中国大致来3000万章抑郁症患者,但单来5%得到看。据统计,中国抗抑郁药物‘氟西汀‘的销售额将当2015年跳70亿。国际心理治疗大会的数量也标志:中国大约有1.9亿口于一生中要承受专业的心理咨询或心理治疗。

另外,根据对世卫组织成员国的人均国民收入和人均精神卫生支出比研究发现,GNI(人均国民总收入)和思维健康服务资费表现来稳步的相关性。

图片 11

多少来自:世界卫生组织(摘自36氪)

从今点的大方向图可以看,当GNI超过10000美元时,人均精神卫生支出呈现有明显的大幅增加势头。据世界银行以图集法统计,2012年我国人均GNI为5870美元,2013年高达6710美元,2014年达到7400美元,2015年追加到大约7880美元,其中2012-2014年,我国人均GNI年全加快及7.3%,远超出世界平均提高水平及高收入国家增长水平。

2)政策标准方便好

多年来,我国政府本着心理健康领域的推崇逐渐增强,先后宣布了《中华人民共和国精神卫生法》和《全国精神卫生工作规划(2015—2020年)》,因此我们当我国发出副心理健康医疗的创业土壤。

归纳以上两触及来拘禁,我国心理健康行业还处发展最初阶段,有着好好的政策原则,市场需求量巨大,发展潜力无限,未来几乎年之趋势都应因市场教育为主,慢慢养用户之心理健康意识。

2.3. put

下是CouncurrentHashMap的put操作的源码:

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);      
    int j = (hash >>> segmentShift) & segmentMask; // 对key求hash,并取模,然后找到对应Segment数组下标位置
    if ((s = (Segment<K,V>)UNSAFE.getObject         
         (segments, (j << SSHIFT) + SBASE)) == null)
        s = ensureSegment(j);                       // 当 Segment 为空时,会创建
    return s.put(key, hash, value, false);          // put操作会委托给Segment的put
}

下面是ensureSegment的代码:

private Segment<K,V> ensureSegment(int k) {
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment<K,V> seg;
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {  // UNSAFE进行volatile读Segment数组对应位置的值
        Segment<K,V> proto = ss[0]; // use segment 0 as prototype   如果为空,则从Segment[0]复制Entry数组长度capacity,loadFactor
        int cap = proto.table.length;
        float lf = proto.loadFactor;
        int threshold = (int)(cap * lf);
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))== null) { // recheck 再次volatile读Segment数组,如果为空,继续新建Segment对象
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
                // 使用while循环和UNSAFE的CAS原子性替换Segment数组对应下标的元素。使用乐观锁的方式
                // 当线程t1和t2都读取Segment[u]==null时,只有一个线程能通过CAS替换成功。假设t1替换成功了,下一次while循环t2能volatile读取到替换的值
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))     
                    break;
            }
        }
    }
    return seg;
}

put最终交由 Segment 的 put
方法,每个Segment相当给一个HashMap,put操作就若当HashMap中寻觅对应之key是否有,如果存在则更新value,如非设有则新建一个HashEntry。put需要加锁,使用了ReetrantLock的tryLock的非阻塞加锁方法。源码如下:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // tryLock尝试加锁,如果加锁成功,node=null。否则自旋等待并寻找对应key的节点是否存在
    HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);   // 获取锁后,对HashEntry的table数组取模,获取数组下标index的第一个节点
        for (HashEntry<K,V> e = first;;) {            // first节点后面的链表,向后遍历,寻找与key相同的节点
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;            // 找到与key相同的节点,按onlyIfAbsent判断是否替换旧的value值
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else { 
                // e==null,代表没有找到。新建HashEntry节点或使用scanAndLockForPut中创建的节点作为新的链表头节点
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);  
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)   // 如果当前总节点个数 > threashold,则rehash扩容
                // Segment rehash是获取锁之后进行的,是将数组长度扩大一倍,将旧的数组元素复制到新数组中(前后有获取锁和释放锁的语义,不需要考虑多线程问题)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);   // 设置新HashEntry节点到table对应的位置中
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();       // 最后解锁
    }
    return oldValue;
}

发生一个接触:HashEntry<K,V>[] tab =
table。一开始用table赋值给tab局部变量,可以减直接引用table时带的性能损失,因为table是一个volatile变量,不能够展开优化,而赋值给tab普通变量后,可以实现编译、运行时之优化。

(四)用户意见

相似的话,用户意见能够反映用户之急需及骨子里运用感受,当然这是好好之状态,因为目前刷榜刷评论的行为并无鲜见,还有竞争对手的故意黑,因此采取市场用户的品仅供参考,并无可知一心真实反应该采取之成色水平。前面说及壹心理安卓端的严重性分发渠道是华为应用市场及360部手机助手,加上安卓各大用市场尚未一样拟于好的反假评论系统,为了见重复实的评论,最终挑选了自身觉得真正概率高的APP
STORE中用户评价。

图片 12

APP STORE最近叔单月之评

由于地方的评说截图可以分析,用户的眷顾点要以篇章、FM等效果。其中50%之评是有关文章功能,希望是一揽子文章的归类效果的主意比较高;对于FM功能,有定时关闭和离线下载的需求,由这可以倒出用户的应用状况:

1.定时关闭:经过同上之忙后,一个人口清净的躺在铺上,打开壹心理FM,查看自己关心的小圈子有无声音更新,听罢即关,这时候的用户就得定时关闭功能。

2.离线下载:上下班挤地铁的下,戴上耳机,暂时与外场嘈杂的世界隔离,享受温暖治愈的声响,但是地铁达到信号不好,并且FM耗费流量大,一般用户会挑选当WIFI环境下听,这时出离线下充斥功能就是展示甚好了。

3.2. 结构:Node和TreeBin

Node是ConcurrentHashMap存储结构的基本单元,用于存储key-value键值对,是一个链表,但无非同意查找数据,不允许修改数据。源码如下:

/**
 * Key-value entry.  This class is never exported out as a
 * user-mutable Map.Entry (i.e., one supporting setValue; see
 * MapEntry below), but can be used for read-only traversals used
 * in bulk tasks.  Subclasses of Node with a negative hash field
 * are special, and contain null keys and values (but are never
 * exported).  Otherwise, keys and vals are never null.
 */
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    // value和next使用volatile来保证可见性和禁止重排序
    volatile V val;
    volatile Node<K,V> next;  // 指向下一个Node节点

    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }

    public final K getKey()       { return key; }
    public final V getValue()     { return val; }
    public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
    public final String toString(){ return key + "=" + val; }
    // 不允许更新value
    public final V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    /**
     * Virtualized support for map.get(); overridden in subclasses.
     */
    // 用于map.get()方法,子类重写
    Node<K,V> find(int h, Object k) {
        Node<K,V> e = this;
        if (k != null) {
            do {
                K ek;
                if (e.hash == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    return e;
            } while ((e = e.next) != null);
        }
        return null;
    }
}

TreeBin是封装TreeNode的容器,提供了转发红黑树的一对条件及沿之操纵,部分源码如下:

/**
 * TreeNodes used at the heads of bins. TreeBins do not hold user
 * keys or values, but instead point to list of TreeNodes and
 * their root. They also maintain a parasitic read-write lock
 * forcing writers (who hold bin lock) to wait for readers (who do
 * not) to complete before tree restructuring operations.
 */
static final class TreeBin<K,V> extends Node<K,V> {
    TreeNode<K,V> root;       // 指向TreeNode的根节点
    volatile TreeNode<K,V> first;
    volatile Thread waiter;
    volatile int lockState;
    // values for lockState
    static final int WRITER = 1; // set while holding write lock
    static final int WAITER = 2; // set when waiting for write lock
    static final int READER = 4; // increment value for setting read lock
    ...
}

(4)核心力量

1)提供免费问答,解决用户一般心理困惑;

2)提供付费咨询服务,专业定制化扫除用户心理障碍;

3)开展心理公开课,从心理学角度分析日常生活;

4)达人分享治愈型FM,用声音温和听众。

5.市场分析

4.用户要求分析

(一)产品概述

目录

(一)产品概述

(二)产品分析

(三)竞品分析

(四)用户意见

(五)如果自己是PM


(5)用户以状况

1)年轻用户在恋爱和婚姻及逢困惑,比如如何读透对方心思等;

2)大学生在学习与个人成长问题达成之类疑惑,需要人来指导;

3)初入职场的新娘为快速融入团队;

4)父母亲在亲子教育上遇到困难;

5)偏内往的用户遇到不好听的总人口跟从事,导致情绪降甚至轻度抑郁等

2.交互方面

问答模块:将标签页与题材编辑页合并,让用户以编排问题面前先确定问题之范畴,对问题思路的梳理出正朝着影响,同时还可以抽操作流程,更关键之凡更为适合人之思维习惯。

咨询师介绍页:将问答、文章等情节折叠,第三屏的周头像放到第一屏,将咨询师个人介绍折叠,或者避免页面过长可做成tab形式,另外不妨将“关注”按钮放到更掀起和易于点击的图上位置。

测试页:配合视觉设计,采取更敏感有趣的交互方式。

壹心理学院:

1)将“家课程”拆解合并及“婚恋课程”与“亲子教育”,根据急需优先级充实而“个人成长”等课程;

2)用“限时免费”替代“其他科目”在科目筛选区域。

(1)问答功能

图片 13

“问答”功能用户采取流程

(三)产品分析

2.具体对比

(1) 目标用户

心理咨询者(首要):需要寻求解答心理困惑、改善情绪的人群

标准爱好者:a.专业心理咨询师 b.非独立机构心理爱好者

1)功能差异化分析

图片 14

竞品功能对比

于效果的覆盖来拘禁,壹心理和”我心目”都比较完善,壹心理的表征功能是FM和线达公开课,电台功能重要得益于该企业旗下别一款APP——心理FM,至于线上公开课很可能和丰富的咨询师资源有关。竞品”我心目”主要噱头是直播功能,借鉴了目前热门领域直播行业,可以送赠咨询师虚拟礼物。

(2)关于咨询作用

(3)用户需要

1)心理咨询者

丝下渠道:怀疑或担心好病倒有心理障碍,但迫于脸面问题讳疾忌医;找专业心理咨询机构,但难费财。

丝及渠道:心理信息零散且专业性不可知确保,针对性和互动性欠缺。

此外,据世界卫生组织统计显示,中国来2.5亿口欲“心理服务”,有8000万总人口得“心理治疗”,中国17东以下的娃子青少年吃,至少有3000万丁-5000万总人口遭到各种情绪障碍和作为问题之赘。

2) 专业爱好者

专业咨询师:提高个人知名度,积累病例,在提升个人能力的以增加收入。

心理学爱好者:获得重新多心理学知识,并会帮助人家解决思想问题。

1)专家榜路线

图片 15

6步专家咨询

平等,起在营收重要力量的学者预约咨询模块出三生进口,主要也凡根“咨询”导航与首页的预定专家板块,“即时倾诉”算是“咨询”功能入口的分,三位一体组成主要之流量入口。假设用户有约定专家的需求,那么该用户以壹心理走相同任何专家预约咨询的流水线最短距离需要经过6独步骤:“进入—预约咨询—选择专家—预约该师—填信息—支付”,对于付费的要害决定吧,整个流程长在情理之中的限量外。下面仅对个别页面分析:

咨询师介绍页

图片 16

红框:有待提升标记

要拿提问问诊视为等同起交易,那么咨询师所能够提供的劳动就是是同等码需要发售的货品,众所周知,商品详情页在流量一定的场面下本着转会自在重要的用意。壹心理的咨询师介绍页面内容大概占4屏,信息量较生。

优点:提高客户好感和促进成交的素基本全,包括咨询师头衔、擅长方向、文章、回答平台认证和保证,客户评价等。

不足:内容框架方面,单页信息量负载过长了重,且擅长方向最好多,跟第三屏之描述来那么些一样的远在,有直接领取的存疑,给人感到不专业

视觉效果方面:a
第一屏之名师介绍对比不足;b.图片占用空间稍加深,导致页面出现“上容易下再次”的失衡感;c.预约按钮色虽与logo颜色统一,但于色彩心理学角度,蓝色之刺激性不足。

建议

1.擅长内容结合精简到1-2只世界,最多不能够超越3个,将略微长情标签化并举行对比色处理或以背景做高斯模糊处理;

2.用问答、文章等情节折叠,第三屏的环头像放到第一屏,将咨询师个人介绍折叠,或者避免页面过长可做成TAB形式,另外不妨把“关注”按钮放到再次掀起和爱点击的图片上位置;

3.“预约”按钮颜色改吗刺激人欲的新民主主义革命。

PS:令我困惑一点的从事,壹心理对咨询师的页面设计无是一个模板,貌似是“大V”版(下图右边的就是),不晓是由于何种考虑啊?

图片 17

简单栽咨询师页面设计相比

1.产品结构

图片 18

壹心理APP结构

瞩目:有“+”图标的代表折叠,考虑到开展阅读经验不好,故将季层以下菜单折叠。

(4)关于首页热门推荐及订阅功能

依照官方数据透露,目前壹心理专栏作者都过600员,早期为为实用的心理学文章引发了扳平特别批判粉丝。通过品读文章,用户能够针对团结之认知产生了更加的升官,甚至有的用户打开APP就是为了看每天更新的心理学文章,用心理学的见解去认识自身,看透纷繁生活之私下,重新审视这世界。中国心理咨询市场最为深之问题或察觉不强,就医率低下,短期内之付费咨询的人流比例最小,因此没有门槛、高频的情节针对整市场之教导具有不可代替的位置。显然,壹心理稳稳的吸引了马上同样块领域,在APP的首页位置放置了首页“热门推荐”与“我的订阅”。

图片 19

红推荐和我的订阅

热点推荐心理学相关文章为主,偶尔推送测试。

优点:1.红推荐与订阅栏随着用户下拉浏览而一定在上,方便用户切换。

2.个别满足了随便目的浏览用户与个性化定制用户之急需,有利于增进用户的对APP的黏性。

不足:1.未曾有导航功能,当用户向下翻的章于多时,想回去地方的功效时只能逐步向回拉。

2.从来不订阅的上发只小BUG,从热推荐切换至“我之引进”,会冒出些微边内容同样的景象,给人卡顿的感觉到。需要经重新同不良左右滑动页面来刷新。

建议:加入局部导航功能,可以是按钮设计还是双击上方推荐栏来回到首页最老状态。

1.宏观对比

当做市场分析的早晚,壹心理的APP竞品主要有取暖心理、我心(心理记)、壹点灵、简单思维、柠檬心理等,下面要对壹心理与我心的差异化分析。

图片 20

竞品宏观对比分析

值得借鉴之效用是心理学直播,

然而行点:1.用户多大且偏年轻化,相比我心可能再发生优势;

2.可知引发更多年轻用户,有效增加咨询双方互为。

顾虑点:增加营业压力,对主播(咨询师)也发出重复胜之求

2.活概述

壹心理,是国内最可怜之大网心理学服务阳台有,于2011年7月上线,从上线之初到今日一直坚持心理健康领域。壹心理创始人黄伟强,先后创办了蓝心网和面向企业客户之博曼心理,拥有12年心理学及互联网创业经验,发展至今,旗下有壹心理、心理FM、口袋心理测试三独移动APP产品。壹心理平台为用户提供包括心理咨询、心理测试、线达公开课、心理文章、心理FM、问答等心理服务,其中课程分为免费和收费少种。据官方公开数量声称,包括网站同APP端注册用户规模大臻1300W。

(1)关于问答功能

图片 21

季步问答

问答功能于底部一级导航及首页的中游位置处于都安装了入口,可见壹心理对问答的珍惜,经体验整个从提问到付终止一共4单步骤“问答-提问-编辑-标签”,流程属于较简单直接,问答体验尚可。

不足之处:编辑好问题以后,“写好了”按钮给丁之暗示是收拾编写环节已截止,已经可以颁发状态,但当最后还要长标签,最后才得发布,给人发出种植唐突的发。

建议:建立标签的利不仅有益内容的分类与追寻,也便于咨询师的作答。将标签页与题材编辑页合并,让用户以编制问题面前先确定问题之层面,对题目思路的梳理出刚刚为影响,同时还可减操作流程,更要的凡越吻合人之思维习惯。

图片 22

题目编辑页

题材编辑页是满提问的主要,壹心理这里出个亮点作用:支持”匿名发布“,用户隐私工作考虑周到!

不足之处:没有退出二不好承认提醒。首先问题之编纂需要消耗大量底光阴和精力,用户发或因临时有事被打断或操作过程中不慎误触退出,再次上发现辛苦编辑的题目烟消云散,这将见面大幅度的打击用户之问话积极性。

建议:加入剥离二软承认,提示用户保存之弹窗提示,同时以个体基本成立“我之草”。

1)安卓端

壹心理的总下载量

图片 23

数量来源:酷传

暖心理的总下载量

图片 24

数来源于:酷传

我心的总下载量

图片 25

壹点灵的总下载量

图片 26

数据出自:酷传

抽取了季单APP最近少单月之总下载量变化,从图备受相比不难看出,在安卓端的总下载量排名:暖心理>我心>壹心理>壹点灵,截至10月11日行下载总量分别吗3973651、2467096、1228336和708386。

进而,我们更加数据挖掘,可理解:

壹心理的重大推广渠道也华为应用市场与360无线电话助手,分别约产生30W和20W的下载量。

暖心理的重要性推广渠道为安卓市场以及vivo应用商店,安卓市场是其极其重大流量渠道,贡献了类似200W的下载量,占了总下载量的靠近50%。

自身衷心的显要推广渠道也360手机助手、安卓市场和魅族应用企业,分别约产生51W、50W和41W的下载量。

壹点灵的要推广渠道也oppo应用商店与安卓市面,分别贡献了26W和17W的下载量。

小结

汇总来拘禁,安卓端用户下载量从数据来拘禁处于中等水平,在四只竞品的对待中排名第3位,相比暖心理和我心仍时有发生老挺的提高空间,这说不定同公司的战略性方向以及资源配置有关,因为壹心理APP只是“1+3”战略有,“1”指的凡单科心理官方网站,“3”指三个APP:壹心理、心理FM、口袋心理测试。后期可于第一渠道华为应用市场以及360无线电话助手两好重要流量渠道入手,增加加大力度,继续抓好相关ASO,争取得到重新特别的用户量。

有关苹果端,目前打好端端健美(免费)排名来拘禁,基本稳定于眼前200名为水平,偶尔冲上前前100底实绩,后续要继续办好有关优化及成品服务即可。

(三)竞品分析

(2)市场数量

在线心理健康市场,跟壹心理的“内容+社区+咨询”模式类似之APP竞品主要有取暖心理、我心、壹点灵、简单思维、柠檬心理等,下面我用自APP的总下载量和排名来分析壹心理和竞品暖心理、我心、壹点灵的市场景。

2)话题榜路线

图片 27

话题榜式咨询

同专家榜的类似,话题榜入口的预定咨询作用实在也发6步,只是前面两步跟大家榜一样,不同之处在于,专家列表变为”话题列表“,话题详情取代咨询师个人介绍页,但是于详情页也闹欠专家的牵线页跳转链接。

至于任何操作流程的利弊跟专家榜的形式大同小异,在这不做赘述。话题榜设置的初衷,我胆大臆测为下话题之热也切入点,以此唤起用户的共鸣,最终有付费。但一个人连自己问题之还搞不清楚的情形下,真的会为话题专家买特也?因此话题榜这个需要,我以为尚需要未来尤为探讨。