Ne zaman ve nasıl bir ThreadLocal değişkeni kullanmalıyım?


876

Ne zaman ThreadLocaldeğişken kullanmalıyım ?

Nasıl kullanılır?


11
ThreadLocal kullanıyorsanız, üzerine asla bir sarıcı yazmayın !!! Değişkeni kullanmak isteyen her geliştirici bunun 'ThreadLocal' olduğunu bilmelidir
Hata değil

2
@Notabug Açıksanız, değişkeninizi bir ThreadLocal değeriyle uğraşmanız için adlandırabilirsiniz, örn RequestUtils.getCurrentRequestThreadLocal(). Ancak bunun çok zarif olduğunu söylememek, ancak bu, ThreadLocal'ın kendisinin çoğu durumda çok zarif olmaması gerçeğinden kaynaklanmaktadır.
whirlwin

@Sasha ThreadLocal mağaza yürütme izi için kullanılabilir
gaurav

Yanıtlar:


864

Bir olası (ve ortak) kullanımı, iş parçacığı için güvenli olmayan bir nesneye sahip olduğunuzda, ancak o nesneye erişimi eşitlemek istemediğinizde (Sana bakıyorum, SimpleDateFormat ). Bunun yerine, her iş parçacığına kendi nesne örneğini verin.

Örneğin:

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

Belgeler .


160
Senkronizasyon veya threadlocal'a bir başka alternatif de değişkeni yerel bir değişken yapmaktır. Yerel değişkenler her zaman diş açmaya karşı güvenlidir. DateFormats'ı yerel hale getirmenin kötü bir uygulama olduğunu varsayıyorum çünkü bunların oluşturulması pahalı, ancak bu konuda hiçbir zaman sağlam metrikler görmedim.
Julien Chastang

18
Bu kesmek için ödemek için yüksek fiyattır SimpleDateFormat. İplik güvenli bir alternatif kullanmak daha iyidir . Eğer singletonların kötü olduğunu kabul ediyorsanız , o zaman ThreadLocaldaha da kötüdür.
Alexander Ryzhov

4
@overthink ThreadLocal statik ve son, performans ya da başka bir şey demek için herhangi bir sebep var mı?
Sachin Gorade

4
ThreadLocal.get () yöntemi, her bir iş parçacığı için ThreadLocal.initialValue () (bir kez) öğesini çağırır, yani her iş parçacığı için bir SimpleDateFormat nesnesi oluşturulur. SimpleDateFormat'ı yerel değişkenler olarak kullanmak daha iyi değil mi (çöp toplama sorunlarıyla uğraşmak zorunda olmadığımız için)?
sudeepdino008

3
SimpleDateFormat'ınız için çift ayraç başlatmamaya dikkat edin, çünkü bu, sınıf yükleyicinizin çöp toplanamaması için anonim bir sınıf oluşturacaktır. bellek sızıntısı: dönüş yeni SimpleDateFormat () {{cameramate ("yyyyMMdd HHmm")}};
user1944408

427

A ThreadLocal, verilen bir Threadverideki bir referans olduğundan , ThreadLocaliş parçacığı havuzları kullanan uygulama sunucularında s kullanırken sınıf yükleme sızıntılarına neden olabilirsiniz . Herhangi temizlik konusunda çok dikkatli olmak gerekir ThreadLocalsizi s get()veya set()kullanarak ThreadLocal'ın remove()metodunu.

İşiniz bittiğinde temizlemezseniz, konuşlandırılmış bir web uygulamasının parçası olarak yüklenen sınıflara yaptığı referanslar kalıcı yığın halinde kalır ve asla çöp toplanmaz. Web uygulamanızın sahip olduğu bir şey olmadığından, webapp'ın yeniden konuşlandırılması / çözülmesi webapp Threadsınıfınıza yönelik her bir referansı temizlemez Thread. Her ardışık dağıtım, hiçbir zaman çöp toplanmayacak yeni bir sınıf örneği oluşturur.

java.lang.OutOfMemoryError: PermGen spaceBazı googling nedeniyle ve sonra -XX:MaxPermSizehatayı düzeltmek yerine muhtemelen artacak bellek yetersiz istisnalar ile sonuçlanacak .

Bu sorunları yaşarsanız, Eclipse Bellek Analizörü'nü kullanarak ve / veya Frank Kieviet'in kılavuzunu ve takibini takip ederek hangi iplik ve sınıfın bu referansları tuttuğunu belirleyebilirsiniz .

Güncelleme: Alex Vasseur'un yaşadığım bazı ThreadLocalsorunları izlememe yardımcı olan blog girişini yeniden keşfetti .


