1. 程式人生 > >逆風奔跑。。不要去考慮什麼天賦異稟,一切都來自經歷和渴望。

逆風奔跑。。不要去考慮什麼天賦異稟,一切都來自經歷和渴望。

先總述,後分析

  深挖過threadLocal之後,一句話概括:Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。所以ThreadLocal的應用場合,最適合的是按執行緒多例項(每個執行緒對應一個例項)的物件的訪問,並且這個物件很多地方都要用到。

  資料隔離的祕訣其實是這樣的,Thread有個TheadLocalMap型別的屬性,叫做threadLocals,該屬性用來儲存該執行緒本地變數。這樣每個執行緒都有自己的資料,就做到了不同執行緒間資料的隔離,保證了資料安全。

  接下來採用jdk1.8原始碼進行深挖一下TheadLocal和TheadLocalMap

ThreadLocal是什麼

  早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。

  當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

  從執行緒的角度看,目標變數就象是執行緒的本地變數,這也是類名中“Local”所要表達的意思。

  所以,在Java中編寫執行緒區域性變數的程式碼相對來說要笨拙一些,因此造成執行緒區域性變數沒有在Java開發者中得到很好的普及。

原理

  ThreadLocal,連線ThreadLocalMap和Thread。來處理Thread的TheadLocalMap屬性,包括init初始化屬性賦值、get對應的變數,set設定變數等。通過當前執行緒,獲取執行緒上的ThreadLocalMap屬性,對資料進行get、set等操作。

  ThreadLocalMap,用來儲存資料,採用類似hashmap機制,儲存了以threadLocal為key,需要隔離的資料為value的Entry鍵值對陣列結構。

  ThreadLocal,有個ThreadLocalMap型別的屬性,儲存的資料就放在這兒。

ThreadLocal、ThreadLocal、Thread之間的關係

  ThreadLocalMap是ThreadLocal內部類,由ThreadLocal建立,Thread有ThreadLocal.ThreadLocalMap型別的屬性。原始碼如下:

Thread的屬性:

public
class Thread implements Runnable {
    /*...其他屬性...*/

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal和ThreadLocalMap

public class ThreadLocal<T> {
    /**..其他屬性和方法稍後介紹...*/
    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

由ThreadLocal對Thread的TreadLocalMap進行賦值

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal的介面方法

ThreadLocal類核心方法set、get、initialValue、withInitial、setInitialValue、remove:

   /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

