精华内容
参与话题
问答
  • 多线程编程——基础篇 (一)

    万次阅读 2007-06-28 15:11:00
    多线程编程模型是目前计算机系统架构的最终模型。随着CPU主频的不断攀升,X86架构的硬件已经成为瓶,在这种架构的CPU主频最高为4G。事实上目前3.6G主频的CPU已经接近了顶峰。 如果不能从根本上更新当前CPU的架构...

      [写在前面]

      随着计算机技术的发展,编程模型也越来越复杂多样化。但多线程编程模型是目前计算机系统架构的最终模型。随着CPU主频的不断攀升,X86架构的硬件已经成为瓶,在这种架构的CPU主频最高为4G。事实上目前3.6G主频的CPU已经接近了顶峰。

      如果不能从根本上更新当前CPU的架构(在很长一段时间内还不太可能),那么继续提高CPU性能的方法就是超线程CPU模式。那么,作业系统、应用程序要发挥CPU的最大性能,就是要改变到以多线程编程模型为主的并行处理系统和并发式应用程序。

      所以,掌握多线程编程模型,不仅是目前提高应用性能的手段,更是下一代编程模型的核心思想。多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O,OEMBIOS等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。

      [第一需要弄清的问题]

      如同程序和进程的区别,要掌握多线程编程,第一要弄清的问题是:线程对象和线程的区别

      线程对象是可以产生线程的对象。比如在java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。

      鉴于作者的水平,无法用更确切的词汇来描述它们的定义。但这两个有本质区别的概念请初学者细细体会,随着介绍的深入和例程分析的增加,就会慢慢明白它们所代表的真实含义。

      天下难事必始于易,天下大事必始于细。

      让我们先从最简单的"单线程"来入手:(1)带引号说明只是相对而言的单线程,(2)基于java。

        class BeginClass{
            public static void main(String[] args){
                for(int i=0;i<100;i++)
                    System.out.println("Hello,World!");
            }
        }
    

      如果我们成功编译了该java文件,然后在命令行上敲入:

      java BeginClass

      现在发生了什么呢?每一个java程序员,从他开始学习java的第一分钟里都会接触到这个问

      题,但是,你知道它到底发生发什么?

      JVM进程被启动,在同一个JVM进程中,有且只有一个进程,就是它自己。然后在这个JVM环境中,所有程序的运行都是以线程来运行。JVM最先会产生一个主线程,由它来运行指定程序的入口点。在这个程序中,就是主线程从main方法开始运行。当main方法结束后,主线程运行完成。JVM进程也随之退出。

      我们看到的是一个主线程在运行main方法,这样的只有一个线程执行程序逻辑的流程我们称

      之为单线程。这是JVM提供给我们的单线程环境,事实上,JVM底层还至少有垃圾回收这样的后台线程以及其它非java线程,但这些线程对我们而言不可访问,我们只认为它是单线程的。

      主线程是JVM自己启动的,在这里它不是从线程对象产生的。在这个线程中,它运行了main方法这个指令序列。理解它,但它没有更多可以研究的内容。

      [接触多线程]

        class MyThread extends Thread{
            public void run(){
                System.out.println("Thread say:Hello,World!");
            }
        }
    
        public class MoreThreads{
            public static void main(String[] args){
                new MyThread();
                new MyThread().start();
                System.out.println("Main say:Hello,World");
            }
        }
    

      执行这个程序,main方法第一行产生了一个线程对象,但并没有线程启动。

      main方法第二行产生了一个线程对象,并启动了一个线程。

      main方法第三行,产生并启动一个线程后,主线程自己也继续执行其它语句。

      我们先不研究Thread对象的具体内容,稍微来回想一下上面的两个概念,线程对象线程。在JAVA中,线程对象是JVM产生的一个普通的Object子类。而线程是CPU分配给这个对象的一个运行过程。我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么。如果一时想不明白,不要急,但你要记得它们不是一回事就行了。

      累了吧?为不么不继续了?

      基于这种风格来介绍多线程,并不是每个人都喜欢和接受的,如果你不喜欢,正好不浪费你的时间了,而如果你接受的话,那就看下一节吧。

      转载自dev2dev网友axman的go deep into java专栏

     
    展开全文
  • C++多线程编程

    万次阅读 多人点赞 2018-06-26 12:44:31
    C++ 11发布之前,C++并没有对多线程编程的专门支持,C++ 11通过标准库引入了对多线程的支持,大大方便了程序员的工作,本篇我们对这部分进行简要的介绍。需要说明的是,C++ 11标准库内部包裹了pthread库,因此,编译...

    c++ 11 之后有了标准的线程库:

    C++ 11发布之前,C++并没有对多线程编程的专门支持,C++ 11通过标准库引入了对多线程的支持,大大方便了程序员的工作,本篇我们对这部分进行简要的介绍。需要说明的是,C++ 11标准库内部包裹了pthread库,因此,编译程序的时候需要加上-lpthread连接选项.

    #include <thread>
    #include <condition_variable>
    #include <mutex>

     

    1 std::thread

    关键点

    a. C++ 11中创建线程非常简单,使用std::thread类就可以,thread类定义于thread头文件,构造thread对象时传入一个可调用对象作为参数(如果可调用对象有参数,把参数同时传入),这样构造完成后,新的线程马上被创建,同时执行该可调用对象;

    b. 用std::thread默认的构造函数构造的对象不关联任何线程;判断一个thread对象是否关联某个线程,使用joinable()接口,如果返回true,表明该对象关联着某个线程(即使该线程已经执行结束);

    c. "joinable"的对象析构前,必须调用join()接口等待线程结束,或者调用detach()接口解除与线程的关联,否则会抛异常;

    d. 正在执行的线程从关联的对象detach后会自主执行直至结束,对应的对象变成不关联任何线程的对象,joinable()将返回false;

     

    e. std::thread没有拷贝构造函数和拷贝赋值操作符,因此不支持复制操作(但是可以move),也就是说,没有两个 std::thread对象会表示同一执行线程;

    f. 容易知道,如下几种情况下,std::thread对象是不关联任何线程的(对这种对象调用join或detach接口会抛异常):

    默认构造的thread对象;

    被移动后的thread对象;

    detach 或 join 后的thread对象;

     

    这个程序创建了两个线程,分别对变量num进行了10000次++操作,由于两个线程同时运行,++num也没有加锁保护,所以最后的输出结果在10000到20000之间,有一定随机性,也证明了++num不是原子操作;

     

    2 std::mutex   (轻松实现互斥)

    常做多线程编程的人一定对mutex(互斥)非常熟悉,C++ 11当然也支持mutex,通过mutex可以方便的对临界区域加锁,std::mutex类定义于mutex头文件,是用于保护共享数据避免从多个线程同时访问的同步原语。它提供了lock,try_lock,unlock等几个接口,功能如下:

    调用方线程从成功调用lock()或try_lock()开始,到unlock()为止占有mutex对象;

    线程占有mutex时,所有其他线程若试图要求mutex的所有权,则将阻塞(对于 lock 的调用)或收到false返回值(对于 try_lock );

    调用方线程在调用 lock 或 try_lock 前必须不占有mutex。

    我们用mutex改写上面的例子,达到两个线程不会同时++num的目的,改写如下:

     

    经过mutex对++语句的保护,使同一时刻,只可能有一个线程对num变量进行++操作,因此,这段程序的输出必然是20000。

    mutex和thread一样,不可复制(拷贝构造函数和拷贝赋值操作符都被删除),而且,mutex也不可移动;

    备注:

     

    a.操作系统提供mutex可以设置属性,C++11根据mutext的属性提供四种的互斥量,分别是

    • std::mutex,最常用,普遍的互斥量(默认属性), 
    • std::recursive_mutex ,允许同一线程使用recursive_mutext多次加锁,然后使用相同次数的解锁操作解锁。mutex多次加锁会造成死锁
    • std::timed_mutex,在mutex上增加了时间的属性。增加了两个成员函数try_lock_for(),try_lock_until(),分别接收一个时间范围,再给定的时间内如果互斥量被锁主了,线程阻塞,超过时间,返回false。
    • std::recursive_timed_mutex,增加递归和时间属性

     

    b.mutex成员函数加锁解锁

    • lock(),互斥量加锁,如果互斥量已被加锁,线程阻塞
    • bool try_lock(),尝试加锁,如果互斥量未被加锁,则执行加锁操作,返回true;如果互斥量已被加锁,返回false,线程不阻塞。
    • void unlock(),解锁互斥量

    c. mutex RAII式的加锁解锁

    • std::lock_guard,管理mutex的类。对象构建时传入mutex,会自动对mutex加入,直到离开类的作用域,析构时完成解锁。RAII式的栈对象能保证在异常情形下mutex可以在lock_guard对象析构被解锁。
    • std::unique_lock 与 lock_guard功能类似,但是比lock_guard的功能更强大。比如std::unique_lock维护了互斥量的状态,可通过bool owns_lock()访问,当locked时返回true,否则返回false

     

    3 std::lock_guard   (有作用域的mutex ,让 程序更稳定,防止死锁)

    很容易想到,mutex的lock和unlock必须成对调用,lock之后忘记调用unlock将是非常严重的错误,再次lock时会造成死锁。有时候一段程序中会有各种出口,如return,continue,break等等语句,在每个出口前记得unlock已经加锁的mutex是有一定负担的,而假如程序段中有抛异常的情况,就更为隐蔽棘手,C++ 11提供了更好的解决方案,对的,RAII,本系列文章多次提到RAII,想必大家应该不陌生。

    类模板std::lock_guard是mutex封装器,通过便利的RAII机制在其作用域内占有mutex。

    创建lock_guard对象时,它试图接收给定mutex的所有权。当程序流程离开创建lock_guard对象的作用域时,lock_guard对象被自动销毁并释放mutex,lock_guard类也是不可复制的。

    一般,需要加锁的代码段,我们用{}括起来形成一个作用域,括号的开端创建lock_guard对象,把mutex对象作为参数传入lock_guard的构造函数即可,比如上面的例子加锁的部分,我们可以改写如下:

     

     

     

    进入作用域,临时对象guard创建,获取mutex控制权(构造函数里调用了mutex的lock接口),离开作用域,临时对象guard销毁,释放了mutex(析构函数里调用了unlock接口),这是对mutex的更为安全的操作方式(对异常导致的执行路径改变也有效),大家在实践中应该多多使用;

    C++11 std::unique_lock与std::lock_guard区别及多线程应用实例

     

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为。通常的做法是在修改共享数据成员的时候进行加锁--mutex。在使用锁的时候通常是在对共享数据进行修改之前进行lock操作,在写完之后再进行unlock操作,进场会出现由于疏忽导致由于lock之后在离开共享成员操作区域时忘记unlock,导致死锁。

    针对以上的问题,C++11中引入了std::unique_lock与std::lock_guard两种数据结构。通过对lock和unlock进行一次薄的封装,实现自动unlock的功能。

     

    std::mutex mut;  
      
    void insert_data()  
    {  
           std::lock_guard<std::mutex> lk(mut);  
           queue.push_back(data);  
    }  
      
    void process_data()  
    {  
           std::unqiue_lock<std::mutex> lk(mut);  
           queue.pop();  
    }  

    std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。

     

    std::unique_lock 的构造函数的数目相对来说比 std::lock_guard 多,其中一方面也是因为 std::unique_lock 更加灵活,从而在构造 std::unique_lock 对象时可以接受额外的参数。总地来说,std::unique_lock 构造函数如下:

    default (1)
    unique_lock() noexcept;
    
    locking (2)
    explicit unique_lock(mutex_type& m);
    
    try-locking (3)
    unique_lock(mutex_type& m, try_to_lock_t tag);
    
    deferred (4)
    unique_lock(mutex_type& m, defer_lock_t tag) noexcept;
    
    adopting (5)
    unique_lock(mutex_type& m, adopt_lock_t tag);
    
    locking for (6)
    template <class Rep, class Period>
    unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
    
    locking until (7)
    template <class Clock, class Duration>
    unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
    
    copy [deleted] (8)
    unique_lock(const unique_lock&) = delete;
    
    move (9)
    unique_lock(unique_lock&& x);

    下面我们来分别介绍以上各个构造函数:

    (1) 默认构造函数

    新创建的 unique_lock 对象不管理任何 Mutex 对象。

    (2) locking 初始化

    新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,如果此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞。

    (3) try-locking 初始化

    新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程。

    (4) deferred 初始化

    新创建的 unique_lock 对象管理 Mutex 对象 m,但是在初始化的时候并不锁住 Mutex 对象。 m 应该是一个没有当前线程锁住的 Mutex 对象。

    (5) adopting 初始化

    新创建的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)。

    (6) locking 一段时间(duration)

    新创建的 unique_lock 对象管理 Mutex 对象 m,并试图通过调用 m.try_lock_for(rel_time) 来锁住 Mutex 对象一段时间(rel_time)。

    (7) locking 直到某个时间点(time point)

    新创建的 unique_lock 对象管理 Mutex 对象m,并试图通过调用 m.try_lock_until(abs_time) 来在某个时间点(abs_time)之前锁住 Mutex 对象。

    (8) 拷贝构造 [被禁用]

    unique_lock 对象不能被拷贝构造。

    (9) 移动(move)构造

    新创建的 unique_lock 对象获得了由 x 所管理的 Mutex 对象的所有权(包括当前 Mutex 的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 Mutex 对象了。

    综上所述,由 (2) 和 (5) 创建的 unique_lock 对象通常拥有 Mutex 对象的锁。而通过 (1) 和 (4) 创建的则不会拥有锁。通过 (3),(6) 和 (7) 创建的 unique_lock 对象,则在 lock 成功时获得锁。

     

     

     

     

     

    线程同步

    std::mutex mtx_syn;
    std::condition_variable cv_syn;
    std::condition_variable cv_syn_1;
    bool ready = false;
    void threadA(int id) {
    	while (1)
    	{
    		std::unique_lock<std::mutex> lck(mtx_syn);
    		while (!ready) cv_syn.wait(lck);
    		// ...
    		std::cout << "thread " << id << '\n';
    		Sleep(500);
    		cv_syn.notify_all();   //cpu 轮询执行 所有被唤醒的线程。
    		cv_syn.wait(lck);
    	}
    	
    }
    void threadB(int id) {
    	while (1)
    	{
    //新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,如果此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞
    		std::unique_lock<std::mutex> lck(mtx_syn);
    		while (!ready) cv_syn.wait(lck);
    		// ...
    		std::cout << "thread " << id << '\n';
    		Sleep(500);
    		cv_syn.notify_all();
    		cv_syn.wait(lck);
    	}
    }
    
    
    void threadC(int id) {
    	while (1)
    	{
    		std::unique_lock<std::mutex> lck(mtx_syn);
    		while (!ready) cv_syn_1.wait(lck);
    		// ...
    		std::cout << "thread " << id << '\n';
    		Sleep(500);
    		cv_syn_1.notify_all();
    		cv_syn_1.wait(lck);
    	}
    }
    
    
    void go()
    {
    	std::unique_lock<std::mutex> lck(mtx_syn);
    	ready = true;
    	cv_syn.notify_one();
    }
    //线程同步
    	std::thread threads[5];
    	// spawn 10 threads:
    	//for (int i = 0; i<5; ++i)
    	//	threads[i] = std::thread(print_id, i);
    	threads[0] = std::thread(threadA, 0);
    	threads[1] = std::thread(threadB, 1);
    	threads[2] = std::thread(threadC, 2);  //该线程 与 0, 1 无关,不影响 0,1 线程的同步,因为用的不是一个 condition_variable
    	std::cout << "2 threads ready to race...\n";
    	go();                       // go!
    
    	for (auto& th : threads) th.join();

     

    thread 使用

     

    #include <iostream>
    
    #include <thread>
    
    std::thread::id main_thread_id = std::this_thread::get_id();
    
    void hello()  
    {
        std::cout << "Hello Concurrent World\n";
        if (main_thread_id == std::this_thread::get_id())
            std::cout << "This is the main thread.\n";
        else
            std::cout << "This is not the main thread.\n";
    }
    
    void pause_thread(int n) {
        std::this_thread::sleep_for(std::chrono::seconds(n));
        std::cout << "pause of " << n << " seconds ended\n";
    }
    
    int main() {
        std::thread t(hello);
        std::cout << t.hardware_concurrency() << std::endl;//可以并发执行多少个(不准确)
        std::cout << "native_handle " << t.native_handle() << std::endl;//可以并发执行多少个(不准确)
        t.join();
        std::thread a(hello);
        a.detach();
        std::thread threads[5];                         // 默认构造线程
    
        std::cout << "Spawning 5 threads...\n";
        for (int i = 0; i < 5; ++i)
            threads[i] = std::thread(pause_thread, i + 1);   // move-assign threads
        std::cout << "Done spawning threads. Now waiting for them to join:\n";
        for (auto &thread : threads)
            thread.join();
        std::cout << "All threads joined!\n";
    }

     

    多线程使用实例:

    #include <iostream>
    #include <opencv2/opencv.hpp>
    #include "../data300w/util/Util.h"
    #include "cunpd.hpp"
    #include <ctime>
    #include <io.h>
    #include <direct.h>  
    #include <thread>
    #include <condition_variable>
    #include <mutex>
    #include <Windows.h>
    
    
    using namespace std;
    using namespace cv;
    using namespace glasssix;
    
    
    #define ZOOM_ 1.0
    #define SIZE 96
    
    
    extern void  splitString(const string& s, vector<string>& v, const string& c);
    
    
    template <class Type>
    Type stringToNum(const string& str)
    {
    	istringstream iss(str);
    	Type num;
    	iss >> num;
    	return num;
    }
    void writeHistoFile(std::string filePath, vector<int> & densi_data)
    {
    	if (filePath == "" || densi_data.size() == 0)
    	{
    		return;
    	}
    	ofstream in;
    	in.open(filePath, ios::app);   //ios::trunc
    	int length = densi_data.size();
    	for (int i = 0; i < length; i++)
    	{
    		string dataline = to_string(densi_data[i]);
    		in << dataline << "\n";
    	}
    	in.close();
    }
    
    
    float getHorizontal(LandMark &landmark)
    {
    	float tan_theta = (landmark.points[1].y - landmark.points[0].y) / (landmark.points[1].x - landmark.points[0].x);
    	float theta = atan(tan_theta);
    	return theta * 180 / 3.1415926;
    }
    void showHistogram(vector<float> & hor_data)
    {
    	int densi[60] = { 0 };
    	int length = hor_data.size();
    	for (int i = 0; i < length; i++)
    	{
    		if (floor((hor_data[i] + 30)) >= 0 && floor((hor_data[i] + 30)) < 60)
    		{
    			densi[(int)floor((hor_data[i] + 30))]++;
    		}
    
    
    		if (floor((hor_data[i] + 30)) < 0)
    		{
    			densi[0]++;
    		}
    		else if (floor((hor_data[i] + 30)) >= 60)
    		{
    			densi[60]++;
    		}
    	}
    	string density_text = "D:\\UMD\\density_text.txt";
    	vector<int>density_data(densi, densi + 60);
    	writeHistoFile(density_text, density_data);
    
    
    	Mat histImg;
    	histImg.create(1000, 1600, CV_8UC3);
    	histImg.setTo(0);
    	int offset = 10;
    	for (int i = 0; i < 60; i++)
    	{
    		double tmpCount = densi[i];
    		rectangle(histImg, Point2f(offset + i * 25, 1000), Point2f(offset + i * 25, 1000 - tmpCount / 15.0), Scalar::all(255), -1);  //画出直方图  
    		putText(histImg, to_string(i - 29), Point2f(offset + i * 25 + 3, 1000 - 3), 0.3, 0.3, Scalar(0, 0, 255));
    		Point2f pt0;
    		pt0.x = offset + i * 25;
    		pt0.y = 1000 - densi[i] / 15.0;
    
    
    		Point2f pt1;
    		pt1.x = offset + (i + 1) * 25;
    		pt1.y = 1000 - densi[i + 1] / 15.0;
    		line(histImg, pt0, pt1, Scalar(255, 0, 0), 1); //连接直方图的顶点  
    	}
    	imshow("hist", histImg);
    	waitKey(0);
    }
    void getDatahor(string file1, vector<float> & hor_data)
    {
    	int mark_num = 5;
    	DataPrepareUtil dpu;
    	vector<LandMark> data;
    	dpu.readFileData(file1, data, mark_num);
    	int length = data.size();
    	for (int i = 0; i < length; i++)
    	{
    		float hor = getHorizontal(data[i]);
    		hor_data.emplace_back(hor);
    	}
    }
    
    
    void rotation(float theta, Mat &img, Mat &dst, Size img_size, LandMark &landmark, int mark_num)
    {
    	//rotation
    	Mat mat = img;
    	Point2f center(img_size.width / 2, img_size.height / 2);
    	double angle = theta;
    
    
    	Mat rot = getRotationMatrix2D(center, angle, 1);
    	Rect bbox = RotatedRect(center, mat.size(), angle).boundingRect();
    
    
    	cv::warpAffine(mat, dst, rot, bbox.size());
    
    
    	for (int j = 0; j < mark_num; j++)
    	{
    		float theta = -3.1415926 / (180 / angle);
    		float x1 = landmark.points[j].x - rot.at<double>(1, 2);
    		float y1 = landmark.points[j].y - rot.at<double>(0, 2);
    		landmark.points[j].x = x1 * cos(theta) - y1 * sin(theta);
    		landmark.points[j].y = x1 * sin(theta) + y1 * cos(theta);
    
    
    		//circle(dst, Point(x, y), 2, Scalar(255, 0, 0));
    	}
    	//cv::imshow("dst", dst);
    	//cv::waitKey(0);
    }
    
    
    void augment_data(string img_path, string img_text, string result_path, string result_text)
    {
    	DataPrepareUtil dpu;
    	int mark_num = 5;
    	srand((unsigned)time(NULL));
    
    
    	vector<LandMark> data;
    	dpu.readFileData(img_text, data, mark_num);
    
    
    	vector<LandMark> rotation_data;
    	vector<float> hor_data;
    	getDatahor(img_text, hor_data);
    	int length = hor_data.size();
    	for (int i = 0; i < length; i++)
    	{
    		if (hor_data[i] > 0 && hor_data[i] < 3)
    		{
    			Mat dst;
    			Mat img = imread(img_path + data[i].fileName);
    			LandMark landmark(data[i]);
    			rotation(25, img, dst, Size(96, 96), landmark, mark_num);
    			rotation_data.push_back(landmark);
    		}
    	}
    
    
    }
    
    
    bool getFaceRect(cunpd &pd, int model_id, Mat &dstImg, LandMark & landmark, Rect & rect)
    {
    	const int widths = dstImg.cols;
    	const int heights = dstImg.rows;
    	vector<FaceInfomation>  face = pd.detect(dstImg, model_id, 48);
    	int length = face.size();
    	if (length == 0)
    	{
    		cout << "not found face ." << endl;
    	}
    	for (int j = 0; j < length; j++)
    	{
    		if (face[j].score > 15)
    		{
    			rect = face[j].rect;
    
    
    			if (landmark.points[0].x > rect.x && landmark.points[0].x < rect.x + rect.width
    				&& landmark.points[0].y > rect.y && landmark.points[0].y < rect.y + rect.height
    				&&landmark.points[12].x > rect.x && landmark.points[12].x < rect.x + rect.width
    				&& landmark.points[12].y > rect.y && landmark.points[12].y < rect.y + rect.height
    				&&landmark.points[16].x > rect.x && landmark.points[16].x < rect.x + rect.width
    				&& landmark.points[16].y > rect.y && landmark.points[16].y < rect.y + rect.height
    				&&landmark.points[20].x > rect.x && landmark.points[20].x < rect.x + rect.width
    				&& landmark.points[20].y > rect.y && landmark.points[20].y < rect.y + rect.height
    				&& (abs(landmark.points[7].y - landmark.points[17].y) > (rect.height / 6.0)))
    			{
    				int rect_w = rect.width;
    				int rect_h = rect.height;
    				rect.width = rect_w * ZOOM_;
    				rect.height = rect_h * ZOOM_;
    				rect.x = max(rect.x - (ZOOM_ - 1.0) * rect_w / 2.0, 0.0);
    				rect.y = max(rect.y - (ZOOM_ - 1.0) * rect_h / 2.0, 0.0);
    
    
    				if (rect.x + rect.width > widths)
    				{
    					rect.width = widths - rect.x;
    				}
    				if (rect.y + rect.height > heights)
    				{
    					rect.height = heights - rect.y;
    				}
    				return true;
    			}
    		}
    	}
    	return false;
    
    
    }
    
    
    void getoffsetRect(Rect & rect, vector<Rect> & all_rect, int cols, int rows, int max_offset)
    {
    	srand((unsigned)time(NULL));
    	Rect rect0(rect), rect1(rect);
    	int offsetx = rand() % max_offset + 1;
    	int offsety = rand() % max_offset + 1;
    
    
    	if (rect.x > offsetx && rect.y > offsety)
    	{
    		rect0.x = rect.x - offsetx;
    		rect0.y = rect.y - offsety;
    	}
    
    
    	offsetx = rand() % max_offset + 1;
    	offsety = rand() % max_offset + 1;
    
    
    	if (rect.x + rect.width + offsetx < cols && rect.y + rect.height + offsety < rows)
    	{
    		rect1.x = rect.x + offsetx;
    		rect1.y = rect.y + offsety;
    	}
    	all_rect.push_back(rect0);
    	all_rect.push_back(rect1);
    }
    
    
    #define NEED_LANDMARK 5
    #define CURRENT_LANDMARK 21
    const int five_points[5] = { 7, 10, 14, 17, 19 };
    string search_base = "H:\\UMD\\";
    string search_dir_[] = { search_base + "umdfaces_batch1", search_base + "umdfaces_batch2", search_base + "umdfaces_batch3" };
    
    
    string text_file[] = { search_base + "umdfaces_batch1\\umdfaces_batch1_ultraface.csv", search_base + "umdfaces_batch2\\umdfaces_batch2_ultraface.csv", search_base + "umdfaces_batch3\\umdfaces_batch3_ultraface.csv" };
    string text_pre[] = { "batch1_aug_", "batch2_aug_", "batch3_aug_" };
    string tail_[] = { ".jpg", ".jpg" , ".jpg" };
    
    
    string base = search_base + "landmark_5\\augment_img\\";
    string result_img = base + "result_img_" + to_string(SIZE) + "\\";
    string result_txt = base + "landmark_" + to_string(SIZE) + "_5.txt";
    const int theta_offset = 5;
    const int theta_max = 20;
    vector<LandMark> rotation_point;
    int countNum = 0;
    bool ready = false;
    std::mutex mtx_syn;
    std::condition_variable cv_syn;
    void roll_yaw_pitch_data(LandMark result_mark, int temp, cunpd &pd, int model_id, DataPrepareUtil &dpu)
    {
    
    
    	float roll = result_mark.direct[2];
    
    
    	string img_path = search_dir_[temp] + "\\" + result_mark.fileName;
    	if (_access(img_path.c_str(), 0) == -1)
    	{
    		cout << "coun't found filename" << img_path << endl;
    		return;
    	}
    	Mat img = imread(img_path);
    	Mat dstImg; //dstImg.create(heights, widths, CV_8UC1);
    	cvtColor(img, dstImg, CV_BGR2GRAY);
    	//yaw 增强 pitch 增强
    	for (int j = 0; j < 2; j++)
    	{
    		if (result_mark.direct[j] > -theta_offset && result_mark.direct[j] < theta_offset)
    		{
    			Rect rect;
    			LandMark landmark(result_mark);
    			bool success = getFaceRect(pd, model_id, dstImg, landmark, rect);
    			if (success)
    			{
    				vector<Rect> all_rect;
    				getoffsetRect(rect, all_rect, img.cols, img.rows, 4);
    				for (int i = 0; i < 2; i++)
    				{
    					LandMark dst_landmark;
    					//vector<string> filenames;
    					//splitString(landmark.fileName, filenames, "/");
    					//string filename = filenames[filenames.size()-1];
    					
    					std::unique_lock<std::mutex> lck(mtx_syn);
    					dst_landmark.fileName = text_pre[temp] + to_string(countNum++) + ".png";
    					lck.unlock();
    					//cout << img.rows << " " << rotat_img.cols << " " << rect.x << " " << rect.y << " " << rect.width << " " << rect.height << endl;
    
    
    					Mat roi_face = img(all_rect[i]);
    					cv::resize(roi_face, roi_face, Size(SIZE, SIZE));
    					//坐标转换
    					for (int k = 0; k < 5; k++)
    					{
    						dst_landmark.visible[k] = landmark.visible[five_points[k]];
    						dst_landmark.points[k].x = ((float)SIZE / all_rect[i].width) * (landmark.points[five_points[k]].x - all_rect[i].x);
    						dst_landmark.points[k].y = ((float)SIZE / all_rect[i].height) * (landmark.points[five_points[k]].y - all_rect[i].y);
    					}
    					imwrite(result_img + dst_landmark.fileName, roi_face);
    
    
    					std::unique_lock<std::mutex> lck1(mtx_syn);
    					rotation_point.push_back(dst_landmark);
    					lck1.unlock();
    				}
    			}
    
    
    		}
    	}
    	// roll 增强
    	if (roll > -theta_offset && roll < theta_offset)
    	{
    		for (int i = -1; i < 2; i = i + 2)
    		{
    			Mat rotat_img;
    			LandMark landmark(result_mark);
    			int theta = (rand() % theta_max + theta_offset) * i;
    			rotation(theta, img, rotat_img, Size(SIZE, SIZE), landmark, CURRENT_LANDMARK);
    
    
    			Mat dstImg; //dstImg.create(heights, widths, CV_8UC1);
    			cvtColor(rotat_img, dstImg, CV_BGR2GRAY);
    
    
    			//for (int j = 0; j < CURRENT_LANDMARK; j++)
    			//{
    			//	circle(rotat_img, Point(landmark.points[j]), 2, Scalar(255, 0, 0));
    			//}
    			//imshow("img", rotat_img);
    			//waitKey(0);
    
    
    			LandMark dst_landmark;
    
    
    			//vector<string> filenames;
    			//splitString(landmark.fileName, filenames, "/");
    			//string filename = filenames[filenames.size()-1];
    			std::unique_lock<std::mutex> lck(mtx_syn);
    			dst_landmark.fileName = text_pre[temp] + to_string(countNum++) + ".png";
    			lck.unlock();
    			Rect rect;
    			bool success = getFaceRect(pd, model_id, dstImg, landmark, rect);
    			if (success)
    			{
    				//cout << rotat_img.rows << " " << rotat_img.cols << " " << rect.x << " " << rect.y << " " << rect.width << " " << rect.height << endl;
    				Mat roi_face = rotat_img(rect);
    				cv::resize(roi_face, roi_face, Size(SIZE, SIZE));
    				//坐标转换
    				for (int k = 0; k < 5; k++)
    				{
    					dst_landmark.visible[k] = landmark.visible[five_points[k]];
    					dst_landmark.points[k].x = ((float)SIZE / rect.width) * (landmark.points[five_points[k]].x - rect.x);
    					dst_landmark.points[k].y = ((float)SIZE / rect.height) * (landmark.points[five_points[k]].y - rect.y);
    				}
    				imwrite(result_img + dst_landmark.fileName, roi_face);
    
    
    				std::unique_lock<std::mutex> lck(mtx_syn);
    				rotation_point.push_back(dst_landmark);
    
    
    				if (rotation_point.size() > 500)
    				{
    					dpu.writePointVisibletoFile(result_txt, rotation_point, NEED_LANDMARK);
    					rotation_point.clear();
    				}
    				if (countNum % 500 == 0)
    				{
    					cout << "prepare data:" << countNum << endl;
    				}
    				lck.unlock();
    			}
    		}
    	}
    
    
    
    
    
    
    }
    
    
    vector<LandMark> result_point;   //注意 使用多线程 时共同处理的 变量用 全局变量。
    void deal_thread(int temp, int model_id, DataPrepareUtil &dpu, cunpd &pd)
    {
    	while (true)
    	{
    		std::unique_lock<std::mutex> lck(mtx_syn);
    		while (!ready) {
    			cv_syn.wait(lck);
    		}
    		//
    		auto itor = result_point.begin();
    		auto itor2 = result_point.end();
    		if (itor == itor2)
    		{
    			break;
    		}
    		LandMark landmark(result_point[0]);
    		result_point.erase(itor);
    //		cout << "landmark.fileName is:"<<landmark.fileName<< "thread id"<< this_thread::get_id()<< endl;
    		lck.unlock();
    
    
    		roll_yaw_pitch_data(landmark, temp, pd, model_id, dpu);
    		
    	}
    
    
    }
    
    
    void go()
    {
    	std::unique_lock<std::mutex> lck(mtx_syn);
    	ready = true;
    	cv_syn.notify_all();
    }
    
    
    
    int main()
    {
    
    	cunpd pd;
    	int model_id = pd.AddNpdModel(0);
    
    	/*string img_path = "D:\\UMD\\result_img_96\\";
    	string result_path = "D:\\UMD\\arguement_data\\";
    	string img_text = img_path + "shutter_96_5_train.txt";
    	string result_text = result_path + "augment_96_5_train.txt";
    	augment_data(img_path, img_text, result_path, result_text);*/
    
    	string base_dir = base;
    	if (_access(base_dir.c_str(), 0) == -1)
    	{
    		_mkdir(base_dir.c_str());
    	}
    	string dir = result_img;
    	if (_access(dir.c_str(), 0) == -1)
    	{
    		_mkdir(dir.c_str());
    	}
    
    
    	srand((unsigned)time(NULL));
    	DataPrepareUtil dpUtil;
    
    	dpUtil.clearFileData(result_txt);
    
    	long count = 0;
    
    	vector<LandMark> rotation_point;
    	for (int temp = 0; temp < 3; temp++)
    	{
    		long countNum = 0;
    		//vector<LandMark> result_point;
    
    
    		dpUtil.readFileData(text_file[temp], result_point, CURRENT_LANDMARK);
    
    
    		std::thread threads[4];
    		for (int i = 0; i < 4; i++)
    		{
    			threads[i] = std::thread(deal_thread, temp, model_id, dpUtil, pd);
    			//threads[i] = std::thread(threadA, i, result_point, temp, model_id, dpUtil, pd);
    		}
    		cout << "temp start:" << temp << endl;
    		go();
    		for (auto &th : threads) {
    			th.join();
    		}
    		cout << "temp end:" << temp << endl;
    		
    		}
    		if (rotation_point.size() > 0)
    		{
    			dpUtil.writePointVisibletoFile(result_txt, rotation_point, NEED_LANDMARK);
    			rotation_point.clear();
    		}
    		system("PAUSE");
    		return 0;
    }

    Future使用

     

    void test_thread() {
        //1.
        std::thread t(foo, "hello");
        t.join();
    
        //2.
        std::packaged_task<int(int)> task([](int a) {std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "my task" << std::endl; return a; });
        std::future<int> result = task.get_future();
        std::thread(std::move(task), 2).detach();
        std::cout << "Waiting...." << std::endl;
        //result.wait();
    
        //result.get会阻塞,直到对应线程完成
        std::cout << "Done  result is:" << result.get() << std::endl;
    
        //3.
        std::packaged_task<string(string)> task1(foo);
        std::future<string> result1 = task1.get_future();
        string str = "liu";
        std::thread(std::move(task1), str).detach();
        //result1.get会阻塞,直到对应线程完成
        std::cout << "task1:" << result1.get() << std::endl;
    }

     

    展开全文
  • 深入理解多线程编程

    千次阅读 2018-04-17 18:21:38
    一、多线程三大特性 1、原子性:一个操作或者多个操作要么全部执行,要么都不执行。 2、可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。 3、有序性:程序...

    一、线程安全需要保证几个基本特性
    1、原子性:一个操作或者多个操作要么全部执行,要么都不执行。
    2、可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
    3、有序性:程序执行的顺序是按照代码的先后顺序执行的,在单线程中,可以保证程序最终的执行结果和代码的顺序执行结果一致。但在多线程中,不一定保证其有序性。

    二、Java内存模型
    java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。

    主内存 —— 即main memory。在java中,实例域、静态域和数组元素是线程之间共享的数据,它们存储在主内存中。

    本地内存 —— 即local memory。 局部变量,方法定义参数 和 异常处理器参数是不会在线程之间共享的,它们存储在线程的本地内存中。

    三、Volatile关键字
    使变量在多个线程之间可见,制线程每次读取该值的时候都去“主内存”中取值
      volatile 变量自身有两个特性:
      原子性:对于任意单个volatile变量的读/写具有原子性,但是类似与volatileVal++这种复合操作来说,它就不具有原子性。
      可见性:对于一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入。
      volatile 的使用条件要严格遵循 —— 即变量真正独立于其他变量和自己以前的值 —— 在某些情况下可以使用 volatile 代替 synchronized 来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。
    **
    四、Synchronized关键字**

    在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
    当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
      不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
      
    volatile与synchronized区别:

    仅靠volatile不能保证线程的安全性。(原子性)

    ①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法

    ②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。

    synchronized不仅保证可见性,而且还保证原子性。因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

    线程安全性:

    线程安全性包括两个方面,①可见性。②原子性。

    从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。

    四、ThreadLocal接口

    参考:https://blog.csdn.net/visant/article/details/79779412

    五、线程池

    线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

    线程池作用

    基于以下几个原因在多线程应用程序中使用线程是必须的:

    1. 线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。

    2. 线程池节省了CLR 为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源。

    3. 线程池根据当前在系统中运行的进程来优化线程时间片。

    4. 线程池允许我们开启多个任务而不用为每个线程设置属性。

    5. 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。

    6. 线程池可以用来解决处理一个特定请求最大线程数量限制问题。

    线程池四种创建方式

    Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:

    • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    • newScheduledThreadPool 创建一个定时线程池,支持定时及周期性任务执行。
    • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO, 优先级)执行。

    六、多线程锁类型

    1. Lock接口

    JUC包中的 Lock 接口支持那些语义不同(重入、公平等)的锁规则。所谓语义不同,是指锁可是有"公平机制的锁"、“非公平机制的锁”、“可重入的锁"等等。“公平机制"是指"不同线程获取锁的机制是公平的”,而"非公平机制"则是指"不同线程获取锁的机制是非公平的”,"可重入的锁"是指同一个锁能够被一个线程多次获取。

    1. ReadWriteLock 读写锁

    ReadWriteLock 接口以和Lock类似的方式定义了一些读取者可以共享而写入者独占的锁。JUC包只有一个类实现了该接口,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。

    1. AbstractOwnableSynchronizer/AbstractQueuedSynchronizer/AbstractQueuedLongSynchronizer
        AbstractQueuedSynchronizer就是被称之为AQS的类,它是一个非常有用的超类,可用来定义锁以及依赖于排队阻塞线程的其他同步器;ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier和Semaphore等这些类都是基于AQS类实现的。AbstractQueuedLongSynchronizer 类提供相同的功能但扩展了对同步状态的 64 位的支持。两者都扩展了类 AbstractOwnableSynchronizer(一个帮助记录当前保持独占同步的线程的简单类)。

    2. LockSupport
        LockSupport提供“创建锁”和“其他同步类的基本线程阻塞原语”。
        LockSupport的功能和"Thread中的Thread.suspend()和Thread.resume()有点类似",LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程。但是park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。

    3. Condition
        Condition需要和Lock联合使用,它的作用是代替Object监视器方法,可以通过await(),signal()来休眠/唤醒线程。
      Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。

    4. ReentrantLock
        ReentrantLock是独占锁。所谓独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。ReentrantLock锁包括"公平的ReentrantLock"和"非公平的ReentrantLock"。“公平的ReentrantLock"是指"不同线程获取锁的机制是公平的”,而"非公平的  ReentrantLock"则是指"不同线程获取锁的机制是非公平的",ReentrantLock是"可重入的锁"。
        (01) ReentrantLock实现了Lock接口。
        (02) ReentrantLock中有一个成员变量sync,sync是Sync类型;Sync是一个抽象类,而且它继承于AQS。
        (03) ReentrantLock中有"公平锁类"FairSync和"非公平锁类"NonfairSync,它们都是Sync的子类。ReentrantReadWriteLock中sync对象,是FairSync与NonfairSync中的一种,这也意味着ReentrantLock是"公平锁"或"非公平锁"中的一种,ReentrantLock默认是非公平锁。

    5. ReentrantReadWriteLock
        ReentrantReadWriteLock是读写锁接口ReadWriteLock的实现类,它包括子类ReadLock和WriteLock。ReentrantLock是共享锁,而WriteLock是独占锁。
        ReentrantReadWriteLock的UML类图如下:

    (01) ReentrantReadWriteLock实现了ReadWriteLock接口。
      (02) ReentrantReadWriteLock中包含sync对象,读锁readerLock和写锁writerLock。读锁ReadLock和写锁WriteLock都实现了Lock接口。
      (03) 和"ReentrantLock"一样,sync是Sync类型;而且,Sync也是一个继承于AQS的抽象类。Sync也包括"公平锁"FairSync和"非公平锁"NonfairSync。

    1. CountDownLatch
        CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
        CountDownLatch的UML类图如下:

    CountDownLatch包含了sync对象,sync是Sync类型。CountDownLatch的Sync是实例类,它继承于AQS。

    1. CyclicBarrier
        CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
        CyclicBarrier的UML类图如下:

    CyclicBarrier是包含了"ReentrantLock对象lock"和"Condition对象trip",它是通过独占锁实现的。
      CyclicBarrier和CountDownLatch的区别是:
      (01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
      (02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

    1. Semaphore
        Semaphore是一个计数信号量,它的本质是一个"共享锁"。
        信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

    建议学习:
    Java多线程系列:
    http://www.cnblogs.com/skywang12345/p/java_threads_category.html

    展开全文
  • 多线程与高并发编程之基础知识(上)

    万次阅读 多人点赞 2018-10-01 21:48:25
    使用多线程,第一步就是需要知道如何实现自定义线程,因为实际开发中,需要线程完成的任务是不同的,所以我们需要根据线程任务来自定义线程,那么,JDK提供了三种自定义线程的方式,供我们实际开发中使用,来开发出...

    前言

    几乎所有的程序员都知道,现代操作系统进行资源分配的最小单元是进程,而操作系统进行运算调度的最小单元是线程,其实,在Linux中线程也可以看作是一种轻量级的进程,那么线程是包含于进程之中的,是进程中实际的运作单位;同一进程中的多个线程共用同一块内存空间,而不同的线程又拥有独立的栈内存用以存放线程本地数据;

    大家都知道,现在的计算机动辄就是多处理器核心的,而每一个线程同一时间只能运行在一个处理器上,那么如果程序采用单线程进行开发,那么就不能充分利用多核处理器带来的优势;所以为了充分利用多核处理器的资源来提高程序的执行性能,多线程编程变得越来越重要,比如对于计算密集型任务,使用一个线程可能需要100秒,但是,如果使用十个线程共同完成,那么需要的时间可能只有10秒左右;如果你是使用Java开发程序的,那么你很幸运,因为Java是内置多线程编程模型的;但是,想要使用好多线程这把利刃,还需要掌握好多线程编程的基础知识,从而做到得心应手地使用多线程进行高性能程序的开发!


    多线程的应用场景

    • 程序中出现需要等待的操作,比如网络操作、文件IO等,可以利用多线程充分使用处理器资源,而不会阻塞程序中其他任务的执行
    • 程序中出现可分解的大任务,比如耗时较长的计算任务,可以利用多线程来共同完成任务,缩短运算时间
    • 程序中出现需要后台运行的任务,比如一些监测任务、定时任务,可以利用多线程来完成

    自定义线程的实现

    处于实用的角度出发,想要使用多线程,那么第一步就是需要知道如何实现自定义线程,因为实际开发中,需要线程完成的任务是不同的,所以我们需要根据线程任务来自定义线程,JDK为我们的开发人员提供了三种自定义线程的方式,供实际开发中使用,来开发出符合需求的多线程程序!

    以下是线程的三种实现方式,以及对每种实现的优缺点进行分析,最后是对这三种实现方式进行总结;

    方式一:继承Thread类

    package com.thread;
    //通过继承Thread类实现自定义线程类
    public class MyThread extends Thread {
    	//线程体
        @Override
        public void run() {
            System.out.println("Hello, I am the defined thread created by extends Thread");
        }
        public static void main(String[] args){
            //实例化自定义线程类实例
            Thread thread = new MyThread();
            //调用start()实例方法启动线程
            thread.start();
        }
    }
    

    优点:实现简单,只需实例化继承类的实例,即可使用线程
    缺点:扩展性不足,Java是单继承的语言,如果一个类已经继承了其他类,就无法通过这种方式实现自定义线程

    方式二:实现Runnable接口

    package com.thread;
    public class MyRunnable implements Runnable {
        //线程体
        @Override
        public void run() {
            System.out.println("Hello, I am the defined thread created by implements Runnable");
        }
        public static void main(String[] args){
        	//线程的执行目标对象
            MyRunnable myRunnable = new MyRunnable();
            //实际的线程对象
            Thread thread = new Thread(myRunnable);
            //启动线程
            thread.start();
        }
    }
    

    优点:

    • 扩展性好,可以在此基础上继承其他类,实现其他必需的功能
    • 对于多线程共享资源的场景,具有天然的支持,适用于多线程处理一份资源的场景

    缺点:构造线程实例的过程相对繁琐一点

    方式三:实现Callable接口

    package com.thread;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello, I am the defined thread created by implements Callable";
        }
        public static void main(String[] args){
            //线程执行目标
            MyCallable myCallable = new MyCallable();
            //包装线程执行目标,因为Thread的构造函数只能接受Runnable接口的实现类,而FutureTask类实现了Runnable接口
            FutureTask<String> futureTask = new FutureTask<>(myCallable);
            //传入线程执行目标,实例化线程对象
            Thread thread = new Thread(futureTask);
            //启动线程
            thread.start();
            String result = null;
            try {
                //获取线程执行结果
                result = futureTask.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println(result);
        }
    }
    

    优点:

    • 扩展性好
    • 支持多线程处理同一份资源
    • 具备返回值以及可以抛出受检查异常

    缺点:

    • 相较于实现Runnable接口的方式,较为繁琐

    小结

    我们对这三种方式进行分析,可以发现:方式一和方式二本质上都是通过实现Runnable接口并重写run()方法,将接口实现类的实例传递给Thread线程类来执行线程体(run()方法中的实现),这里将Runnable接口实现类的实例作为线程执行目标,供线程Thread实例执行;对于方式三,其实也是这样的,由于Thread类只能执行Runnable接口实现类的执行目标,所以需要对Callable接口的实现类进行包装,包装成Runnable接口的实现类(通过实现了Runnable接口的FutureTask类进行包装),从而使得Thread类能够接收Callable接口实现类的实例,可见这里使用了适配器模式!

    综上所述,三种实现方式都存在着一个使用范式,即首先实现线程执行目标对象(包含线程所要执行的任务),然后将目标对象作为构造参数以实例化Thread实例,来获得线程!本质上都是实现一个线程体,由Thread来执行线程体,达到开启线程执行任务的效果!但是,三种实现方式各有优缺点,使用时,应该结合具体需求来选用合适的实现方式进行开发!


    线程的生命周期

    经过上面的代码演示,我们知道了线程如何实现,但是如果我们想要更好地使用线程,还需要对程序运行中线程的状态以及状态之间的转换(即线程的生命周期)有所了解,这样才能在多线程程序运行出现问题时,分析问题产生的原因,从而快速准确地定位并解决问题!

    首先,看一下Thread类中给出的关于线程状态的说明:

    	/**
         * 线程生命周期中的的六种状态
         * NEW:还没有调用start()的线程实例所处的状态
         * RUNNABLE:正在虚拟机中执行的线程所处的状态
         * BLOCKED:等待在监视器锁上的线程所处的状态
         * WAITING:等待其它线程执行特定操作的线程所处的状态
         * TIMED_WAITING:等待其它线程执行超时操作的线程所处的状态
         * TERMINATED:退出的线程所处的状态
         * 给定时间点,一个线程只会处于以下状态中的一个,这些状态仅仅是虚拟机层面的线程状态,并不能反映任何操作系统中线程的状态
         */
        public enum State {
            //还没有调用start()开启的线程实例所处的状态
            NEW, 
            //正在虚拟机中执行或者等待被执行的线程所处的状态,但是这种状态也包含线程正在等待处理器资源这种情况
            RUNNABLE,
            // 等待在监视器锁上的线程所处的状态,比如进入synchronized同步代码块或同步方法失败
            BLOCKED,
            // 等待其它线程执行特定操作的线程所处的状态;比如线程执行了以下方法: Object.wait with no timeout、Thread.join with no timeout、 LockSupport.park
            WAITING,
           // 等待其它线程执行超时操作的线程所处的状态;比如线程执行了以下方法: Thread.sleep、Object.wait with timeout
           //Thread.join with timeout、LockSupport.parkNanos、LockSupport.parkUntil
            TIMED_WAITING,
            //退出的线程所处的状态
            TERMINATED;
        }
    
    • 新建(New):当线程实例被new出来之后,调用start()方法之前,线程实例处于新建状态
    • 可运行(Runnable):当线程实例调用start()方法之后,线程调度器分配处理器资源之前,线程实例处于可运行状态或者线程调度器分配处理器资源给线程之后,线程实例处于运行中状态,这两种情况都属于可运行状态
    • 等待(Waitting):当线程处于运行状态时,线程执行了obj.wait()或Thread.join()方法、Thread.join、LockSupport.park以及Thread.sleep()时,线程处于等待状态
    • 超时等待(Timed Waitting):当线程处于运行状态时,线程执行了obj.wait(long)、Thread.join(long)、LockSupport.parkNanos、LockSupport.parkUntil以及Thread.sleep(long)方法时,线程处于超时等待状态
    • 阻塞(Blocked):当线程处于运行状态时,获取锁失败,线程实例进入等待队列,同时状态变为阻塞
    • 终止(Terminated):当线程执行完毕或出现异常提前结束时,线程进入终止状态

    线程的状态转换

    上面也提到了,某一时间点线程的状态只能是上述6个状态中的其中一个;但是,线程在程序运行过程中的状态是会发生变化的,由一个状态转变为另一个状态,那么下面给出线程状态转换图帮助我们清晰地理解线程的状态转变过程:
    在这里插入图片描述


    上面我们已经对线程的实现以及线程的状态有了较为清晰的认识,那么通过上述内容,我们也可以发现其实有很多方法,我们并没有详细地介绍,比如start()、yield()、wait()、notify()、notifyAll()、sleep()、join()等等,这些方法大多来源于JDK中Thread类这一关键的线程类中,下面结合Thread类的源码看一下,多线程编程中经常遇到的方法有哪些,以及这些方法的用途;

    线程类Thread源码

    实例同步方法:join()

        /**
         * 等待调用此方法的线程执行结束
         * @throws  InterruptedException 如果任何线程中断了当前线程,将会抛出此异常,同时将中断标志位清除
         */
        public final void join() throws InterruptedException {
            join(0);
        }
        /**
         * 最多等待millis毫秒,时间一到无论是否执行完毕,都会返回
         * 如果millis为0,那么意味着一直等到线程执行完毕才会返回
         * 此方法的实现是基于循环检测当前线程是否存活来判断是否调用当前实例的wait方法来实现的
         * @param  millis 等待时间
         * @throws  IllegalArgumentException 非法参数异常
         * @throws  InterruptedException 如果任何线程中断了当前线程,将会抛出此异常,同时将中断标志位清除
         */
        public final synchronized void join(long millis) throws InterruptedException {
            long base = System.currentTimeMillis();
            long now = 0;
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
            if (millis == 0) {
                while (isAlive()) {
                    wait(0);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }
        /**
         * 线程执行结束之前最多等待millis毫秒nanos纳秒
         * 此方法基于循环判断isAlive返回值来决定是否调用wait方法来实现
         * 随着一个线程终止,将会调用notifyAll方法 
         * 所以建议不要在当前实例上调用 wait、 notify、 notifyAll
         */
        public final synchronized void join(long millis, int nanos)
                throws InterruptedException {
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                        "nanosecond timeout value out of range");
            }
            if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
                millis++;
            }
            join(millis);
        }
    

    中断方法以及检测中断方法和判活方法:

        /**
         * 中断当前线程
         * 如果当前线程阻塞在Object的wait()、wait(long)、wait(long, int),或者
         * join()、join(long)、join(long, int)以及sleep(long)、sleep(long, int)等方法
         * 那么将会清除中断标志位并受到一个中断异常
         * 非静态方法
         */
        public void interrupt() {
            if (this != Thread.currentThread())
                checkAccess();
            synchronized (blockerLock) {
                Interruptible b = blocker;
                if (b != null) {
                    interrupt0();           // Just to set the interrupt flag
                    b.interrupt(this);
                    return;
                }
            }
            interrupt0();
        }
        /**
         * 检测当前线程是否已经被中断,此方法会清除当前线程的中断标志
         * 也就是说,如果这个方法被连续调用两次,并且第一次调用之前,线程被中断过,那么第一次调用返回true,第二次返回false
         * @return  <code>true</code> 如果当前线程已经被中断,返回true,否则返回false     
         * 静态方法
         */
        public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
        /**
         * 检测当前线程是否已经被中断,此方法不会清除当前线程的中断标志
         * 非静态方法
         */
        public boolean isInterrupted() {
            return isInterrupted(false);
        }
        /**
         * 根据参数值决定是否在判断中断标志位之后清除标志位
         * 实例方法
         */
        private native boolean isInterrupted(boolean ClearInterrupted);
        /**
         * 检测一个线程是否还存活,存活指的是已经启动但还没有终止
         * 实例方法
         */
        public final native boolean isAlive();
    

    结尾

    到此为止,本文已经对线程的使用场景、实现方式以及生命周期、状态转换过程以及线程类常用的方法进行了介绍,但是上面只是从概念上对线程的相关知识进行叙述,但是实际开发中,我们使用多线程是为了解决实际问题,比如如何实现多个线程共同完成一个耗时长的任务或者如何实现多个线程交互完成一个大型任务,在这些实际应用线程的过程中,会遇到问题,下面将在多线程与高并发编程基础知识(下)一文中给出多线程开发中,面临的问题以及对这些问题进行分析,并介绍常用的解决方案;最后,希望读者在阅读之后,如果发现文中出现不正确的地方还请指出,大家一同探讨和学习,共同成长^^!

    展开全文
  • java多线程编程详细入门教程

    万次阅读 多人点赞 2018-10-31 15:10:49
    1、概念 &amp;amp;amp;amp;nbsp;...线程是jvm调度的最小单元,也叫做轻量级进程,进程是由...这里提出一个问题,为什么要用多线程?有一下几点,首先,随着cpu核心数的增加,计算机硬件的并行计算能力得到提升,
  • 多线程编程核心技术

    千次阅读 2018-12-01 15:12:05
    一、多线程基础知识 ①、进程和线程的区别 线程:线程是进程当中独立运行的子任务。 ②、java.exe、javaw.exe和javaws.exe    javaw.exe主要用于启动基于GUI的应用程序。  java.exe执行应用日志再在...
  • .NET使用多线程编程

    千次阅读 2019-04-20 13:34:50
    本文介绍了如何使用System.Threading命名空间编写多线程应用程序。应用程序中的多线程可能会导致并发问题,例如竞争条件和死锁。 最后,本文讨论了各种同步技术,如Locks,Mutexes和Semaphores,以处理并发问题并...
  • 【java多线程编程】三种多线程的实现方式

    万次阅读 多人点赞 2019-01-01 16:20:56
    文章目录前言进程与线程继承Thread类,实现多线程FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?基于Runnable接口实现多线程Thread 与 Runnable 的关系Callable实现多线程线程...
  • Windows下多线程编程

    千次阅读 2019-03-26 17:58:25
    熟练掌握Windows下的多线程编程,能够让我们编写出更规范多线程代码,避免不要的异常。Windows下的多线程编程非常复杂,但是了解一些常用的特性,已经能够满足我们普通多线程对性能及其他要求。 进程与线程 1. ...
  • Windows下C++多线程编程(入门实例)

    万次阅读 多人点赞 2018-08-28 18:44:09
    多线程在编程中有相当重要的地位,我们在实际开发时或者找工作面试时总能遇到多线程的问题,对多线程的理解程度从一个侧面...),但Windows系统为我们提供了相关API,我们可以使用他们来进行多线程编程。 创建线...
  • 多线程编程注意事项

    千次阅读 2016-12-06 11:04:21
    (1)sleep主动暂停线程执行,并挂起该线程,不分配时间片,但是调度者需要检查睡眠时间是否到了。sleep(0)将该线程剩余时间片作废,请勿在主线程中使用,这会减慢消息的处理。 (2)如果消息队列中一条消息处理的...
  • java多线程编程实例

    万次阅读 多人点赞 2018-05-25 10:01:22
    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下。1.相关知识:Java多线程程序设计到的知识:(1)对同一个数量...
  • Java多线程:彻底搞懂线程池

    万次阅读 多人点赞 2019-07-09 19:27:00
    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列...
  • Java多线程编程

    万次阅读 2016-06-17 22:08:38
    Java多线程编程 Java多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一 个线程,并且每个线程定义了一个独立的执行路径。 多线程是多任务的一种特别...
  • 【java多线程编程核心技术】1.java多线程技能-笔记总结 【java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结 【java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结 【java多线程编程...
  • Java多线程编程 深入详解

    千次阅读 多人点赞 2018-01-17 21:59:54
    Java多线程编程 深入详解》读书笔记 第一章 多进程多线程概述 线程概述 进程(PROCESS):CPU的执行路径 多进程:系统中同时运行多个程序 线程(THREAD):运行在进程当中的运行单元 多线程...
  • 实战体会Java多线程编程的精要

    千次阅读 2006-04-27 08:45:00
    Java 程序中使用多线程要比在 C 或 C++ 中容易得多,这是因为 Java 编程语言提供了语言级的支持。本文通过简单的编程示例来说明 Java 程序中的多线程是多么直观。读完本文以后,用户应该能够编写简单的多线程程序...
  • 第1章 Java多线程技能 本章主要介绍线程和进程的相关概念,多线程的实现和停止,以及Thread类中的核心方法,具体实现的代码可以参考《Java多线程编程核心技术》。 进程和线程 1.进程 一个可并发执行的具有独立...
  • Java多线程编程实战指南(核心篇)读书笔记(一)

    千次阅读 多人点赞 2017-07-31 11:17:45
    Java多线程编程实战指南(核心篇)读书笔记
  • Java多线程编程核心技术》推荐

    万次阅读 多人点赞 2016-03-04 21:35:43
    写这篇博客主要是给猿友们推荐一本书《Java多线程编程核心技术》。之所以要推荐它,主要因为这本书写得十分通俗易懂,以实例贯穿整本书,使得原本抽象的概念,理解起来不再抽象。只要你有一点点Java基础,你就可以...
  • java电子书目录:部分 多线程编程基础第1章 走近Java世界中的线程 21.1 进程、线程与任务 21.2 多线程编程简介 41.2.1 什么是多线程编程 41.2.2 为什么使用多线程 41.3 Java线程API简介 51.3.1 线程的创建...
  • Java多线程编程学习与实践

    千次阅读 2018-06-27 10:14:53
    怎么样才算得上熟悉多线程编程?第一,明白进程和线程的基本概念第二,明白保护线程安全的基本方法有哪些第三,明白这些线程安全的方法,包括互斥锁,自旋锁,无锁编程的适用的业务场景是什么?从OS和硬件角度说说...
  • 1.章节结构图 ...多线程编程一方面有助于提高系统吞吐量、提高系统的响应性、充分利用多核处理器、最小化对系统资源的使用和简化程序的结构;另一方面面临线程安全、线程活性、上下文切换和可靠性等问题 ...
  • Java多线程编程核心技术》书上案例代码 基本和书上是一样的。 每一部分都是按照书上说 创建什么项目 就直接用做包最后一层的名字,全部小写,中间加下划线。java不推荐包名大小写混用。 part几就是第几章,第一章...
  • java多线程编程技术 +代码实例

    万次阅读 2011-08-27 10:05:57
    可以指定程序包含不同的执行线程,每个线程都具有自己的方法调用堆栈和程序计数器,使得线程在与其他线程并发地执行能够共享程序范围内的资源,比如共享内存,这种能力被称为多线程编程(multithreading),在核心的C...
  • 对于Java程序员来讲,Spring全家桶几乎可以搞定一切,Spring全家桶便是精妙的招式,多线程就是内功心法很重要的一块,线上出现...掌握Java多线程可以:应对面试、优化之路、深入理解。 应对面试 在面试中,求职...
  • 一道经典的Java多线程编程

    千次阅读 热门讨论 2015-09-21 11:10:34
    启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75. 程序的输出结果应该为:   线程1: ...
  • Java多线程编程-(1)-线程安全和锁Synchronized概念

    万次阅读 多人点赞 2017-09-15 14:51:59
    一、进程与线程的概念 (1)在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单位都是进程。 在未配置 OS 的系统中,程序的执行方式是顺序执行,即必须在一个程序执行完后,才允许另一个...
  • 豆瓣主页https://book.douban.com/subject/27034721/购买链接京东:https://item.jd.com/12184434.html亚马逊: https://www.amazon.cn/dp/B071RV8NLW互动出版网: http://product.china-pub.com/5661167当当: ...
  • Java多线程学习(吐血超详细总结)

    万次阅读 多人点赞 2015-03-14 13:13:17
    本文主要讲了java多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。

空空如也

1 2 3 4 5 ... 20
收藏数 1,270,744
精华内容 508,297
关键字:

多线程编程