在Java中使用ThreadLocal的良好实践(Good Practice of using ThreadLocal in Java)
我有一个关于如何使用
ThreadLocal
。背景和情况
有几个单独的对象使用
ThreadLocal
为每个线程创建一个副本。 这个单例对象有一个函数foo()
。public class SingletonA { protected static ThreadLocal<SingletonA> singleton = new ThreadLocal<SingletonA>() { @Override protected SingletonA initialValue() { return new SingletonA(); } }; private SingletonA() { ... } public static SingletonA getInstance() { return singleton.get(); } public static void remove() { singleton.remove(); } public static void foo() { ... } }
...有SingletonB,SingletonC等等。
有一个单例存储库可以缓存上面的
ThreadLocal
单例。 这个类也是一个ThreadLocal
单例 -public class SingletonRepo { protected static ThreadLocal<SingletonRepo> singleton = new ThreadLocal<SingletonRepo>() { @Override protected SingletonRepo initialValue() { return new SingletonRepo(); } }; private SingletonRepo() { ... } public static SingletonRepo getInstance() { return singleton.get(); } public static void remove() { singleton.remove(); } public SingletonA singletonA; // same thing for singletonB, singletonC, ... public static init() { // Caching the ThreadLocal singleton singletonA = SingletonA.getInstance(); // Same thing for singletonB, singletonC ... } }
这是我目前通过
SingletonRepo
访问ThreadLocal
单SingletonRepo
public class App { public SingletonRepo singletonRepo; public static void main(String [] args) { singletonRepo = SingletonRepo.getInstance(); singletonRepo.init(); singletonRepo.singletonA.helperFunction(); } }
题
正如您在上面的上下文中看到的,为了访问
ThreadLocal
单例,我首先在SingletonRepo
缓存它们。 当我需要使用ThreadLocal
单例时,我从缓存引用中获取它。 我有以下问题 -
- 通过缓存副本访问
ThreadLocal
单例是不好的做法吗?- 总是通过
SingletonA.getInstance()
(为Singleton对象调用get()
访问ThreadLocal
单例是一种更好的做法吗?I have a question regarding to how I should use
ThreadLocal
.Context and Situation
There are several singleton objects that use
ThreadLocal
to create one copy per thread. This singleton object has a functionfoo()
.public class SingletonA { protected static ThreadLocal<SingletonA> singleton = new ThreadLocal<SingletonA>() { @Override protected SingletonA initialValue() { return new SingletonA(); } }; private SingletonA() { ... } public static SingletonA getInstance() { return singleton.get(); } public static void remove() { singleton.remove(); } public static void foo() { ... } }
... There are SingletonB, SingletonC, and so forth.
There is a singleton repository that caches the
ThreadLocal
singletons above. This class is also aThreadLocal
singleton -public class SingletonRepo { protected static ThreadLocal<SingletonRepo> singleton = new ThreadLocal<SingletonRepo>() { @Override protected SingletonRepo initialValue() { return new SingletonRepo(); } }; private SingletonRepo() { ... } public static SingletonRepo getInstance() { return singleton.get(); } public static void remove() { singleton.remove(); } public SingletonA singletonA; // same thing for singletonB, singletonC, ... public static init() { // Caching the ThreadLocal singleton singletonA = SingletonA.getInstance(); // Same thing for singletonB, singletonC ... } }
This is currently how I access the
ThreadLocal
singletons throughSingletonRepo
public class App { public SingletonRepo singletonRepo; public static void main(String [] args) { singletonRepo = SingletonRepo.getInstance(); singletonRepo.init(); singletonRepo.singletonA.helperFunction(); } }
Question
As you have seen in the context above, in order to access the
ThreadLocal
singletons, I first cached them in theSingletonRepo
. When I need to use theThreadLocal
singleton, I get it from the cache reference. I have the following questions -
- Is it a bad practice to access the
ThreadLocal
singleton through a cache copy?- Is it a better practice to always access the
ThreadLocal
singleton throughSingletonA.getInstance()
(Callingget()
for the Singleton object)?
原文:https://stackoverflow.com/questions/27536194
最满意答案
我将假设该图既是无向的也是连通的。 话虽这么说,我认为你走的正确,但你还需要更多的东西。 首先,我强烈建议您将搜索状态和节点实现分开 - 换句话说,存储私有成员变量
Node.visible
以帮助您进行搜索并不是一个好主意。您可以通过在搜索方法中保留一些额外状态来避免这种情况,并使用递归私有帮助器方法从公共
traverse()
方法的调用方隐藏该状态。 您需要在Node
类中正确实现equals
和hashCode
才能执行此操作。此外 - 如果您想要创建一个具有不同节点的完全独立的
Tree
,您将希望基本上在图中创建每个Node
新的空实例,并首先使用其对应的数据填充它们,然后使用克隆的节点构建树。 也就是说,这里有一些代码可以帮助您(我没有对此进行测试,但它应该让您知道该怎么做):/** * This facade method traverses just the root of the new tree. All recursion is * passed to the "traverseHelper(3)" recursive method. */ public Tree<T> traverse(Graph<T> g){ if(g == null || g.mainNode == null) return null; Node<T> node = g.mainNode; Node<T> clone = new Node<T>(node.data); //this is the root of our new Tree Set<Node<T>> searched = new HashSet<Node<T>>(); //accumulates searched nodes searched.add(node); traverseHelper(node,clone,searched); return new Tree<T>(clone); } /** * Recursively performs BFS on all the children of the specified node and its * corresponding cloned instance. * * Assumes that "node" has been added to "searched" and that * "searched.contains(node)" AND "searched.contains(clone)" will return true by * the time this method is called. */ private void traverseHelper(Node<T> node, Node<T> clone, Set<Node<T>> searched){ if(node.children == null) return; Map<Node<T>,Node<T>> toRecurseOn = new HashMap<Node<T>,Node<T>>(); //This is the Breadth-First part - builds the next leaves in the tree: for(Node<T> child : node.children){ if(child == null || searched.contains(child)) continue; Node<T> childClone = new Node<T>(child.data); //create leaf in the tree clone.children.add(childClone); //builds the current level in the tree childClone.children.add(clone); //maintains undirected-ness of the tree toRecurseOn.put(child,childClone); //lets us BFS later } //This is the Search part - builds the subtrees: Iterator<Node<T>> i = toRecurseOn.keySet().iterator(); while(i.hasNext()){ Node<T> child = i.next(); Node<T> childClone = toRecurseOn.get(child); i.remove(); //Saves a little memory throughout the recursion traverseHelper(child,childClone,searched); } }
I found a simple answer to my question. Instead of building a tree, I remove edges which lead to already visited nodes (this information we get for free as part of the BFS algorithm). Below is my implementation (it might be modified if one doesn't want to destroy the initial graph structure).
public static Tree BFS(Node node){ Queue queue= new Queue(); node.visited= true; queue.enqueue(node); while (!queue.isEmpty()){ Node r= queue.dequeue(); for (int i= 0; i < r.childen.size(); i++){ Node s= (Node)r.childen.get(i); if (s.visited == false){ s.visited= true; queue.enqueue(s); } else{ //Remove edge here r.childen.remove(i); i--; } } } Tree tree= new Tree(node); return tree; }
EDIT. The following is an implementation which doesn't destroy the initial graph structure by keeping a separate queue.
public static Tree BFS(Graph G, Node node){ Queue queue= new Queue(); Queue treeQueue= new Queue(); ArrayList<Node> tempV= new ArrayList<Node>(); tempV.add(node); queue.enqueue(node); Node root= new Node(node.data); treeQueue.enqueue(root); while (!queue.isEmpty()){ Node r= queue.dequeue(); Node t= treeQueue.dequeue(); for (int i= 0; i < r.childen.size(); i++){ Node s= (Node)r.childen.get(i); if (tempV.indexOf(s) < 0){ tempV.add(s); Node child= new Node(s.data); t.childen.add(child); queue.enqueue(s); treeQueue.enqueue(child); } } } Tree tree= new Tree(root); return tree; }
相关问答
更多-
是的,你必须使用一个队列来保存你已经检查但尚未递归的节点。 从队列中的根节点开始,然后重复[弹出一个节点,并为其每个子节点执行您需要的任何操作( res += [tree.key] )并将其推res += [tree.key]队列中]。 Yes, you'll have to use a queue to hold nodes which you've checked but have yet to recurse into. Start with the root node in the queue, t ...
-
寻找最短路径时,广度优先搜索如何工作?(How does a Breadth-First Search work when looking for Shortest Path?)[2023-12-19]
从技术上讲,宽度优先搜索(BFS)本身并不能让您找到最短的路径,只是因为BFS不是寻找最短的路径:BFS描述了搜索图形的策略,但并不表示您必须搜索特别是什么 Dijkstra的算法适应BFS,让您找到单源最短路径。 为了检索从原点到节点的最短路径,您需要为图中每个节点保留两个项目:其当前最短距离,以及最短路径中的前一个节点。 最初所有的距离设置为无穷远,所有前辈都设置为空。 在您的示例中,将A的距离设置为零,然后继续进行BFS。 在每个步骤中,您检查是否可以提高后代的距离,即从原始距离到前辈的距离加上您正在 ... -
(我假设这只是一些思想练习,甚至是一个技巧的家庭作业/面试问题,但是我想我可以想像一些奇怪的情况,你不能因为某些原因允许任何堆空间内存管理器?一些奇怪的运行时/操作系统问题?],而你仍然可以访问堆栈...) 宽度优先遍历传统上使用队列,而不是堆栈。 队列和堆栈的性质几乎相反,所以尝试使用调用堆栈(这是一个堆栈,因此名称)作为辅助存储(队列)几乎注定要失败,除非你在做一些愚蠢的可笑的电话堆栈,你不应该是。 同样,您尝试实现的任何非尾递归的性质本质上是在算法中添加一个堆栈。 这使得它不再在二叉树上首先搜索广泛, ...
-
广度优先搜索输入(Breadth First Search input)[2023-04-14]
如果要遍历BFS,则需要使用队列结构:例如: 取第一个节点( 0 )。 将( 0 )节点放入队列。 重复此过程,直到队列不为空。 出队( 0 ) 如果尚未访问,请检查节点( 0 )的子节点 根据您的输入( “0 1” )边缘表示其唯一的子节点为1,因此将1放入队列中。 打印(即为0 )。 结束循环结束 换一种说法。 下一次迭代将是1有两个孩子,即1(2)和1(4)将所有孩子放入队列并出列1并打印。 下一次迭代将出列2 ,有三个孩子,即2(6) , 2(3)和2(7)将所有孩子放入队列并打印2 。 下一次迭代 ... -
我将假设该图既是无向的也是连通的。 话虽这么说,我认为你走的正确,但你还需要更多的东西。 首先,我强烈建议您将搜索状态和节点实现分开 - 换句话说,存储私有成员变量Node.visible以帮助您进行搜索并不是一个好主意。 您可以通过在搜索方法中保留一些额外状态来避免这种情况,并使用递归私有帮助器方法从公共traverse()方法的调用方隐藏该状态。 您需要在Node类中正确实现equals和hashCode才能执行此操作。 此外 - 如果您想要创建一个具有不同节点的完全独立的Tree ,您将希望基本上在图 ...
-
假设你有4个不同的项目。 然后你正在搜索的图是一个像这样的超立方体( Yury Chebiryak的图片 ): 节点处的二进制数是所有可能的背包,其中n表示第n个零,意味着项n不在背包中,1表示它是,因此例如0000表示空背包,1001表示背包包含第一个项目和第四个项目,依此类推。 在每一步中,您从队列中移除当前节点,如果它不是目标,则通过查找与当前节点相差1个尚未访问过的所有背包来构造相邻节点。 因此,例如,如果当前节点是1001,则将构造节点0001,1101,1011和1000.然后将这些节点添加到队 ...
-
并行广度优先搜索(Parallel breadth first search)[2023-01-07]
与共享数据的并行化和并发需要同步。 同步很昂贵 - 正如您可能正在目睹的那样。 ConcurrentQueue拥有自己的同步,这可能不适合您的情况。 超出CPU数量的并行化(这可能不是在这里发生,但它是相关的)会产生大量的上下文切换 - 这些都是昂贵的并且降低了并行运行的代码的生产率。 即,在问题中抛出更多线程的前提通常会产生相反的效果。 如果性能受到关注,我认为您可能希望查看不同的设计,可能是基于Actor , 消息传递或生产者/消费者,以避免共享数据并避免共享数据的同步。 Parallelisation ... -
你似乎错过了广度优先搜索的一些细微差别。 必须在级别中搜索树或图 这是对的。 虽然你不一定会追踪一组不同的关卡,但你自然会在完成后继续下一个关卡。 我们需要将路径存储在队列中(FIFO) 不是真的。 通常存储未来的节点以便在队列中进行探索,而不是整个路径。 如果需要维护路径是一个额外的问题。 然后找到最终项目的所有路径中的最短路径 在具有均匀边缘权重的图形中,这是不必要的。 你找到的第一条路径是最短的。 (请注意,对于BFS,要求均匀边缘权重) 目标节点的BFS的通用代码如下所示: q := new Que ...
-
广度优先搜索c(breadth first search in c)[2022-11-05]
这里有一些优化,但你的代码看起来大多正确(我还没有完成自己的BFS实现)。 这是考虑什么: 你不必要地检查被访问的== 0多次。 您已将节点设置为已访问,无需检查是否等于零。 for(u=1;uMaxSize;u++){ if(mygraph->table[u].visited==0){ /* Silly */ 考虑是否已经分配了所有指针所指向的每个内存块。 这可能是您的分段错误的根源。 该死的,该死的。 A few optimizations here, but your ... -
你真的应该使用队列,因为它更容易实现。 此外,队列允许多台计算机一起工作(一个队列站点,而另一个队列从队列中弹出站点以进行遍历)。 我认为这样做的唯一另一种方法是使用递归(更加困难,并且只使用或多或少的内存)。 You really should be using a queue, as its easier to implement. Also, a queue allows for multiple machines to work together (one queues site while ano ...