11
Alex Vasseur blogunu taşıdı. İşte bellek sızıntısı makalesine geçerli bir bağlantı.
Kenster

12
Julien'in bağladığı konu buraya taşınmış gibi görünüyor ve okumaya değer…
Donal Fellows

28
Bu, bilgilendirici olsa da, hiçbir şekilde soruyu gerçekten cevaplamayan bir cevap için çok sayıda upvotes'dur.
Robin

8
Artık PermGen Java 8 tarafından öldürüldüğüne göre, bu cevap herhangi bir şekilde değişiyor mu?
Evil Çamaşır Makinesi

13
@Robin: Kabul etmiyorum. Soru, ThreadLocal'ın doğru bir şekilde nasıl kullanılacağı, herhangi bir kavramı (burada ThreadLocal) tam olarak anlamak için nasıl kullanılacağı ve dikkatsizce kullanılma risklerinin anlaşılması önemlidir ve Phil'in cevabı budur. Başka bir yanıta yer vermeyen bu noktayı ele aldığından memnunum. Tüm bu oylar hak ediyor. SO'nun sadece bir QA sitesi olmaktan ziyade kavramları anlaması gerektiğine inanıyorum.
Saurabh Patil

166

Birçok çerçeve, geçerli iş parçacığıyla ilgili bir bağlamı korumak için ThreadLocals kullanır. Örneğin, geçerli işlem bir ThreadLocal içinde depolandığında, yığıntaki birisinin buna erişmesi gerekiyorsa, her yöntem çağrısında bir parametre olarak iletmeniz gerekmez. Web uygulamaları geçerli istek ve oturum hakkındaki bilgileri bir ThreadLocal içinde depolayabilir, böylece uygulama bunlara kolayca erişebilir. Guice ile , enjekte edilen nesneler için özel kapsamlar uygularken ThreadLocals'ı kullanabilirsiniz (Guice'in varsayılan sunucu uygulaması kapsamları büyük olasılıkla bunları da kullanır).

ThreadLocals bir tür küresel değişkendir (bir iş parçacığıyla sınırlı oldukları için biraz daha az kötü olsa da), bu nedenle istenmeyen yan etkilerden ve bellek sızıntılarından kaçınmak için bunları kullanırken dikkatli olmalısınız. API'larınızı, artık ihtiyaç duyulmadığında ThreadLocal değerleri her zaman otomatik olarak silinecek ve API'nın yanlış kullanımı mümkün olmayacak şekilde tasarlayın (örneğin , bunun gibi ). ThreadLocals, kodu daha temiz hale getirmek için kullanılabilir ve bazı nadir durumlarda, bir şeyleri çalıştırmanın tek yoludur (şu anki projemde böyle iki durum vardı; burada "Statik Alanlar ve Global Değişkenler" altında belgelenmiştir ).


4
ExPOJO çerçevesi (www.expojo.com), ek açıklamalara ve enjeksiyona ek yüke gerek kalmadan ORM Session / PersistenceManager'a tam olarak böyle izin verir. 'Nesne enjeksiyonu' yerine 'iplik enjeksiyonu' gibidir. Bu bağımlılıklara ihtiyaç duyabilecek her nesneye gömülmesine gerek kalmadan (ve ek yük) bağımlılıklara erişim sağlar. Klasik DI (örneğin, Spring vb.)
Yerine

27
Bu temel cevabı bulmak için neden bu kadar aşağı kaydırmam gerekti?
Jeremy Stein

1
@Esko, Projenizde, ThreadLocal'ı hashcode ve equals'ta kullanmanız gerekmiyor. Daha iyi bir yaklaşım, gerektiğinde bir hashcode / equals işlevine başvuru oluşturmak veya bu işleve bir referans iletmektir. Bu şekilde ThreadLocal hack ihtiyacını tamamen önleyebilirsiniz.
Pacerier


1
TL kullanımının en iyi açıklaması budur.
Dexter

52

Java'da, iş parçacığı başına değişebilen bir veriye sahipseniz, seçimleriniz bu veriyi ona ihtiyaç duyan (veya ihtiyaç duyabilecek) her yönteme iletmek veya veriyi iş parçacığıyla ilişkilendirmektir. Tüm yöntemlerinizin zaten ortak bir "bağlam" değişkeninden geçmesi gerekiyorsa, veriyi her yerde geçirmek uygulanabilir.

Durum böyle değilse, yöntem imzalarınızı ek bir parametre ile karıştırmak istemeyebilirsiniz. İş parçacığı olmayan bir dünyada, sorunu global bir değişkenin Java eşdeğeri ile çözebilirsiniz. İş parçacıklı bir sözcükte, genel bir değişkenin eşdeğeri bir iş parçacığı-yerel değişkendir.


