最近又学到了不少新知识,感谢优锐课老师细致地讲解,这篇博客记录下本身所学所想,也和你们分享、了解有关Java中的并发问题和线程限制的更多信息。安全
在此文中,咱们将探讨线程限制,它的含义以及如何实现。所以,让咱们直接研究它。并发
大多数并发问题仅在咱们但愿在线程之间共享可变变量或可变状态时才会发生。若是在多个线程之间共享了可变状态,则全部线程都将可以读取和修改状态的值,从而致使错误或意外的行为。避免此问题的一种方法是根本不共享线程之间的数据。该技术被称为线程限制,是在咱们的应用程序中实现线程安全的最简单方法之一。学习
Java语言自己没有任何执行线程限制的方法。线程限制是经过设计程序的方式实现的,该程序不容许状态被多个线程使用,所以由实现来强制执行。线程限制有几种类型,以下所述。spa
临时线程限制描述了一种线程限制方式,由开发人员或负责该程序的一组开发人员共同负责,以确保对象的使用仅限于单个线程。这种方法很是脆弱,在大多数状况下应避免使用。线程
Ad-hoc线程限制下的一种特殊状况适用于volatile变量。只要确保只从单个线程写入volatile变量,就能够对共享的volatile变量执行读-修改-写操做。在这种状况下,你将修改限制在单个线程中,以防止出现竞争状况,而且易失变量的可见性保证可确保其余线程看到最新的值。设计
堆栈限制是将变量或对象限制在线程的堆栈中。这比临时线程约束要强得多,由于经过定义堆栈自己中变量的状态,它甚至进一步限制了对象的范围。例如,考虑如下代码:code
1 private long numberOfPeopleNamedJohn(List<Person> people) { 2 List<Person> localPeople = new ArrayList<>(); 3 localPeople.addAll(people); 4 return localPeople.stream().filter(person -> person.getFirstName().equals("John")).count(); 5 }
在上面的代码中,咱们传递了一我的员列表,但没有直接使用它。 相反,咱们建立本身的列表,该列表位于当前正在执行的线程的本地,并将全部人员添加到localPeople
中。因为咱们仅在numberOfPeopleNamedJohn
方法中定义列表,所以这使变量localPeople
堆栈受到限制,由于它存在于一个线程的堆栈中,所以没法被其余任何线程访问。这使localPeople
线程安全。咱们惟一须要注意的是,咱们不该该容许localPeople
逃脱此方法的范围,以使其保持在堆栈限制内。定义此变量时,也应记录或注释该变量,一般来讲,只有当前的开发人员才能避免使用此变量,未来,另外一位开发人员可能会搞砸了。对象
ThreadLocal
容许你将每一个线程的值与值持有对象相关联。它容许你为不一样的线程存储不一样的对象,并维护哪一个对象对应于哪一个线程。它具备set和get访问器方法,该方法为使用它的每一个线程维护该值的单独副本。get()
方法始终返回从当前正在执行的线程传递给set()
的最新值。让咱们看一个例子:blog
1 public class ThreadConfinementUsingThreadLocal { 2 public static void main(String[] args) { 3 ThreadLocal<String> stringHolder = new ThreadLocal<>(); 4 Runnable runnable1 = () -> { 5 stringHolder.set("Thread in runnable1"); 6 try { 7 Thread.sleep(5000); 8 System.out.println(stringHolder.get()); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 }; 13 Runnable runnable2 = () -> { 14 stringHolder.set("Thread in runnable2"); 15 try { 16 Thread.sleep(2000); 17 stringHolder.set("string in runnable2 changed"); 18 Thread.sleep(2000); 19 System.out.println(stringHolder.get()); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 }; 24 Runnable runnable3 = () -> { 25 stringHolder.set("Thread in runnable3"); 26 try { 27 Thread.sleep(5000); 28 System.out.println(stringHolder.get()); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 }; 33 Thread thread1 = new Thread(runnable1); 34 Thread thread2 = new Thread(runnable2); 35 Thread thread3 = new Thread(runnable3); 36 thread1.start(); 37 thread2.start(); 38 thread3.start(); 39 } 40 }
在上面的示例中,咱们使用相同的ThreadLocal
对象stringHolder
执行了三个线程。如你在这里看到的,首先,咱们在stringHolder
对象的每一个线程中设置一个字符串,使其包含三个字符串。而后,通过一些暂停后,咱们仅从第二个线程更改了该值。下面是程序的输出:开发
1 string in runnable2 changed 2 Thread in runnable1 3 Thread in runnable3
如你在上面的输出中看到的,线程2的字符串已更改,但线程1和线程3的字符串未受影响。若是在从ThreadLocal
获取特定线程上的值以前未设置任何值,则它将返回null
。终止线程后,ThreadLocal
中特定于线程的对象将准备进行垃圾回收。
这就是关于线程限制的所有内容。我但愿这篇博客能对你有所帮助,而且你必须学习新的知识。
谢谢,祝你愉快!