精华内容
下载资源
问答
  • jvm的垃圾回收方式采用的是复制算法回收的有哪些? 新生代串行收集器 新生代并行回收收集器 两个最基本的java回收算法:复制算法和标记清理算法。 复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B...

    jvm的垃圾回收方式采用的是复制算法回收的有哪些?

    1. 新生代串行收集器
    2. 新生代并行回收收集器

    两个最基本的java回收算法:复制算法和标记清理算法。

    复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。

    标记清理:一块区域,标记可达对象(可达性分析),然后回收不可达对象,会出现碎片,那么引出

    标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象。

    两个概念:新生代和年老代

    新生代:初始对象,生命周期短的;

    年老代:长时间存在的对象。

    整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收。

    新生代基本采用复制算法,年代采用标记整理算法,CMS采用标记清理。

     

    来源:牛客网的评论

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 现实生活中的分治 分治的思想,顾名思义分而治之。就像古代的王想治理好天下,单单靠他一个人是不够的,还需要大臣的辅助,把天下划分为一块块区域,分派的下面的人负责,然后下面...分治在算法很多应用,类似...

     

    现实生活中的分治

    分治的思想,顾名思义分而治之。就像古代的王想治理好天下,单单靠他一个人是不够的,还需要大臣的辅助,把天下划分为一块块区域,分派的下面的人负责,然后下面的人又分派给他们的属下负责,层层传递。

    这就是分治,也就是把一个复杂的问题分解成相似的子问题,然后子问题再分子问题,直到问题分的很简单不必再划分了。然后层层返回问题的结果,最终上报给王!

    分治在算法上有很多应用,类似大数据的MapReduce,归并算法、快速排序算法等。
    在JUC中也提供了一个叫Fork/Join的并行计算框架用来处理分治的情况,它类似于单机版的 MapReduce

     

    Fork/Join

    分 治分为两个阶段,第一个阶段分解任务,把任务分解为一个个小任务直至小任务可以简单的计算返回结果。

    第二阶段合并结果,把每个小任务的结果合并返回得到最终结果。而Fork就是分解任务,Join就是合并结果。

    Fork/Join框架主要包含两部分:ForkJoinPool、ForkJoinTask

     

    ForkJoinPool

    就是治理分治任务的线程池。它和在之前的文章提到ThreadPoolExecutor线程池,共同点都是消费者-生产者模式的实现,但是有一些不同。ThreadPoolExecutor的线程池是只有一个任务队列的,而ForkJoinPool有多个任务队列。通过ForkJoinPoolinvokesubmitexecute提交任务的时候会根据一定规则分配给不同的任务队列,并且任务队列的双端队列。

     

    execute 异步,无返回结果
    invoke 同步,有返回结果 (会阻塞)
    submit 异步,有返回结果 (Future<T>)

    为啥要双端队列呢?因为ForkJoinPool有一个机制,当某个工作线程对应消费的任务队列空闲的时候它会去别的忙的任务队列的尾部分担(stealing)任务过来执行(好伙伴啊)。然后那个忙的任务队列还是头部出任务给它对应的工作线程消费。这样双端就井然有序,不会有任务争抢的情况。

     

    ForkJoinTask

    这就是分治任务啦,就等同于我们平日用的Runnable。它是一个抽象类,核心方法就是forkjoinfork方法用来异步执行一个子任务,join方法会阻塞当前线程等待子任务返回。

    ForkJoinTask有两个子类分别是RecursiveActionRecursiveTask。这两个子类也是抽象类,都是通过递归来执行分治任务。每个子类都有抽象方法compute差别就在于RecursiveAction的没有返回值而RecursiveTask有返回值。

     

    简单应用 

    这样分治思想用递归实现的经典案例就是斐波那契数列了。

    斐波那契数列:1、1、2、3、5、8、13、21、34、……
    公式 :F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)

        public static void main(String[] args) {
            ForkJoinPool forkJoinPool = new ForkJoinPool(4); // 最大并发数4
            Fibonacci fibonacci = new Fibonacci(20);
            long startTime = System.currentTimeMillis();
            Integer result = forkJoinPool.invoke(fibonacci);
            long endTime = System.currentTimeMillis();
            System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
        }
        //以下为官方API文档示例
        static  class Fibonacci extends RecursiveTask<Integer> {
            final int n;
            Fibonacci(int n) {
                this.n = n;
            }
            @Override
            protected Integer compute() {
                if (n <= 1) {
                    return n;
                }
                Fibonacci f1 = new Fibonacci(n - 1);
                f1.fork(); 
                Fibonacci f2 = new Fibonacci(n - 2);
                return f2.compute() + f1.join(); 
            }
        }
    

    当然你也可以两个任务都fork,要注意的是两个任务都fork的情况,必须按照f1.fork(),f2.fork(), f2.join(),f1.join()这样的顺序,不然有性能问题。JDK官方文档有说明,有兴趣的可以去研究下。

    我是推荐使用invokeAll方法

                Fibonacci f1 = new Fibonacci(n - 1);
                Fibonacci f2 = new Fibonacci(n - 2);
                invokeAll(f1,f2);
                return f2.join() + f1.join();
    

    Method invokeAll (available in multiple versions) performs the most common form of parallel invocation: forking a set of tasks and joining them all.

    官方API文档是这样写到的,所以平日用invokeAll就好了。invokeAll会把传入的任务的第一个交给当前线程来执行,其他的任务都fork加入工作队列,这样等于利用当前线程也执行任务了。以下为invokeAll源码

        public static void invokeAll(ForkJoinTask<?>... tasks) {
            Throwable ex = null;
            int last = tasks.length - 1;
            for (int i = last; i >= 0; --i) {
                ForkJoinTask<?> t = tasks[i];
                if (t == null) {
                    if (ex == null)
                        ex = new NullPointerException();
                }
                else if (i != 0)   //除了第一个都fork
                    t.fork();
                else if (t.doInvoke() < NORMAL && ex == null)  //留一个自己执行
                    ex = t.getException();
            }
            for (int i = 1; i <= last; ++i) {
                ForkJoinTask<?> t = tasks[i];
                if (t != null) {
                    if (ex != null)
                        t.cancel(false);
                    else if (t.doJoin() < NORMAL)
                        ex = t.getException();
                }
            }
            if (ex != null)
                rethrow(ex);
        }
    

    结语

    Fork/Join就是利用了分治的思想组建的框架,平日里很多场景都能利用到分治思想。框架的核心ForkJoinPool,因为含有任务队列和窃取的特性所以能更好的利用资源。

    展开全文
  • [译]C++17,标准库有哪些新变化?

    千次阅读 2018-08-18 14:38:21
    C++17 许多新的标准库变化,简单起见,这篇文章只介绍了以下内容:std::string_view,标准模板库中新添加的并行算法,新的文件系统库,以及3个新的数据类型:std::any, std::optional, 和 std::variant.让我们来了解一下...

    看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第二篇~

    C++17 有许多新的标准库变化,简单起见,这篇文章只介绍了以下内容:std::string_view,标准模板库中新添加的并行算法,新的文件系统库,以及3个新的数据类型:std::any, std::optional, 和 std::variant.让我们来了解一下其中的细节.

    首先看看 std::string_view.

    std::string_view

    std::string_view 代表一个字符串的非所有权引用(即不负责管理引用字符串的生命周期),他表示的是一个字符序列(可以是 C++ 中的 string 或者 C风格的字符串)的"视图".C++17 中为不同的字符类型提供了四种 string_view :

    std::string_view      std::basic_string_view<char>
    std::wstring_view     std::basic_string_view<wchar_t>
    std::u16string_view   std::basic_string_view<char16_t>
    std::u32string_view   std::basic_string_view<char32_t>
    

    你也许会有疑问:为什么我们需要 std::string_view 呢(Google, LLVM 和 Bloomberg 甚至实现了自己的 string_view 版本)? .答案其实很简单: 因为 std::string_view 可以高效的进行复制! 而高效的原因在于 std::string_view 的创建成本很低, 仅需要两个数据:字符序列的指针以及字符序列的长度. std::string_view 以及他的3个"兄弟"类型(指 std::wstring_view, std::u16string_view 和 std::u32string_view)提供了和 std::string 一致的字符串读取接口,另外也新增了两个方法:remove_prefix 和 remove_suffix.(译注:译文对作者的原始示例代码做了些许调整,原始代码请参看原文)

    #include <iostream>
    #include <string>
    #include <string_view>
    
    int main()
    {
        std::string str = "   A lot of space";
    	std::string_view strView = str;
    	strView.remove_prefix(std::min(strView.find_first_not_of(" "), strView.size()));
    	std::cout << "str      :  " << str << std::endl
    		      << "strView  : " << strView << std::endl;
    
    	std::cout << std::endl;
    
    	char arr[] = { 'A', ' ', 'l', 'o', 't', ' ', 'o', 'f', ' ', 's', 'p', 'a', 'c', 'e', '\0', '\0', '\0' };
    	std::string_view strView2(arr, sizeof arr);
    	auto trimPos = strView2.find('\0');
    	if (trimPos != strView2.npos) strView2.remove_suffix(strView2.size() - trimPos);
    	std::cout << "arr     : " << arr << ", size=" << sizeof arr << std::endl
    		      << "strView2: " << strView2 << ", size=" << strView2.size() << std::endl;
    		      
        return 0;
    }
    

    示例代码应该没有什么令人惊讶的地方:第8行代码创建了引用 C++ string 的 std::string_view(strView变量), 而第16行代码中创建的 std::string_view(strView2变量) 引用的则是字符数组.在第9行代码中,我们通过组合使用 remove_prefix 和 find_first_not_of 方法移除了 strView 的所有前导空格符,同样在第21行代码中, 借助 remove_suffix 方法, strView2 的所有尾随"\0"符号也被移除了.

    image

    下面介绍的内容你应该更加熟悉.

    Parallel algorithm of the Standard Template Library(标准模板库中的并行算法)

    关于STL中并行算法的介绍比较简短: 标准库中的 69 个算法会提供串行,并行以及矢量并行这3个版本.另外,新标准(C++17)也引入了 8 个(此处有误,见后面译注)新算法.下面的示意图标明了所有相关算法的名字,其中新引入的算法标为红色,非新引入的算法则为黑色.(译注:图中红色标明的 for_each 并非是新算法,所以实际C++17新引入的算法只有7个)

    image

    算法的介绍这么多了,关于这个话题的进一步细节你可以看看我写的另外一篇文章.

    相比较算法,文件系统库应该属于全新的内容.

    The filesystem library

    新的文件系统库基于 boost::filesystem,并且文件系统库中的一些组件是可选的,这意味着并不是每一个文件系统库实现都支持标准定义的所有功能.例如, FAT-32 文件系统便不支持符号链接.

    文件系统库基于3个概念: 文件(file), 文件名(file name) 以及 文件路径(path). file 可以是目录,硬链接,符号链接或者常规文件.path 则可以是绝对路径或者相对路径.

    filesystem 提供了强大的读取及操作文件的接口,
    你可以在cppreference.com上获取到更多细节,下面的示例代码可以给你一些初步印象:

    #include <fstream>
    #include <iostream>
    #include <string>
    #include <filesystem>
    namespace fs = std::filesystem;
    
    int main()
    {
    	std::cout << "Current path: " << fs::current_path() << std::endl;
    
    	std::string dir = "sandbox/a/b";
    	fs::create_directories(dir);
    
    	std::ofstream("sandbox/file1.txt");
    	fs::path symPath = fs::current_path() /= "sandbox";
    	symPath /= "syma";
    	fs::create_symlink("a", symPath);
    
    	std::cout << "fs::is_directory(dir): " << fs::is_directory(dir) << std::endl;
    	std::cout << "fs::exists(symPath): " << fs::exists(symPath) << std::endl;
    	std::cout << "fs::symlink(symPath): " << fs::is_symlink(symPath) << std::endl;
    
    	for (auto& p : fs::recursive_directory_iterator("sandbox"))
    	{
    		std::cout << p.path() << std::endl;
    	}
    	fs::remove_all("sandbox");
    	
    	return 0;
    }
    

    第9行代码中的 fs::current_path() 方法可以返回当前工作目录.你也可以使用
    fs::create_directories 方法(代码第12行)创建层级目录. fs::path 重载了 /= 操作符,借助他我们可以方便的创建符号链接(第17行),你也可以使用文件库提供的接口来检查文件的各项属性(19行到21行).23行的 fs::recursive_directory_iterator 功能非常强大,你可以使用他来递归的遍历某个目录,当然,你也可以使用 fs::remove_all 来删除某个目录(第27行).

    代码的输出如下:

    image

    新加入的数据类型 std::any, std::optional, 和 std::variant 都基于 boost程序库.

    std::any

    如果你想创建一个可以包含任意类型元素的容器,那么你就应该使用std::any,不过确切来说的话,std::any 并不是对任意类型都提供存储支持,只有可复制的类型才能存放入 std::any.下面列一段简短的示例代码:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <any>
    
    struct MyClass {};
    
    int main() 
    {
    	std::cout << std::boolalpha;
    
    	std::vector<std::any> anyVec { true, 2017, std::string("test"), 3.14, MyClass() };
    	std::cout << "std::any_cast<bool>anyVec[0]: " << std::any_cast<bool>(anyVec[0]) << std::endl; // true
    	int myInt = std::any_cast<int>(anyVec[1]);
    	std::cout << "myInt: " << myInt << std::endl;                                    // 2017
    
    	std::cout << std::endl;
    	std::cout << "anyVec[0].type().name(): " << anyVec[0].type().name() << std::endl;             // b
    	std::cout << "anyVec[1].type().name(): " << anyVec[1].type().name() << std::endl;             // i
    	
    	return 0;
    }
    

    示例代码的输出已经在注释中写明了.代码第 12 行创建了一个 std::vectorstd::any,你必须使用 std::any_cast 来获取其中的元素,如果你向 std::any_cast 传递了错误的数据类型,那么就会产生转型异常(std::bad_any_cast).你可以去cppreferenc.com获取更多相关细节或者等待我之后的更多文章介绍.

    std::any 可以存储任意类型(译注:这里的任意类型指可复制的类型)的数据,而 std::optional 则支持存储数据或者不存储数据.

    std::optional

    std::optional 这里就不做介绍了,在之前我写的 Monads in C++ 中就已经介绍了这个单子(指std::optional).(译注: 单子(Monad) 是函数式编程编程的概念,简单理解的话可以看看这里)

    我们再来看下 std::variant.

    std::variant

    std::variant 是一个类型安全的联合体(union).一个 std::variant 实例存储着其指定类型中某一类型的数据,并且 std::variant 的指定类型不能是引用类型,数组类型以及 void 类型,不过 std::variant 可以指定重复的数据类型(譬如指定多个int). std::variant 默认会以其第一个指定类型进行初始化,这就要求该类型(第一个指定类型)必须支持默认构造函数,下面是一个基于cppreference.com的代码示例:

    #include <variant>
    #include <string>
    
    int main() 
    {
    	std::variant<int, float> v, w;
    	v = 12;                              // v contains int
    	int i = std::get<int>(v);
    	w = std::get<int>(v);
    	w = std::get<0>(v);                  // same effect as the previous line
    	w = v;                               // same effect as the previous line
    
    	//std::get<double>(v);               // error: no double in [int, float]
    	//std::get<3>(v);                    // error: valid index values are 0 and 1
    
    	try 
    	{
    		float f = std::get<float>(w);    // w contains int, not float: will throw
    	}
    	catch (std::bad_variant_access&) 
    	{
    	}
    
    	std::variant<std::string> v2("abc"); // converting constructors work when unambiguous
    	v2 = "def";                          // converting assignment also works when unambiguous
    	
    	return 0;
    }
    

    第6行代码中我创建了两个 std::variants 实例 v 和 w,他们的指定类型为 int 和 float,并且初始值为0(第一个指定类型 int 的默认初始值).第7行代码中我将整型12赋值给了v,后面我们可以通过 std::get(v) 来获取该值.第9行到11行代码中,我使用了3种方式将v中的数值赋值给了w. std::variants 的使用自然也有一定的规则限制,你可以使用指定某一类型(第9行代码)或者指定某一索引(第10行代码)的方式来获取 std::variants 的数值,但是指定的类型必须是唯一的,指定的索引也必须是有效的.第18行代码中我尝试从 w 中获取 float 类型数据,但是由于 w 目前包含 int 类型数据,所以会产生 std::bad_variant_access 异常.另外值得一提的是, std::variants 的构造函数以及赋值函数支持类型转换(要求转换没有歧义),这也是第24行及25行代码中我可以使用C风格的字符串直接初始化(或者赋值) std::variantstd::string 的原因.

    展开全文
  • 在单核CPU上使用并行算法的效率通常低于原来的串行算法。而并行计算之所以能够提高系统的性能,并不是因为它“工作量小”,而是因为它能够更合理地调度任务,充分利用各个CPU资源。因此,合理的组合可以最大限度地...

         锁是最常见的同步方法之一。在高并发环境中,激烈的锁争用会导致程序性能下降,因此有与锁相关的一些性能问题,以及避免死锁、降低锁粒度和锁分离等一些注意事项都是有必要讨论和研究清楚的。

    在单核CPU上使用并行算法的效率通常低于原来的串行算法。而并行计算之所以能够提高系统的性能,并不是因为它“工作量小”,而是因为它能够更合理地调度任务,充分利用各个CPU资源。因此,合理的组合可以最大限度地提高多核CPU的性能。

    1. 提高锁性能的几点方法和建议

    如果锁资源的竞争不加以合理的控制必然会导致程序的整体性能下降。为了将这种副作用降到最低,我总结了一些有关使用锁的建议,希望对大家有所帮助。

    1.1 减少锁持有的时间

    对于程序来说,在锁竞争过程中,单个线程对锁的持有时间越长,则锁的竞争程度也就越激烈。因此应该尽可能地减少对某个锁的占有时间,以此来减少程序间互斥的可能。

    举个简单的例子:

    public synchronized void syncMethod(){
        method1();
        method2();
        method3();
    }

    假如在这个syncMethod()方法中只有method2()方法是有同步需求的,而method1()和method2()方法不需要做同步控制。这时如果method1()和method2()都是重量级的方法,就会花费较长的CPU时间。如果在并发量较大的时候,使用这种方案,就会导致等待线程大量增加。针对这种问题一个比较好的解决方法就是,只在必要时进行同步,这样就能明显的减少线程持有锁的时间,提高系统的吞吐量。具体请看下面的代码调整:

    public void syncMethod(){
    
        method1();
        synchronized (this){
            method2();
        }
    
        method3();
    }

           在改进后的代码中只针对method2()方法做了同步,锁占用时间较短,因此有了更好的并行度。其实类似于这样的手段在JDK的源码中也是可以很容易的找到,比如处理正则表达式的Pattern类:

    1.2 减小锁的粒度

           所谓减小锁粒度,就是指缩小锁定对象的范围,从而降低锁冲突的可能性,进而提高系统的并发能力。

           减小锁的粒度也能够很有效的多线程之间锁资源的竞争,ConcurrentHashMap就是一个很典型的例子。其实对于ConcurrentHashMap类,它的内部进一步的细分成了若干个小的HashMap,默认一个ConcurrentHashMap类是分为16小段。

           如果需要在ConcurrentHashMap类中增加一个新的表项,并不是将整个HashMap加锁,而是首先根据hashCode得到该表项应该被存放在哪个段中,然后对该段加锁,并完成put()方法操作。在多线程环境下,如果多个线程同时进行put操作,如果被加入的表项不在同一段中,就可以做到真正的并行。

           由于默认有16个段,如果足够幸运,则ConcurrentHashMap类可以接收16个线程同时插入,从而大大的提升吞吐量。一下的代码便显示了put()方法操作的过程。

    在5-6行获取对应的段,然后再第9行得到段将数据插入给定的段中。

    public V put(K key, V value) {
    
        Segment<K, V> s;
        if (value == null)
            throw new NullPointerException();
    
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
    
        if((s =(Segment<K,V>)UNSAFE.getObject(segments,(j << SSHIFT) + SBASE) == nul1)
        s = ensureSegment(j);
    
        return s.put(key, hash, value, false);
    }

    但是,减小锁的粒度会带来一个新的问题,当系统需要取得全局锁时,其消耗的资源会比较多。以ConcurrentHashMap为例,虽然put()方法很好的分离了锁,但是当试图访问ConcurrentHashMap类的全局信息时,就需要同时获取所有端的锁,比如size()方法:

    if (check != sum) {
    
        sum = 0L;
        for(i = 0; i < segments.length; ++i) {
            segments[i].lock();
        }
    
        for(i = 0; i < segments.length; ++i) {
            sum += (long)segments[i].count;
        }
    
        for(i = 0; i < segments.length; ++i) {
            segments[i].unlock();
        }
    }

    可以看出,需要先获取所有的段再求和。不过ConcurrentHashMap的类也不总是这样执行的,事实上,size() 方法会先使用无锁的方式求和,如果失败才会尝试这种加锁的方式。

    1.3 用读写分离锁来替换独占锁

    使用读写分离锁ReadWriteLock可以提供系统的性能。使用读写分离锁来替代独占锁是减小锁粒度的一种特殊情况。减小锁粒度是通过分割数据结构实现的,那么读写分离锁则是对系统功能点的分割。两者的维度不同。

    读写锁在读多写少的情况下有利于系统性能。因为如果系统在读写数据时只使用独占锁,那么读写操作、读写操作、写写操作都不是真正的并发操作,需要相互等待。读取操作本身不会影响数据的完整性和一致性。因此,理论上,在大多数情况下,可以允许多个线程同时读取,这正是读写锁所做的。

    1.4 锁分离

    根据读写操作的功能,有效地分离读写锁。同理根据应用程序的功能特性,还可以使用类似的分离思想来分离独占锁。一个典型的例子是java.util.concurrent.LinkedBlockingQueue的实现。在LinkedBlockingQueue的实现中,take()和put分别实现了从队列中获取数据和向队列中添加数据的功能。虽然这两个函数都修改当前队列,但是由于LinkedBlockingQueue是基于链表的,因此这两个操作分别作用于队列的前面和尾端操作,理论上它们并不冲突。

    如果使用独占锁,则要求在两个操作进行时获取当前队列的独占锁,那么take()方法和put()方法就不能真正的实现并发,在运行时他们会彼此等待对方释放资源,在这种情况下锁竞争会相对比较激烈。因此在JDK的实现中,并没有采用这样的方式,取而代之的是用两把不同的锁分离了take()和put()方法。

    由于take()和put()方法使用了不同的锁,因此两个方法之间是互相独立的,不存在锁竞争关系的。

    take()方法的实现如下:给出了部分说明

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();           //加锁 不能有两个线程同时取数据
        try {
            while (count.get() == 0) {          //如果当前没有可用数据 就一直等待
                notEmpty.await();               //等待put()方法操作的通知
            }
            x = dequeue();                      //取得第一个数
            c = count.getAndDecrement();        //数量减1 原子操作因为会和put()方法同时访问count  c是count减1之前的值
            if (c > 1)
                notEmpty.signal();              //通知其他未中断的线程
        } finally {
            takeLock.unlock();                  //释放锁
        }
        if (c == capacity)
            signalNotFull();                    //通知put()方法操作,已有空余空间
        return x;
    }

    put()方法实现如下:给出部分说明

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();             //加锁 不能有两个线程同时进行put操作
        try {
            while (count.get() == capacity) {   //如果队列已经满了就 等待
                notFull.await();
            }
            enqueue(node);                       //插入数据
            c = count.getAndIncrement();        //更新总数 变量c是count加1前的值
            if (c + 1 < capacity)
                notFull.signal();               //如果有足够的空间 通知其他线程
        } finally {
            putLock.unlock();                   //释放锁
        }
        if (c == 0)
            signalNotEmpty();                   //插入成功后 通知take()方法取数据
    }

     

    通过takeLock和putLock两把锁,LinkedBlockingQueue实现了取数据和写数据的分离,实现了真正意义上的可并发操作。

    1.5 锁粗化

           通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间越短越好,在使用完公共资源后,应该立马释放锁。只有这样,等待获取该锁资源的其他线程才能尽在的获取的锁资源。但如果对一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。

           为此,虚拟机在遇到一连串连续的对同一个锁不断进行请求和释放操作时,便会把所有的锁操作整合成对锁的一次性请求,从而减小对锁的请求同步的次数,这个操作叫锁的粗化。

    例如一个简单的例子:

    public void demoMethod(){
         synchronized (lock){
             //do sth
         }
         //其他不需要同步的工作,但是很快就能完成
    
        synchronized (lock){
             //do sth
        }
    }

    而上面的代码最终会被整合成为一次锁请求:

    public void demoMethod(){
         synchronized (lock){
             //do sth
             //其他不需要同步的工作,但是很快就能完成
         }
    }

    而在开发的过程中,大家也应该有意识的在合理的场合进行锁的粗化,尤其当在循环内请求锁时,以下是一个循环内请求锁的例子,意味着每一次循环都要去请求一次锁资源。但是这种情况显然是没有必要的。

    for (int i = 0; i < 50; i++) {
        synchronized (lock){
            // do sth
        }
    }

    所以更加合理的一种做法就是在外层请求一次锁:

    synchronized (lock){
        for (int i = 0; i < 50; i++) {
    
        }
    }

     

    展开全文
  • Spark是一个开源的通用并行分布式计算框架,由加州大学伯克利分校的AMP实验室开发,支持内存计算、多迭代批量处理、即席查询、流处理和图计算等多种范式。Spark内存计算框架适合各种迭代算法和交互式数据分析,能够...
  • 集成算法

    2019-03-21 22:07:12
    集成算法有三种(bagging ,Boosting,Stacking) 1 bagging 训练多个分类器取平均(最典型的就是随机森林) 随机:数据采样随机,特征选择随机 森林:多棵树 随机优点: 处理很高维度(feature很多)的数据,...
  • 经常会问到的问题,经典算法推导(加分项),原理,各个损失函数之间区别,使用场景,如何并行化,有哪些关键参数 比如LR(并行算法的几种优化方法经常被问到),FFM,SVM,RF,KNN,EM,Adaboost,PageRank,GBDT,...
  • (2)你先了解整个生态需要几个部分的哪些组件,这些组件都什么,然后跑几个demo,再每个部分可以挑一两个框架仔细研究其源码,比如消息队列学一学kafka、日志采集学一学flume、分布式数据库学一学Hbase、计算框架...
  • 题目: 关于并行计算的一些基础开放问题。 ◼ 如何定义并计算,请分别阐述分布式内存到共享内存模式行...◼ 请用 Amdahl 定律说明什么是并行效率以及并行算法的扩展性?并说明扩展性的性能指标和限制因素,最后请...
  • 假定你是稠密矩阵(如果是稀疏的,直接spqr或者eigen3.2的qr都很好用)根据这个(OpenCV vs. Armadillo vs. Eigen on Linux revisited)...再加速的话是不是可以试试并行算法?Google了一下比如CULA(Dense « C...
  • 在了解Fork-Join之前,我们得先了解什么是并行计算。...以程序和算法设计人员的角度看,并行计算又可分为数据并行和任务并行。数据并行把大的任务化解成若干个相同的子任务,任务并行是指每一个...
  • 并行收集器:串行收集器使用一个单独的线程进行收集,GC 时服务停顿时间 串行收集器:次要回收中使用多线程来执行 CMS 收集器是基于“标记—清除”算法实现的,经过多次标记才会被清除 G1 从整体来看是基于“标记...
  • Spark是一个开源的通用并行分布式计算框架,由加州大学伯克利分校的AMP实验室开发,支持内存计算、多迭代批量处理、即席查询、流处理和图计算等多种范式。Spark内存计算框架适合各种... 1、Spark VS Hadoop有哪些
  • 在了解Fork-Join之前,我们得先了解什么是并行计算。# 并行计算相对于串行计算,并行计算...以程序和算法设计人员的角度看,并行计算又可分为数据并行和任务并行。数据并行把大的任务化解成若干个相同的子任务,任务...
  • ParNew收集器:新生代收集器,使用复制算法,Serial收集器的多线程版,用多个线程进行GC,并行,其它工作线程 暂停。使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用- XX:...
  • 谈完并行执行的原理,咱们再来谈谈优化,到底并行执行能给我们带来哪些好处,我们又应该注意什么呢,下面展开。 Amdahl’s Law 再谈并行优化前我想必要谈谈阿姆达尔定律,可惜老爷子去年已经驾鹤先去了。 其中...
  • 1)串行的,也就是采用单线程(比较老了),分类:serial new(收集...(2)并行的,采用多线程,对于年轻代两个: parallel new(简称ParNew)(参考serial new的多线程版本)和parallel scavenge;parallel sca...
  • 终于开始攻克并行这一块了,有点小兴奋,来看看网络上R语言并行办法有哪些: 赵鹏老师(R与并行计算)做的总结已经很到位。现在并行可以分为: 隐式并行:隐式计算对用户隐藏了大部分细节,用户不需要知道具体数据...
  • 进程调度算法模拟与实现

    千次阅读 2019-05-21 21:18:31
    在进程管理中,进程调度是核心,因为在采用多道程序设计的系统中,往往若干个进程同时处于就绪状态,当就绪进程个数大于处理器数目时,就必须依照某种策略决定哪些进程优先占用处理器。 调度算法 编写允许进程并行...
  • MapReduce中的常见算法

    千次阅读 2017-02-17 13:58:29
    来自:http://www.thebigdata.cn/Hadoop/13619.html一、MapReduce中有哪些常见算法 (1)经典之王:单词计数 这个是MapReduce的经典案例,经典的不能再经典了! (2)数据去重 "数据去重"主要是为了掌握和利用...
  • 前段时间做过一些LBM相关的工作,正好借此机会分享一下我当时从零开始的LBM入门之路。...在简单查了一下目前一些广泛应用的CFD算法的谱系关系之后,我打算试一试号称编程简单、并行方便的格子Boltzmann方法(L...
  • Thinking1:ALS都有哪些应用场景 这里是引用 Thinking2:ALS进行矩阵分解的时候,为什么可以并行化处理 这里是引用 Thinking3:梯度下降法中的批量梯度下降(BGD),随机梯度下降(SGD),和小批量梯度下降有...
  • 一、MapReduce中有哪些常见算法  (1)经典之王:单词计数  这个是MapReduce的经典案例,经典的不能再经典了!  (2)数据去重  "数据去重"主要是为了掌握和利用并行化思想来对数据进行有意义的筛选。统计...
  • 一、MapReduce中有哪些常见算法  (1)经典之王:单词计数  这个是MapReduce的经典案例,经典的不能再经典了!  (2)数据去重  "数据去重"主要是为了掌握和利用并行化思想来对数据进行有意义的筛选。统计...

空空如也

空空如也

1 2 3 4 5
收藏数 99
精华内容 39
关键字:

并行算法有哪些