13
Bu nedenle, yerelden kaçındığınız şekilde yerel iş parçacıklarından kaçınmalısınız. Değerleri iletmek yerine globaller (yerel iş parçacıkları) oluşturmanın sorun olmayacağını kabul edemiyorum, insanlar genellikle düzeltmek istemedikleri mimari sorunları ortaya çıkardığından hoşlanmıyor.
Juan Mendes

10
Belki de ... Her yerde aktarılması gereken yeni bir veri eklemeniz gereken çok büyük, mevcut bir kod tabanınız varsa , örneğin bir oturum bağlamı, db işlemi, oturum açmış kullanıcı, vb. .
Cornel Masson

6
Veri yerine referans noktası kullanmak için şeref.
derin

Bu beni meraklandırdı. 'datum' is the singular form and 'data' is the plural form.
Siddhartha

23

Uygulamada Java Concurrency kitabında çok iyi bir örnek var . Yazar ( Joshua Bloch ), İplik sınırlamasının iplik güvenliğini sağlamanın en basit yollarından biri olduğunu ve ThreadLocal'ın iplik sınırını korumanın daha resmi yollarını açıkladığı yerdir . Sonunda, insanların küresel değişkenler olarak kullanarak onu nasıl kötüye kullanabileceğini de açıklıyor.

Söz konusu kitaptan metni kopyaladım ancak ThreadLocal'ın nerede kullanılması gerektiğini anlamak çok önemli olmadığından kod 3.10 eksik.

İş parçacığı yerel değişkenleri, genellikle değiştirilebilir Değişkenler veya global değişkenlere dayalı tasarımlarda paylaşımı önlemek için kullanılır. Örneğin, tek iş parçacıklı bir uygulama, her yönteme bir Bağlantıyı geçmek zorunda kalmamak için başlangıçta başlatılan genel bir veritabanı bağlantısını koruyabilir. JDBC bağlantıları iş parçacığı için güvenli olmayabileceğinden, ek koordinasyon olmadan genel bağlantı kullanan çok iş parçacıklı bir uygulama da iş parçacığı için güvenli değildir. Liste 3.10'daki ConnectionHolder'da olduğu gibi JDBC bağlantısını saklamak için bir ThreadLocal kullanarak, her iş parçacığının kendi bağlantısı olacaktır.

ThreadLocal, uygulama çerçevelerinin uygulanmasında yaygın olarak kullanılmaktadır. Örneğin, J2EE kapsayıcıları, bir işlem bağlamını bir EJB çağrısı süresince bir yürütme iş parçacığıyla ilişkilendirir. Bu, işlem bağlamını tutan statik bir Thread-Local kullanılarak kolayca uygulanır: çerçeve kodunun şu anda hangi işlemin çalıştığını belirlemesi gerektiğinde, işlem içeriğini bu ThreadLocal öğesinden alır. Bu, yürütme bağlam bilgilerinin her yönteme geçirilmesi ihtiyacını azaltması açısından uygundur, ancak bu mekanizmayı kullanan herhangi bir kodu çerçeveye bağlar.

ThreadLocal özelliğini, global değişkenleri kullanma lisansı veya “gizli” yöntem bağımsız değişkenleri oluşturmanın bir yolu olarak iş parçacığı hapsetme özelliğini kullanarak kötüye kullanmak kolaydır. Küresel değişkenler gibi, yerel-yerel değişkenler de yeniden kullanılabilirlikten uzaklaşabilir ve sınıflar arasında gizli kuplajlar oluşturabilir ve bu nedenle dikkatli kullanılmalıdır.


18

Esasen, bir ihtiyacım olduğunda geçerli iş parçacığı üzerinde bağımlı değişkenin değerini ve onu size başka bir şekilde iplik değer eklemek için uygun değil (iplik sınıflara, örneğin).

Tipik bir durum, başka bir çerçevenin, kodunuzun çalıştığı iş parçacığını oluşturduğu , örneğin bir sunucu uygulaması kapsayıcısının veya değişkeninizin daha sonra "mantıksal yerinde" (değişken yerine) İş parçacığı alt sınıfından veya başka bir karma haritada asılı).

Web sitemde, ThreadLocal'ın ne zaman kullanılacağına dair daha fazla tartışmam ve örneklerim var .

Bazı insanlar, bir thread numarasına ihtiyaç duyduğunuz belirli eşzamanlı algoritmalarda her thread'a "thread ID" eklemenin bir yolu olarak ThreadLocal'ı kullanmayı savunurlar (bkz. Herlihy & Shavit). Bu gibi durumlarda, gerçekten fayda sağladığınızı kontrol edin!


