首页 \ 问答 \ 在Java中使用ThreadLocal的良好实践(Good Practice of using ThreadLocal in Java)

在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访问ThreadLocalSingletonRepo

public class App {
    public SingletonRepo singletonRepo;
    public static void main(String [] args) {
        singletonRepo = SingletonRepo.getInstance();
        singletonRepo.init();

        singletonRepo.singletonA.helperFunction();
    }
}

正如您在上面的上下文中看到的,为了访问ThreadLocal单例,我首先在SingletonRepo缓存它们。 当我需要使用ThreadLocal单例时,我从缓存引用中获取它。 我有以下问题 -

  1. 通过缓存副本访问ThreadLocal单例是不好的做法吗?
  2. 总是通过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 function 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() { ... }
}

... There are SingletonB, SingletonC, and so forth.

There is a singleton repository that caches the ThreadLocal singletons above. This class is also a ThreadLocal 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 through SingletonRepo

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 the SingletonRepo. When I need to use the ThreadLocal singleton, I get it from the cache reference. I have the following questions -

  1. Is it a bad practice to access the ThreadLocal singleton through a cache copy?
  2. Is it a better practice to always access the ThreadLocal singleton through SingletonA.getInstance() (Calling get() for the Singleton object)?

原文:https://stackoverflow.com/questions/27536194
更新时间:2021-05-08 14:05

最满意答案

我将假设该图既是无向的也是连通的。 话虽这么说,我认为你走的正确,但你还需要更多的东西。 首先,我强烈建议您将搜索状态和节点实现分开 - 换句话说,存储私有成员变量Node.visible以帮助您进行搜索并不是一个好主意。

您可以通过在搜索方法中保留一些额外状态来避免这种情况,并使用递归私有帮助器方法从公共traverse()方法的调用方隐藏该状态。 您需要在Node类中正确实现equalshashCode才能执行此操作。