    /**
     * Creates a thread local variable. The initial value of the variable is
     * determined by invoking the {@code get} method on the {@code Supplier}.
     *
     * @param <S> the type of the thread local's value
     * @param supplier the supplier to be used to determine the initial value
     * @return a new thread local variable
     * @throws NullPointerException if the specified supplier is null
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
  • initialValue返回該執行緒區域性變數的初始值。該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲呼叫方法,線上程第1次呼叫get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的預設實現直接返回一個null。
  • withInitial提供一個Supplier的lamda表示式用來當做初始值,java8引入。
  • setInitialValue設定初始值。在get操作沒有對應的值時,呼叫此方法。private方法,防止被覆蓋。過程和set類似,只不過是用initialValue作為value進行設定。
  • set設定當前執行緒對應的執行緒區域性變數的值。先取出當前執行緒對應的threadLocalMap,如果不存在則用建立一個,否則將value放入以this,即threadLocal為key的對映的map中,其實threadLocalMap內部和hashMap機制一樣,儲存了Entry鍵值對陣列,後續會深挖threadLocalMap
  • get該方法返回當前執行緒所對應的執行緒區域性變數。和set類似,也是先取出當前執行緒對應的threadLocalMap,如果不存在則用建立一個,但是是用inittialValue作為value放入到map中,且返回initialValue,否則就直接從map取出this即threadLocal對應的value返回。
  • remove將當前執行緒區域性變數的值刪除,目的是為了減少記憶體的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當執行緒結束後,對應該執行緒的區域性變數將自動被垃圾回收,所以顯式呼叫該方法清除執行緒的區域性變數並不是必須的操作,但它可以加快記憶體回收的速度。需要注意的是如果remove之後又呼叫了get,會重新初始化一次,即再次呼叫initialValue方法,除非在get之前呼叫set設定過值。

ThreadLocalMap簡介

  看名字就知道是個map,沒錯,這就是個hashMap機制實現的map,用Entry陣列來儲存鍵值對,key是ThreadLocal物件,value則是具體的值。值得一提的是,為了方便GC,Entry繼承了WeakReference,也就是弱引用。裡面有一些具體關於如何清理過期的資料、擴容等機制,思路基本和hashmap差不多,有興趣的可以自行閱讀了解,這邊只需知道大概的資料儲存結構即可。

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

Thread同步機制的比較

  ThreadLocal和執行緒同步機制相比有什麼優勢呢?

Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。

  在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式慎密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。

  而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。

  概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。

  Spring使用ThreadLocal解決執行緒安全問題我們知道在一般情況下,只有無狀態的Bean才可以在多執行緒環境下共享,在Spring中,絕大部分Bean都可以宣告為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非執行緒安全狀態採用ThreadLocal進行處理,讓它們也成為執行緒安全的狀態,因為有狀態的Bean就可以在多執行緒中共享了。

  一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過介面向上層開放功能呼叫。在一般情況下,從接收請求到返回響應所經過的所有程式呼叫都同屬於一個執行緒。

  同一執行緒貫通三層這樣你就可以根據需要,將一些非執行緒安全的變數以ThreadLocal存放,在同一次請求響應的呼叫執行緒中,所有關聯的物件引用到的都是同一個變數。

  下面的例項能夠體現Spring對有狀態Bean的改造思路:

程式碼清單3 TestDao:非執行緒安全

package com.test;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class TestDao {
	private Connection conn;// ①一個非執行緒安全的變數

	public void addTopic() throws SQLException {
		Statement stat = conn.createStatement();// ②引用非執行緒安全變數
		// …
	}
}

由於①處的conn是成員變數,因為addTopic()方法是非執行緒安全的,必須在使用時建立一個新TopicDao例項(非singleton)。下面使用ThreadLocal對conn這個非執行緒安全的“狀態”進行改造:

程式碼清單4 TestDao:執行緒安全

package com.test;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class TestDaoNew {// ①使用ThreadLocal儲存Connection變數
  private static ThreadLocal<Connection> connThreadLocal = ThreadLocal.withInitial(Test::createConnection);

  // 具體建立資料庫連線的方法
  private static Connection createConnection() {
    Connection result = null;
    /**
     * create a real connection...
     * such as :
     * result = DriverManager.getConnection(dbUrl, dbUser, dbPwd);
     */
    return result;
  }

  // ③直接返回執行緒本地變數
  public static Connection getConnection() {
    return connThreadLocal.get();
  }

  // 具體操作
  public void addTopic() throws SQLException {
    // ④從ThreadLocal中獲取執行緒對應的Connection
    Statement stat = getConnection().createStatement();
    //....any other operation
  }
}

  不同的執行緒在使用TopicDao時,根據之前的深挖get具體操作,判斷connThreadLocal.get()會去判斷是有map,沒有則根據initivalValue建立一個Connection物件並新增到本地執行緒變數中,initivalValue對應的值也就是上述的lamba表示式對應的建立connection的方法返回的結果,下次get則由於已經有了,則會直接獲取已經建立好的Connection,這樣,就保證了不同的執行緒使用執行緒相關的Connection,而不會使用其它執行緒的Connection。因此,這個TopicDao就可以做到singleton共享了。

  當然,這個例子本身很粗糙,將Connection的ThreadLocal直接放在DAO只能做到本DAO的多個方法共享Connection時不發生執行緒安全問題,但無法和其它DAO共用同一個Connection,要做到同一事務多DAO共享同一Connection,必須在一個共同的外部類使用ThreadLocal儲存Connection。

ConnectionManager.java

package com.test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

	private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {
		Connection conn = null;
		try {
			conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/test", "username",
					"password");
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	});

	public static Connection getConnection() {
		return connectionHolder.get();
	}
}

執行緒隔離的祕密