ThreadLocal mağaza yürütme iz için kullanılabilir
Gaurav

13
  1. Java'daki ThreadLocal, JDK 1.2'de tanıtıldı, ancak daha sonra ThreadLocal değişkeni üzerinde tip güvenliği sağlamak için JDK 1.5'te üretildi.

  2. ThreadLocal, Thread kapsamı ile ilişkilendirilebilir, Thread tarafından yürütülen tüm kod ThreadLocal değişkenlerine erişebilir, ancak iki thread birbirinin ThreadLocal değişkenini göremez.

  3. Her iş parçacığı, normal olarak veya herhangi bir İstisna nedeniyle iş parçacığı bittikten veya öldükten sonra Çöp toplamaya uygun hale gelen özel bir ThreadLocal değişkeninin bir kopyasını tutar.

  4. Java'daki ThreadLocal değişkenleri genellikle Sınıflar'daki özel statik alanlardır ve durumunu Thread içinde tutar.

Devamını oku: Java'da ThreadLocal - Örnek Program ve Eğitim


ThreadLocal mağaza yürütme iz için kullanılabilir
Gaurav

11

Belgeler çok iyi söylüyor: "[yerel-iş parçacığı değişkenine] erişen her iş parçacığının (get veya set yöntemiyle) değişkenin kendi bağımsız olarak başlatılmış bir kopyası var".

Her iş parçacığının kendi kopyasına sahip olması gerektiğinde bir tane kullanırsınız. Varsayılan olarak, veriler iş parçacıkları arasında paylaşılır.


18
Varsayılan olarak, statik nesneler veya her iki iş parçacığının aynı nesneye başvuru içerdiği iş parçacıkları arasında açıkça geçirilen nesneler paylaşılır. Bir iş parçacığında yerel olarak bildirilen nesneler paylaşılmaz (iş parçacığının yığınında yereldir). Sadece bunu açıklığa kavuşturmak istedim.
Ian Varley

@IanVarley: Bunu işaret ettiğiniz için teşekkürler. MyThread sınıfında bildirilen ve kullanılan bir değişkenimiz varsa, bu durumda ThreadLocal'a ihtiyacımız var mı? Örnek: class MyThread Thread {String var1 ;, int var2; } bu durumda var1 ve var2, Thread kendi yığınının bir parçası olacak ve paylaşılmayacak, bu durumda ThreadLocal'a ihtiyacımız var mı? Anlayışımda tamamen yanlış olabilirim, lütfen yönlendirin.
Jayesh

4
Her iş parçacığının kendi bir kopyasını almak isterse, neden yerel olarak bildirilemiyor (her zaman iş parçacığı için güvenli)?
sudeepdino008

@ sudeepdino008 her zaman bu konuların (webapp çerçeveleri, threadpool kütüphaneleri) oluşturulmasını kontrol
edemezsiniz

10

Webapp sunucusu bir iş parçacığı havuzu tutabilir ThreadLocalve istemciye yanıt vermeden önce bir değişken kaldırılmalıdır, dolayısıyla geçerli iş parçacığı bir sonraki istekle yeniden kullanılabilir.


8

İş
parçacığı değişkeninin kullanılabileceği iki kullanım durumu - 1- Durumu bir iş parçacığıyla ilişkilendirme gereksinimimiz olduğunda (örneğin, bir kullanıcı kimliği veya İşlem Kimliği). Bu genellikle bir sunucu uygulamasına giden her isteğin kendisiyle ilişkilendirilmiş benzersiz bir işlem kimliği olan bir web uygulamasıyla olur.

// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

Burada ınInitial yönteminin lambda ifadesi kullanılarak uygulandığını unutmayın.
2- Başka bir kullanım durumu, güvenli bir iş parçacığı örneğine sahip olmak istediğimizde ve senkronizasyon ile performans maliyeti daha fazla olduğundan senkronizasyonu kullanmak istemediğimiz durumdur. Böyle bir durum SimpleDateFormat kullanıldığındadır. SimpleDateFormat iş parçacığı güvenli olmadığından, iş parçacığını güvenli hale getirmek için mekanizma sağlamalıyız.

public class ThreadLocalDemo1 implements Runnable {
    // threadlocal variable is created
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
            return new SimpleDateFormat("dd/MM/yyyy");
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo1 td = new ThreadLocalDemo1();
        // Two threads are created
        Thread t1 = new Thread(td, "Thread-1");
        Thread t2 = new Thread(td, "Thread-2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + dateFormat.get().toPattern());
        System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
    } 

}