此外 - 如果您想要创建一个具有不同节点的完全独立的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 ...
  • 从技术上讲,宽度优先搜索(BFS)本身并不能让您找到最短的路径,只是因为BFS不是寻找最短的路径:BFS描述了搜索图形的策略,但并不表示您必须搜索特别是什么 Dijkstra的算法适应BFS,让您找到单源最短路径。 为了检索从原点到节点的最短路径,您需要为图中每个节点保留两个项目:其当前最短距离,以及最短路径中的前一个节点。 最初所有的距离设置为无穷远,所有前辈都设置为空。 在您的示例中,将A的距离设置为零,然后继续进行BFS。 在每个步骤中,您检查是否可以提高后代的距离,即从原始距离到前辈的距离加上您正在 ...
  • (我假设这只是一些思想练习,甚至是一个技巧的家庭作业/面试问题,但是我想我可以想像一些奇怪的情况,你不能因为某些原因允许任何堆空间内存管理器?一些奇怪的运行时/操作系统问题?],而你仍然可以访问堆栈...) 宽度优先遍历传统上使用队列,而不是堆栈。 队列和堆栈的性质几乎相反,所以尝试使用调用堆栈(这是一个堆栈,因此名称)作为辅助存储(队列)几乎注定要失败,除非你在做一些愚蠢的可笑的电话堆栈,你不应该是。 同样,您尝试实现的任何非尾递归的性质本质上是在算法中添加一个堆栈。 这使得它不再在二叉树上首先搜索广泛, ...
  • 如果要遍历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.然后将这些节点添加到队 ...
  • 与共享数据的并行化和并发需要同步。 同步很昂贵 - 正如您可能正在目睹的那样。 ConcurrentQueue拥有自己的同步,这可能不适合您的情况。 超出CPU数量的并行化(这可能不是在这里发生,但它是相关的)会产生大量的上下文切换 - 这些都是昂贵的并且降低了并行运行的代码的生产率。 即,在问题中抛出更多线程的前提通常会产生相反的效果。 如果性能受到关注,我认为您可能希望查看不同的设计,可能是基于Actor , 消息传递或生产者/消费者,以避免共享数据并避免共享数据的同步。 Parallelisation ...
  • 你似乎错过了广度优先搜索的一些细微差别。 必须在级别中搜索树或图 这是对的。 虽然你不一定会追踪一组不同的关卡,但你自然会在完成后继续下一个关卡。 我们需要将路径存储在队列中(FIFO) 不是真的。 通常存储未来的节点以便在队列中进行探索,而不是整个路径。 如果需要维护路径是一个额外的问题。 然后找到最终项目的所有路径中的最短路径 在具有均匀边缘权重的图形中,这是不必要的。 你找到的第一条路径是最短的。 (请注意,对于BFS,要求均匀边缘权重) 目标节点的BFS的通用代码如下所示: q := new Que ...
  • 这里有一些优化,但你的代码看起来大多正确(我还没有完成自己的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 ...

相关文章

更多

最新问答

更多
  • 您如何使用git diff文件,并将其应用于同一存储库的副本的本地分支?(How do you take a git diff file, and apply it to a local branch that is a copy of the same repository?)
  • 将长浮点值剪切为2个小数点并复制到字符数组(Cut Long Float Value to 2 decimal points and copy to Character Array)
  • OctoberCMS侧边栏不呈现(OctoberCMS Sidebar not rendering)
  • 页面加载后对象是否有资格进行垃圾回收?(Are objects eligible for garbage collection after the page loads?)
  • codeigniter中的语言不能按预期工作(language in codeigniter doesn' t work as expected)
  • 在计算机拍照在哪里进入
  • 使用cin.get()从c ++中的输入流中丢弃不需要的字符(Using cin.get() to discard unwanted characters from the input stream in c++)
  • No for循环将在for循环中运行。(No for loop will run inside for loop. Testing for primes)
  • 单页应用程序:页面重新加载(Single Page Application: page reload)
  • 在循环中选择具有相似模式的列名称(Selecting Column Name With Similar Pattern in a Loop)
  • System.StackOverflow错误(System.StackOverflow error)
  • KnockoutJS未在嵌套模板上应用beforeRemove和afterAdd(KnockoutJS not applying beforeRemove and afterAdd on nested templates)
  • 散列包括方法和/或嵌套属性(Hash include methods and/or nested attributes)
  • android - 如何避免使用Samsung RFS文件系统延迟/冻结?(android - how to avoid lag/freezes with Samsung RFS filesystem?)
  • TensorFlow:基于索引列表创建新张量(TensorFlow: Create a new tensor based on list of indices)
  • 企业安全培训的各项内容
  • 错误:RPC失败;(error: RPC failed; curl transfer closed with outstanding read data remaining)
  • C#类名中允许哪些字符?(What characters are allowed in C# class name?)
  • NumPy:将int64值存储在np.array中并使用dtype float64并将其转换回整数是否安全?(NumPy: Is it safe to store an int64 value in an np.array with dtype float64 and later convert it back to integer?)
  • 注销后如何隐藏导航portlet?(How to hide navigation portlet after logout?)
  • 将多个行和可变行移动到列(moving multiple and variable rows to columns)
  • 提交表单时忽略基础href,而不使用Javascript(ignore base href when submitting form, without using Javascript)
  • 对setOnInfoWindowClickListener的意图(Intent on setOnInfoWindowClickListener)
  • Angular $资源不会改变方法(Angular $resource doesn't change method)
  • 在Angular 5中不是一个函数(is not a function in Angular 5)
  • 如何配置Composite C1以将.m和桌面作为同一站点提供服务(How to configure Composite C1 to serve .m and desktop as the same site)
  • 不适用:悬停在悬停时:在元素之前[复制](Don't apply :hover when hovering on :before element [duplicate])
  • 常见的python rpc和cli接口(Common python rpc and cli interface)
  • Mysql DB单个字段匹配多个其他字段(Mysql DB single field matching to multiple other fields)
  • 产品页面上的Magento Up出售对齐问题(Magento Up sell alignment issue on the products page)