祕密就就在於上述敘述的ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設定和獲取(對比Map物件來理解),每個執行緒中都有一個獨立的ThreadLocalMap副本,它所儲存的值,只能被當前執行緒讀取和修改。ThreadLocal類通過操作每一個執行緒特有的ThreadLocalMap副本,從而實現了變數訪問在不同執行緒中的隔離。因為每個執行緒的變數都是自己特有的,完全不會有併發錯誤。還有一點就是,ThreadLocalMap儲存的鍵值對中的鍵是this物件指向的ThreadLocal物件,而值就是你所設定的物件了。


為了加深理解,我們接著看上面程式碼中出現的getMap和createMap方法的實現:
    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     * @param map the map to store.
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

小結

  ThreadLocal是解決執行緒安全問題一個很好的思路,它通過為每個執行緒提供一個獨立的變數副本解決了變數併發訪問的衝突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決執行緒安全問題更簡單,更方便,且結果程式擁有更高的併發性。

後記

  看到網友評論的很激烈,甚至關於ThreadLocalMap不是ThreadLocal裡面的,而是Thread裡面的這種評論都出現了,於是有了這個後記,下面先把jdk原始碼貼上,原始碼最有說服力了。

/**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {...}

  原始碼就是以上,這原始碼自然是在ThreadLocal裡面的,有截圖為證。

  本文是自己在學習ThreadLocal的時候,一時興起,深入看了原始碼,思考了此類的作用、使用範圍,進而聯想到對傳統的synchronize共享變數執行緒安全的問題進行比較,而總結的博文,總結一句話就是一個是鎖機制進行時間換空間,一個是儲存拷貝進行空間換時間。

(全文完)

相關推薦

逆風奔跑不要考慮什麼天賦一切來自經歷渴望

先總述,後分析  深挖過threadLocal之後,一句話概括:Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。所以ThreadLocal的應用場合,最適合的是按執行緒多例項(每個執行緒對應一個例項)的物件的訪問,並且這個物件

【cuseronline的專欄】我想一個人若肯花三至五年的時間專注的做一件事情總會有一些成績的吧

技術方向1: Eclipse平臺技術愛好者,實踐者,5年以上Eclipse外掛、RCP開發經驗,對Eclipse外掛、RCP有深入的瞭解。熟悉Eclipse外掛,RCP,GEF,JDT,AST及IDE開發,瞭解EMF,OSGI,對程式碼自動化生成及MDA等也有一定的經驗和實踐。閱讀和研

被引用的帳戶目前被鎖定可能不會登錄這個問題困擾我好久每次要找域管理員

是什麽 設置 管理員 閾值 密碼 但是 出現 還需 帳戶 這個問題,一直出現,而且每次都要找域administrator,他給我的用戶權限是admin的,但是我經常被鎖住,這是為什麽呢?因為怕暴力破解密碼,所以設置了10次的閾值,但是鎖定以後,就不會再解開,30分鐘過了也不

nginx----------前端寫了一套帶有vue路由的的功能放到nginx配置的目錄下以後刷新會報404未找到

HA index chat .html nginx配置 files activit nginx hat 1、   這是根據實際情況來寫的。   location /h5/activity/wechat/ { index index.html inde

有兩個字串strsubstrstrsubstr的字元個數不超過10^5只包含大小寫字母數字(字元個數不包括字串結尾處的’\0’)將substr插入到str中ASCII碼最大的那個字元

輸入格式只有一行: str substr java 程式碼: package jiegouSuanfa; import java.util.Scanner; public class InsertString {     public static void

【兵仔賈---的專欄】有聚就有散有相伴就有分離一切順其自然隨遇而安

姓名:賈大兵 生日:1992.8 學歷:大專 專注:c++、演算法、iOS開發 學校:湖北師範學院 愛好:籃球、遊戲、美女 目前工作:iOS移動終端開發

兵仔賈---的專欄(有聚就有散有相伴就有分離一切順其自然隨遇而安

姓名:賈大兵 生日:1992.8 學歷:大專 專注:c++、演算法、iOS開發 學校:湖北師範學院 愛好:籃球、遊戲、美女 目前工作:iOS移動終端開發

《連載 | 物聯網框架ServerSuperIO教程》1.4種通訊模式機制附小文:招.NET開發結果他轉JAVA了一切是為了生活

參考文章: 一、感慨       上大學的時候,沒有學過C#,花了5塊錢在地壇書市買了一本教程,也就算是正式入行了。後來深造,學過JAVA,後來迫於生計,打算轉JAVA了。後來考慮考慮,自己寫的框架還是有很大發展餘地,後來還是在C#的陣地上堅持了下來。從一開始的雛形,到SuperIO的

校招線上程式設計題:第一行為陣列的大小接下來為陣列元素將為0的值放在末尾非零值保持輸入順序

   線上程式設計題:第一行,為陣列的大小,接下來為陣列元素。將為0的值放在末尾,非零值保持輸入順序。例如:輸入: 4  0 8  0  3    輸出 : 8 3 0 0package org.personal.tjut.candy; import java.util

編寫子函式能將一個字串中的大寫字元變為小寫字元而小寫字元變為大寫字元主函式中要求能輸入字串並輸出變換後的字串

#include<stdio.h>int main(){ void change(char m[32]); int i; char m[32]; gets(m); change(m); printf("%s\n",m); return 0;}void change

定義一個圖形類及其子類(三角形類矩形類)分別計算其面積周長(第十週)

/*  * 定義一個圖形類及其子類(三角形類和矩形類),分別計算其面積和周長。  */ class Graphical {//父類public double width;//成員變數public double length;public double area;public double Perimeter;

Arith.java--浮點數運算 -- 由於Java的簡單型別不能夠精確的對浮點數進行運算這個工具類提供精確的浮點數運算包括加減乘除四捨五入

package com.boco.common.util; import java.math.BigDecimal; /** * 由於Java的簡單型別不能夠精確的對浮點數進行運算, <br>這個工具類提供精確的浮點數運算,包括加減乘除和四捨五入。 *

使用HashMap如果key是自定義的類就必須重寫hashcode()equals()

hashcode()和equals()都繼承於object,在Object類中的定義為: equals()方法在Object類中的定義: publicboolean equals(Object obj){     return (this== obj); } eq

我投了上百個標電話打到欠費了

7月3日,開源眾包上線了發包方的匹配增值服務。旨在進一步分層和提高平臺的發包質量。     &nbs

ajax入門 不要畏懼 很簡單 進了門一切好學多了

 以前總是聽別人說ajax是多麼的好,然後自己就去借了本書看,哇塞感覺好難哦,什麼介紹JavaScript,html,css,還有很多一些東西。看的那個難啊,然後就是硬著頭皮把它給看完了,但是就是缺少了一步最關鍵的步驟,執行它。理解它的原理。瞭解它的機制。去感覺它,觸控它

度度熊想商場買一頂帽子商場裏有N頂帽子有些帽子的價格可能相同度度熊想買一頂價格第三便宜的帽子問第三便宜的帽子價格是多少?

length dex 相同 多少 turn this javascrip brush 便宜 var data=[10,25,50,10,20,80,30,30,40,90]; function fun(arr,index){ var min=Math.

項目受源代碼管理向源代碼管理註冊此項目時出錯建議不要對此項目進行任何更改

provide win 建議 進行 work 打開 name 內容 源代碼管理 編譯Rocket.Windows.Framework項目的時候提示如題的錯誤, 用記事本打開出錯的幾個項目的.csproj文件,把下面幾行內容刪掉就行了。

將字符串轉成整數的函數給我看一下嗎簡單看一下代碼基本功要求:不要調用parseInt等轉換函數

main 一段 ack substr date tin || parse pre 為了提高面試流程效率,方便用java寫一段將字符串轉成整數的函數給我看一下嗎,簡單看一下代碼基本功。 要求:不要調用parseInt等轉換函數。按位讀取字符串裏的字符進行處理將字符串轉化為整數

VS 提示:請考慮使用 app.config 將程式集“XXX”從版本“XX”重新對映到版本“XX”以解決衝突並消除警告

具體提示如下: 請考慮使用 app.config 將程式集“System.Web.Http.WebHost, Culture=neutral, PublicKeyToken=31bf3856ad364e35”從版本“5.0.0.0”[D:\Code…(專案路徑就省略了)\bi

陣列顯示重複的元素及個數

 var arr = ['土豆','土豆','茄子','土豆','茄子','土豆','紫紅色'];     function qc(arr){         var resultObj = {};