Eğer tüm amacınız artımlı bir benzersiz tamsayı değeri döndürmekse, yine bir örnekte benzersiz kimliği oluşturmak için ThreadLocal kullanmanın faydasını görmedim. `` public class ThreadId {// Özel statik son atanacak sonraki iş parçacığı kimliğini içeren atom tamsayısı AtomicInteger nextId = new AtomicInteger (0); // Geçerli iş parçacığının benzersiz kimliğini döndürerek gerekirse genel statik int get () {return nextId..getAndIncrement (); }} ``
dashenswen

7

Java 8 sürümünden bu yana, başlatmanın daha bildirici bir yolu var ThreadLocal:

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

Java 8 sürümüne kadar aşağıdakileri yapmak zorundaydınız:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

Ayrıca, sınıfın örnekleme yöntemi (yapıcı, fabrika yöntemi) ThreadLocalherhangi bir parametre almazsa, yöntem başvurularını (Java 8'de tanıtıldı) kullanabilirsiniz:

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

Not:java.util.function.Supplier Yalnızca ThreadLocal#getarandığında değerlendirilen ancak değer daha önce değerlendirilmeyen lambda'yı geçtiğiniz için değerlendirme tembeldir .


5

ThreadLocal modeline çok dikkat etmelisiniz. Phil gibi bazı önemli aşağı taraflar vardır, ancak bahsedilmeyen bir tanesi ThreadLocal bağlamını oluşturan kodun "yeniden giriş" olmadığından emin olmaktır.

İş parçacığınızdaki bilgiler beklemediğinizde değişmeye başlayabileceğinden, bilgileri ayarlayan kod ikinci veya üçüncü kez çalıştırıldığında kötü şeyler olabilir. Bu yüzden, tekrar ayarlamadan önce ThreadLocal bilgilerinin ayarlanmadığından emin olun.


2
Kodla başa çıkmak için hazırlanırsa, yeniden giriş sorunu olmaz. Girişte, değişkenin zaten ayarlanmış olup olmadığını not edin ve çıkışta önceki değerini (varsa) geri yükleyin veya kaldırın (değilse).
supercat

1
@ Jeff, Ahbap, bu sadece ThreadLocalkalıp değil, yazdığınız her kod için de geçerli . Bunu yaparsanız F(){ member=random(); F2(); write(member); }ve F2, üyeyi yeni bir değerle geçersiz kılarsa, açık bir write(member)şekilde random()edindiğiniz sayıyı yazmaz . Bu tam anlamıyla sadece sağduyu. Benzer şekilde, yaparsanız F(){ F(); }, sonsuz döngü ile gd şans! Bu her yerde doğrudur ve buna özgü değildir ThreadLocal.
Pacerier

@Jeff, kod örneği?
Pacerier

5

ne zaman?

Bir nesne iş parçacığı için güvenli olmadığında, ölçeklenebilirliği engelleyen senkronizasyon yerine, her iş parçacığına bir nesne verin ve iş parçacığının kapsama alanını (yani ThreadLocal) tutun. En sık kullanılan ancak iş parçacığı için güvenli olmayan nesnelerden biri veritabanı Bağlantısı ve JMSConnection'dır.

Nasıl ?

Bir örnek, Spring framework, ThreadLocal'ı bu bağlantı nesnelerini ThreadLocal değişkenlerinde tutarak sahnelerin arkasındaki işlemleri yönetmek için yoğun bir şekilde kullanıyor. Yüksek düzeyde, bir işlem başlatıldığında bağlantı kurulur (ve otomatik taahhüdü devre dışı bırakılır) ve ThreadLocal içinde tutulur. diğer db çağrılarında db ile iletişim kurmak için aynı bağlantıyı kullanır. Sonunda, bağlantı ThreadLocal'dan alır ve işlemi gerçekleştirir (veya geri alır) ve bağlantıyı serbest bırakır.

Log4j MDC korumak için ThreadLocal kullanır.


5

ThreadLocal farklı iş parçacıkları arasında paylaşılmaması gereken bir duruma sahip olmak istediğinizde, ancak her iş parçasından tüm ömrü boyunca erişilebilir olması gerektiğinde yararlıdır.

Örnek olarak, her isteğin farklı bir iş parçacığı tarafından sunulduğu bir web uygulaması düşünün. Her istek için, birden fazla kez bir veri parçasına ihtiyacınız olduğunu ve bunun hesaplanması oldukça pahalı olduğunu düşünün. Ancak, bu veriler gelen her istek için değişmiş olabilir, yani düz bir önbellek kullanamazsınız. Bu soruna basit ve hızlı bir çözüm, bu ThreadLocalverilere erişimi tutan bir değişkene sahip olmaktır, böylece her istek için yalnızca bir kez hesaplamanız gerekir. Tabii ki, bu sorun kullanılmadan da çözülebilir ThreadLocal, ancak illüstrasyon amacıyla tasarladım.

Bununla birlikte ThreadLocal, esasen küresel devletin bir formu olduğunu unutmayın . Sonuç olarak, birçok başka etkisi vardır ve sadece diğer tüm olası çözümler dikkate alındıktan sonra kullanılmalıdır.


ThreadLocals, global durumu yapmadığınız sürece global durum DEĞİLDİR. Pratik olarak her zaman erişilebilir yığın kaynağıdır. Bu yöntemi tüm değişkenlerinizde (geciktirilmiş) geçirerek yerel iş parçacığını benzetebilirsiniz; küresel devlet yapmaz ...
Enerccio

Kodun herhangi bir yerinden (aynı iş parçacığının bağlamında) erişilebilir olması anlamında bir küresel devlet şeklidir. Bu, bu değeri kimin okuduğu ve yazdığı hakkında akıl yürütmemek gibi tüm yansımaları beraberinde getirir. Bir fonksiyon parametresi kullanmak geciktirilmiş bir şey değildir, temiz arayüzleri teşvik etmek iyi bir uygulamadır. Ancak, ben sizin katılıyorum kod tabanı tüm derinliği boyunca bir parametre geçirmenin bir kod kokusu olduğunu kabul ediyorum. Ancak, birçok durumda ThreadLocal kullanımının sizi buraya getiren bir kod kokusu olduğuna da inanıyorum, bu yüzden yeniden düşünmeliyiz.
Dimos

Nesne durumunun bir parçası olabilir, küresel olarak kullanılması gerekmez. Tabii, biraz yükü alıyorsunuz, ancak birden fazla nesnenin iş parçacığı başına farklı durumu olması gerekiyorsa, ThreadLocalnesne alanı olarak kullanabilirsiniz ...
Enerccio

Haklısın. Muhtemelen ThreadLocal, dünyanın her yerinde erişilebilir olduğu çeşitli yanlış kullanımlara rastladım . Söylediğiniz gibi, yine de görünürlüğü sınırlayan bir sınıf alanı olarak kullanılabilir.
Dimos

4

Burada gerçekten yeni bir şey yok, ama bugün ThreadLocalbir web uygulamasında Bean Validation kullanırken çok yararlı olduğunu keşfettim . Doğrulama mesajları yerelleştirilmiştir, ancak varsayılan olarak kullanılır Locale.getDefault(). İle Validatorfarklı bir yapılandırma yapabilirsiniz MessageInterpolator, ancak Localene zaman arayacağınızı belirtmenin bir yolu yoktur validate. Bir statik yaratabilir Yani ThreadLocal<Locale>henüz daha iyi (veya başka şeylerle genel kapsayıcı olmak gerekebilir ThreadLocalve daha sonra özel olması MessageInterpolatoralmak Localebundan. Sonraki adım, bir yazmaktır ServletFilterbir oturum değerini kullanan veya request.getLocale()yerel ayarı ve mağaza almaya sizin de ThreadLocalreferans.


4

@Unknown (google) tarafından belirtildiği gibi, kullanımı, başvurulan değerin her bir iş parçacığında benzersiz olabileceği genel bir değişken tanımlamaktır. Kullanımları genellikle geçerli yürütme iş parçacığına bağlı bir tür bağlamsal bilginin depolanmasını gerektirir.

Java EE farkında olmayan (HttpSession veya EJB SessionContext'e erişimi olmayan) sınıflara kullanıcı kimliğini iletmek için bunu Java EE ortamında kullanırız. Bu şekilde, güvenlik tabanlı işlemler için kimlikten yararlanan kod, kimliğe her yöntem çağrısında açıkça geçmesine gerek kalmadan her yerden erişebilir.

Çoğu Java EE çağrısındaki istek / yanıt döngüsü, ThreadLocal öğesini ayarlamak ve ayarlamak için iyi tanımlanmış giriş ve çıkış noktaları sağladığından bu tür kullanımı kolaylaştırır.


4

ThreadLocal, değiştirilemeyen nesneye, senkronize edilmemiş yöntemdeki birden fazla iş parçacığıyla senkronize edilmesini sağlar; bu, değişebilir nesnenin yöntem içinde değiştirilemez hale getirilmesi anlamına gelir.

Bu, erişmeye çalışılan her iş parçacığı için yeni değişken nesne örneği verilerek gerçekleştirilir. Yani her iş parçacığının yerel kopyasıdır. Bu, yerel bir değişken gibi erişilecek bir yöntemde örnek değişkeni yapmaya yönelik bir hack'tir. Yerel değişkenin yalnızca iş parçacığı tarafından kullanılabildiğini bildiğinizden, bir fark; yöntem yerel değişkenleri, threadlocal ile paylaşılan mutable nesnenin onu temizleyene kadar birden çok yöntemde kullanılabileceği yerde yöntem yürütme bittiğinde iş parçacığı tarafından kullanılamaz.

Tanım olarak:

Java'daki ThreadLocal sınıfı, yalnızca aynı iş parçacığı tarafından okunabilen ve yazılabilen değişkenler oluşturmanıza olanak tanır. Böylece, iki iş parçacığı aynı kodu yürütüyor ve kod bir ThreadLocal değişken başvurusu olsa bile, iki iş parçacığı birbirlerinin ThreadLocal değişkenleri göremez.

ThreadJava ThreadLocalMapiçindeki her biri içerir .
Nerede

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

ThreadLocal'a ulaşmak:

Şimdi ThreadLocal için mutable nesnesini aşağıdaki gibi tutacak bir sarmalayıcı sınıfı oluşturun initialValue().
Şimdi bu paketleyicinin alıcısı ve ayarlayıcısı, değişken nesne yerine threadlocal örneği üzerinde çalışacaktır.

Threadlocal getter () yöntemi, threadlocalmap değerinde herhangi bir değer bulamazsa Thread; daha sonra thread'a göre özel kopyasını almak için initialValue () yöntemini çağırır.

class SimpleDateFormatInstancePerThread {

    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {

        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
                UUID id = UUID.randomUUID();
                @Override
                public String toString() {
                    return id.toString();
                };
            };
            System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
            return dateFormat;
        }
    };

    /*
     * Every time there is a call for DateFormat, ThreadLocal will return calling
     * Thread's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

    public static void cleanup() {
        dateFormatHolder.remove();
    }
}

Şimdi wrapper.getDateFormatter()arayacak threadlocal.get()ve bu (threadlocal) örneği currentThread.threadLocalMapiçerdiğini kontrol edecektir . Evet ise, karşılık gelen threadlocal örneği için değeri (SimpleDateFormat) döndürürseniz, haritayı initialValue () olan bu threadlocal örneğiyle ekleyin.


İşbu değişken sınıfta elde edilen iplik güvenliği ile; her iş parçacığı tarafından kendi mutable örneği ancak aynı ThreadLocal örneği ile çalışıyor. Tüm iş parçacığı anahtarla aynı ThreadLocal örneğini, ancak değer olarak farklı SimpleDateFormat örneğini paylaşacak anlamına gelir.

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java


3

İş parçacığı yerel değişkenleri, genellikle değiştirilebilir Değişkenler veya global değişkenlere dayalı tasarımlarda paylaşımı önlemek için kullanılır.

Bir Bağlantı Havuzu kullanmadığınızda her iş parçacığı için ayrı JDBC bağlantısı yapmak gibi senaryolarda kullanılabilir.

private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
      public Connection initialValue() {
           return DriverManager.getConnection(DB_URL);
          }
     };

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

GetConnection'ı çağırdığınızda, bu iş parçacığıyla ilişkili bir bağlantı döndürür. Aynı şey, tarih biçimi, iş parçacıkları arasında paylaşmak istemediğiniz işlem bağlamı gibi diğer özelliklerle de yapılabilir.

Aynı şekilde yerel değişkenleri de kullanabilirsiniz, ancak bu kaynak genellikle oluşturmada zaman alır, bu nedenle onlarla bir iş mantığı gerçekleştirdiğinizde bunları tekrar tekrar oluşturmak istemezsiniz. Ancak, ThreadLocal değerleri evre nesnesinde saklanır ve evre çöp toplanır toplanmaz bu değerler de kaybolur.

Bu bağlantı ThreadLocal kullanımını çok iyi açıklamaktadır.


2
Bu örnekte önemli bir sorun var: Bağlantının kapatılmasından kim sorumlu? Bu bağlantıyı initiali değeri olarak oluşturmak yerine, bağlantı tüketicisinin bağlantıyı açıkça oluşturmasına ve iş parçacığına ThreadLocal üzerinden bağlamasına izin verin. Bağlantının yaratıcısı da kapanıştan sorumludur. Bu örnekte bu açık değildir. Bağlantının oluşturulması ve bağlanması Transaction.begin () ve Transaction.end () gibi basit bir çerçevede de gizlenebilir.
Rick-Rainer Ludwig

2

ThreadLocal Java sınıfı sadece okumak ve aynı iş parçacığı tarafından yazılabilir değişkenler oluşturmak için olanak sağlar. Böylece, iki iş parçacığı aynı kodu yürütüyor ve kod bir ThreadLocal değişken başvurusu olsa bile, iki iş parçacığı birbirlerinin ThreadLocal değişkenleri göremez.

Daha fazla oku


2

SimpleDateFormat gibi bir sınıf yardımcısını çoklu iş parçacığı kodunda kullanmak için 3 senaryo vardır , bunlardan en iyisi ThreadLocal kullanmaktır

Senaryolar

1- Uygulamayı yavaşlatan kilit veya senkronizasyon mekanizması yardımıyla benzer paylaşım nesnesinin kullanılması

2- Bir yöntemin içinde yerel bir nesne olarak kullanma

Bu senaryoda, her biri bir yöntemi 1000 kez çağırmak için 4 iş parçacığımız varsa, o zaman 4000 SimpleDateFormat nesnesini oluşturduk ve GC'nin bunları silmesini bekliyoruz.

3- ThreadLocal Kullanımı

4 iş parçacığımız varsa ve her iş parçacığına bir SimpleDateFormat örneği
verdik, böylece 4 iş parçacığımız , SimpleDateFormat 4 nesnemiz var.

Kilit mekanizması ve nesne oluşturma ve imhaya gerek yoktur. (İyi zaman karmaşıklığı ve alan karmaşıklığı)


2

[Referans için] ThreadLocal, paylaşılan nesnenin güncelleme sorunlarını çözemez. Aynı iş parçacığında tüm işlemler tarafından paylaşılan bir staticThreadLocal nesnesi kullanılması önerilir. [Zorunlu] remove () yöntemi, özellikle iş parçacıklarının sıklıkla yeniden kullanıldığı iş parçacığı havuzları kullanılırken ThreadLocal değişkenleri tarafından uygulanmalıdır. Aksi takdirde, sonraki iş mantığını etkileyebilir ve bellek sızıntısı gibi beklenmedik sorunlara neden olabilir.


1

Önbellekleme, bazen aynı değeri çok zaman hesaplamanız gerekir, böylece son girdi kümesini bir yönteme kaydederek sonucu hızlandırabilirsiniz. Yerel İş Parçacığı Depolama'yı kullanarak kilitleme hakkında düşünmek zorunda kalmazsınız.


1

ThreadLocal, JVM tarafından yalnızca iş parçacıkları için yalıtılmış bir depolama alanı sağlamak için özel olarak sağlanan bir işlevdir. örneğin, kapsam kapsamındaki değişkenin değeri yalnızca belirli bir sınıf örneğine bağlıdır. her nesnenin tek değerleri vardır ve birbirlerini göremezler. yani ThreadLocal değişkenleri kavramıdır, onu yaratanlar hariç, diğer iş parçacığı nesne örnekleri anlamında iş parçacığı için yereldir. Buraya Bakın

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;


public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
    return threadId.get();
}

public static void main(String[] args) {

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

}
}

1

Threadlocal, sıfır maliyetle nesnelerin yeniden kullanılabilirliğini elde etmenin çok kolay bir yolunu sunar.

Her güncelleme bildirimi üzerinde birden çok iş parçacığı değiştirilebilir önbellek bir görüntü oluştururken bir durum vardı.

Her iş parçacığında bir Threadlocal kullandım ve sonra her iş parçacığının eski görüntüyü sıfırlaması ve ardından her güncelleme bildiriminde önbellekten yeniden güncellemesi gerekir.

Nesne havuzlarındaki olağan yeniden kullanılabilir nesnelerin kendileriyle ilişkili iş parçacığı güvenlik maliyeti vardır, ancak bu yaklaşımın hiçbiri yoktur.


0

ThreadLocal değişkeni hakkında fikir edinmek için bu küçük örneği deneyin:

public class Book implements Runnable {
    private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);

    private final String bookName; // It is also the thread's name
    private final List<String> words;


    public Book(String bookName, List<String> words) {
        this.bookName = bookName;
        this.words = Collections.unmodifiableList(words);
    }

    public void run() {
        WORDS.get().addAll(words);
        System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
        Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
        t1.start();
        t2.start();
    }
}


İlk olarak BookA iş parçacığı yapılırsa konsol çıktısı:
Sonuç BookA: 'wordA1, wordA2, wordA3'.
Sonuç KitabıB: 'wordB1, wordB2'.

İlk olarak BookB iş parçacığı yapılırsa konsol çıktısı:
Sonuç BookB: 'wordB1, wordB2'.
Sonuç KitabıA: 'wordA1, wordA2, wordA3'.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.