精华内容
下载资源
问答
  • 多线程编程
    千次阅读
    2020-11-21 00:54:28

    Java中的多线程编程(超详细总结)

    一、线程与多线程的概念

    线程,即单线程,是程序的一条执行线索,执行路径,是程序使用cpu的最小单位。线程本身不能运行,它只能运行在程序中,线程是依赖于程序存在的。

    多线程,从字面上理解,就是从多个单线程一起执行多个任务。在Java 编程中,已经给多线程编程提供了内置的支持。多线程是多任务的一种特别的形式,但多线程使用了更小的cpu资源开销。 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

    二、线程与进程之间的关系

    进程: 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    ① 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

    ② 线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。

    “同时”执行是人的感觉,在线程之间实际上轮换执行。(这句话简明阐述了多线程的实现机制)

    ③ 进程在执行过程中拥有独立的内存单元,进程有独立的地址空间,而多个线程共享内存,从而极大地提高了程序的运行效率。

    ④ 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    ⑤ 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

    ⑥线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

    线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:

    (1)一个指向当前被执行指令的指令指针;
    (2)一个栈;
    (3)一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值;
    (4)一个私有的数据区。

    总而言之:

    1. 一个程序至少有一个进程,一个进程至少有一个线程。

    2. 线程的划分尺度小于进程,使得多进程程序的并发性高。

    3. 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

    4. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    5. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    在Java中,每次程序运行至少启动2个线程: 一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。

    三、一个线程的生命周期

    线程是一个动态执行的过程,它也有一个从产生到死亡的过程。下图显示了一个线程完整的生命周期。在这里插入图片描述
    1)新建状态:
    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

    2)就绪状态:
    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

    3)运行状态:
    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

    4)阻塞状态:
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    ①等待阻塞: 运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    ②同步阻塞: 线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    ③其他阻塞: 通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

    5)死亡状态:
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

    四、多线程的目的和意义

    多线程的意义: 其实任何一个程序的执行都需要获得cpu的执行权,是由cpu来去决定到底是由哪个程序来去执行,那么多线程的存在其实就是“最大限度的利用cpu资源”,当某一个线程的处理不需要占用cpu而之和I/O打交道的时候,让需要占用cpu资源的其他线程有机会获得cpu资源。从根本上说,这就是说多线程编程的目的。

    多线程的目的: 不同于其他大多数编程语言,Java本身内置了多线程的支持。使用多线程,可以帮助我们编写出cpu最大利用率的高效程序,使得空闲时间降到最低,这个对于Java运行的交互式的网络互联环境是至关重要的,因为空闲时间是公共的。例如,网络的传输效率远远低于计算机的处理速度,而本地文件系统资源的读写速度也远远低于cpu的处理能力。多线程使得并且能够充分利用这些空闲时间。

    五、线程的实现的方式

    ①继承Thread类:

    在这里插入图片描述
    在利用Thread类实现多线程的代码,需要去重写的run()方法,但是重写完run()方法 之后,在main函数中只会利用线程类创建对象,然后调用的是start()方法

    在这里大家可能会问了,为什么不是直接调用重写好的run()方法 呢?而是是利用start()方法 来开启线程的操作呢?

    这里我简单来回答一下吧, 首先通过对象.run() 方法可以执行方法,但是不是使用的多线程的方式,就是一个普通的方法,要想实现多线程的方式,一定需要通过对象.start() 方法。Java 中实现真正的多线程是 start 中的 start() 方法,run() 方法只是一个普通的方法。

    ②实现Runnable接口:

    在这里插入图片描述
    ③使用继承Thread类的方法和使用实现Runnable接口的方法之间的关系与区别:

    (1)Thread类 本身也是实现了Runnable接口,因此也是实现了Runnable接口 中的run方法

    (2)当使用继承Thread类 去实现线程时,我们需要重写run方法,因为Thread类 中的run方法 本身什么事情都不干。

    (3)当使用实现Runnable接口 去实现线程时,我们需要重写run方法,然后使用new Thread(Runnable) 这种方式来生成线程对象,这个时候线程对象中的run方法 才会去执行我们自己实现的Runnable接口 中的run方法

    更多相关内容
  • 汪文君JAVA多线程编程实战(完整不加密),连接挂了留言, 我补
  • Linux下的多线程编程.pdf 很好的一本书,值得看一看
  • 多线程编程

    千次阅读 2022-04-03 20:08:34
    多线程编程学习笔记——《Linux高性能服务器编程》

    线程概述

    线程模型

    按照线程运行环境和调度者的身份,线程可以分为内核线程用户线程

    内核线程运行在内核空间,由内核来调度。当进程的一个内核线程获得cpu使用权时,他就加载并运行一个用户线程。

    用户线程运行在用户空间,由线程库来调用。

    一个进程可以拥有M个内核线程和N个用户线程, M ≤ N M \le N MN。并且在一个sys里的所有进程中,系统线程和用户线程的比值都是固定的:完全在用户空间实现、完全由内核实现、双层调度。

    完全在用户空间实现(和协程其实有点像):无需内核支持,线程库管理并执行所有的线程,比如线程的优先级、时间片等。线程利用longjump来切换线程,但是实际上内核依然将整个进程作为最小单位来调度,对外表现出相同的优先级。优点是创建和调度无需内核干预,因而速度很快。缺点是对于多处理器系统,一个进程的多个线程无法在多个处理机上运行。此外线程优先级只对同一个进程中的其他线程有效,对不同进程中的线程无效。
    完全由内核实现将创建调度任务都交给了内核。线程库无需自行管理任务,优缺点与上面相反。M:N = 1:1
    双层调度在这里插入图片描述

    Linux线程库

    LinuxThreads和NPTL,都是按照1:1模式实现的(完全由内核实现)。Linux默认使用的是NPTL

    创建和结束线程

    定义在ptherad.h

    pthread_create

    #include<pthread.h>
    int pthread_create(pthread_t *thread, const pthread_attr_t* attr, void*(*start_routine)(void*), void* arg);
    
    typedef unsigned long int pthread_t; 
    

    thread参数是新线程的标识符,后续pthread_*系列函数通过它来引用新线程。其类型其实是unsigned long int
    attr参数用来设定新线程的属性,设置为NULL表示使用默认属性。start_routinearg表示线程将运行的函数以及参数列表。
    pthread_create调用成功返回0,失败返回错误码(和fork区别)。

    pthread_exit

    线程函数结束时最好调用如下函数保证安全干净地退出

    #include<pthread.h>
    void pthread_exit(void* retval);
    

    函数通过retval向线程的回收者传递其退出信息,执行完后不会回到调用者且不会执行失败。

    pthread_join

    一个进程中的线程可以调用pthread_join函数来回收其他线程(若其他线程是可回收的),即等待其他线程结束。类似于回收进程的wait和waitpid机制。

    int pthread_join(pthread_t thread, void** retval);
    

    thread是目标线程id,retval是目标线程的退出信息。该函数会阻塞至目标线程结束为止。成功返回0,失败:
    在这里插入图片描述

    pthread_cancel

    int pthread_cancel(pthread_t thread);
    

    用来异常终止一个线程。thread是目标线程的标识符。

    接收到取消请求的线程可以决定是否取消以及如何取消。

    int pthread_setcancelstate(int state, int* oldstate);
    int pthread_setcanceltype(int type, int* oldtype);
    

    state设置取消状态(是否允许取消),oldstate记录原来的取消状态(和setnonblocking里返回oldopt一个意思)。
    type设置取消类型(如何取消),oldtype记录原来的取消类型。
    在这里插入图片描述

    线程属性

    #include<bits/pthreadtypes.h>
    #define __SIZEOF_PTHREAD_ATTR_T 36
    typedef union{
    char __size[__SIZEOF_PTHREAD_ATTR_T];
    long int __align;
    } pthread_attr_t;
    

    可见线程的属性都包含在一个字符数组中,线程库定义了一系列函数来操作pthread_attr_t类型的变量。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    POSIX信号量

    Linux上有两组信号量API,多进程里的System V IPC信号量(semget…等),以及线程里的POSIX信号量。接口很接近但是不保证能互换。
    POSIX信号量格式是sem_*

    #include<semaphore.h>
    int sem_init(sem_t* sem, int pshared, unsigned int value);
    int sem_destroy(sem_t* sem);
    // P操作
    int sem_wait(sem_t* sem);
    int sem_trywait(sem_t* sem);
    // V操作
    int sem_post(sem_t* sem);
    

    sem指向操作的信号量。
    在这里插入图片描述

    互斥锁

    基础API

    #include<pthread.h>
    // init
    int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr);
    // init
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    int pthread_mutex_destroy(pthread_mutex_t* mutex);
    int pthread_mutex_lock(pthread_mutex_t* mutex);
    int pthread_mutex_trylock(pthread_mutex_t* mutex);
    int pthread_mutex_unlock(pthread_mutex_t* mutex);
    

    mutex指向要操作的目标互斥锁,互斥锁类型是struct pthread_mutex_t
    pthread_mutex_init初始化一个互斥锁,mutexattr指定互斥锁的属性,NULL表示默认属性。
    另外可以使用宏初始化,其实是默认将互斥锁的每个字段设置为0.
    在这里插入图片描述

    互斥锁属性

    # define __SIZEOF_PTHREAD_MUTEX_T 40
    typedef union
    {
      struct __pthread_mutex_s __data;
      char __size[__SIZEOF_PTHREAD_MUTEX_T];
      long int __align;
    } pthread_mutex_t;
    

    线程库提供了一系列函数来操作pthread_mutex_t

    // 初始化互斥锁对象
    int pthread_mutexattr_init(pthread_mutexattr__t* attr);
    // 销毁互斥锁属性对象
    int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
    // 获取和设置互斥锁的pshared属性
    int pthread_mutexattr_getpshared(const pthread_mutexattr__t* attr, int* pshared);
    int pthread_mutexattr_setpshared(const pthread_mutexattr_t* attr, int* pshared);
    // 获取和设置互斥锁的type属性
    int pthread_mutexattr_gettype(const pthread_mutexattr__t* attr, int* type);
    int pthread_mutexattr_settype(const pthread_mutexattr_t* attr, int* type);
    

    pshared指定是否允许跨进程共享互斥锁

    • PSHARED_PROCESS_SHARED 互斥锁可以被跨进程共享
    • PTHREAD_PROCESS_PRIVATE 互斥锁只能被和锁的初始化线程隶属的同一个进程的线程共享

    type指定互斥锁的类型
    在这里插入图片描述

    Example

    使用pthread_mutex_tpthread_t求和(采取累加方式),来测试多线程加速计算。
    示例了如何编写多线程程序以及如何传递参数。

    单线程:test1.cpp

    int main(){
        unsigned long long int ans = 0;
        for(unsigned long long i = 0; i < 1000000; i++){
            ans += i;
        }
        cout<<ans<<endl;
    }
    

    运行台输入 time ./test1:

    real    0m0.017s
    user    0m0.017s
    sys     0m0.001s
    

    多线程计算:test2.cpp

    #include<time.h>
    #include<iostream>
    #include<unistd.h>
    #include<pthread.h>
    #include<sys/time.h>
    using std::cout;
    using std::endl;
    // 使用struct往线程函数里传值
    struct parameter{
        int begin, end;
        parameter(int a, int b) : begin(a), end(b) {}
    };
    
    // 临界值和对应的锁
    unsigned long long int ans = 0;
    pthread_mutex_t ans_mutex;
    
    void* func(void* argc){
        parameter* val = (parameter*)argc;
        unsigned long long int v = 0;
        for(unsigned long long i = val->begin; i < val->end; i++){
            v += i;
        }
        pthread_mutex_lock(&ans_mutex);
        ans += v;
        pthread_mutex_unlock(&ans_mutex);
        pthread_exit(NULL);
    }
    
    int main(){
    	// 初始化锁
        pthread_mutex_init(&ans_mutex, NULL);
        pthread_t id1;
        pthread_t id2;
        pthread_create(&id1, NULL, func, new parameter(0, 500000));
        pthread_create(&id2, NULL, func, new parameter(500000, 1000000));
        
        pthread_join(id1, NULL);
        pthread_join(id2, NULL);
        cout<<ans<<endl;
        pthread_mutex_destroy(&ans_mutex);
    }
    

    注意:原生Linux函数里并不带pthread,所以要在编译指令里加 -lpthread
    g++ test2.cpp -o test2 -lpthread
    测试:
    time ./test2

    real    0m0.010s
    user    0m0.015s
    sys     0m0.000s
    

    条件变量

    互斥锁一般用于同步线程对共享数据的访问,条件变量一般用于在线程间同步共享变量的值。条件变量是线程间通知机制,当某个共享数据的值达到某个范围时,唤醒等待这个共享数据的线程

    #include<pthread.h>
    // 初始化条件变量
    int pthread_cond_init(pthread_cond__t* cond, const pthread_condattr_t* cond_attr);
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    // 销毁条件变量,如果销毁一个正在被等待的条件变量会返回EBUSY
    int pthread_cond_destroy(pthread_cond_t* cond);
    // 以广播的方式唤醒所有等待目标条件变量的线程
    int pthread_cond_broadcast(pthread_cond_t* cond);
    // 唤醒一个等待目标条件变量的线程
    int pthread_cond_signal(pthread_cond_t* cond);
    // 等待目标条件变量
    int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
    

    如何唤醒特定的线程:
    在这里插入图片描述
    pthread_cond_wait:等待目标条件变量,mutex参数是用于保护条件变量的互斥锁,以确保pthread_cond_wait操作的原子性。在调用pthread_cond_wait前,必须确保互斥锁mutex已经加锁,否则会导致不可预期的后果。
    pthread_cond_wait函数执行时,首先把调用线程放进条件变量的等待队列中,然后将互斥锁mutex解锁。可见,在pthread_cond_wait开始执行到其调用线程被放入条件变量的等待队列间,pthread_cond_broadcast和pthread_cond_signal等函数不会修改条件变量。
    所以,pthread_cond_wait函数不会错过目标条件变量的任何变化,pthread_cond_wait成功返回时,mutex将被再次锁上。

    封装成类

    #ifndef LOCKER_H
    #define LOCKER_H
    #include<exception>
    #include<pthread.h>
    #include<semaphore.h>
    
    class sem {
    private:
    	sem_t m_sem;
    public:
    	sem(int v) {
    		if (sem_init(&m_sem, 0, v) != 0) {
    			throw std::exception();
    		}
    	}
    
    	sem(int v = 0) {
    		if (sem_init(&m_sem, 0, v) != 0) {
    			throw std::exception();
    		}
    	}
    
    	~sem() {
    		sem_destroy(&m_sem);
    	}
    
    	bool wait() {
    		return sem_wait(&m_sem) == 0;
    	}
    
    	bool post() {
    		return sem_post(&m_sem) == 0;
    	}
    };
    
    class locker {
    private:
    	pthread_mutex_t mutex;
    public:
    	locker() {
    		if (pthread_mutex_init(&mutex, NULL) != 0) {
    			throw std::exception();
    		}
    	}
    
    	~locker() {
    		pthread_mutex_destroy(&mutex);
    	}
    
    	bool lock() {
    		return pthread_mutex_lock(&mutex) == 0;
    	}
    
    	bool unlock() {
    		return pthread_mutex_unlock(&mutex) == 0;
    	}
    };
    
    class cond {
    private:
    	pthread_cond_t m_cond;
    	pthread_mutex_t cond_mutex;
    public:
    	cond() {
    		if (pthread_mutex_init(&cond_mutex) != 0) {
    			throw std::exception();
    		}
    		if (pthread_cond_init(&m_cond, NULL) != 0) {
    			throw std::exception();
    		}
    	}
    
    	~cond() {
    		pthread_cond_destroy(&m_cond);
    		pthread_mutex_destroy(&cond_mutex);
    	}
    
    	bool wait() {
    		int ret = 0;
    		pthread_mutex_lock(&cond_mutex);
    		ret = pthread_cond_wait(&m_cond, &cond_mutex);
    		pthread_mutex_unlock(&cond_mutex);
    		return  ret == 0;
    	}
    
    	bool signal() {
    		return pthread_cond_signal(&m_cond) == 0;
    	}
    };
    
    #endif // !LOCKER_H
    
    

    多线程环境

    可重入函数

    如果一个函数能被多个线程同时被调用且不发生竞态条件,就称之为线程安全的,或者它是可重入函数
    Linux大多库函数都是可重入的,不可重入的主要是因为内部使用了静态变量,但是一般都有可重入版本:在函数名尾部加_r
    在多线程程序中调用一定要使用可重入版本。

    线程和进程

    **Problem:**在多线程程序的某个线程调用fork函数,那么新创建的子进程是否会自动创建和父进程相同数量的线程?
    子进程只拥有一个执行线程,是调用fork的哪个线程的完整赋值。

    void* func(void *argc){
        int v = (*((int*)argc));
        cout<<v<<endl;
        pthread_exit(NULL);
    }
    
    int main(){
        for(int i = 0; i < 5; i++){
            pthread_t id;
            int v = i;
            pthread_create(&id, NULL, func, &v);
        }
        pid_t id = fork();
        if(id == 0){
            cout<<"Child Over"<<endl;
        }else{
            cout<<"P Over"<<endl;
        }
    }
    /*
    0
    1
    3
    3
    4
    P Over
    Child Over
    */
    

    子进程会自动继承父进程中的互斥锁(条件变量与之类似)状态——即父进程中已经被加锁的互斥锁在子进程里也是加锁的。
    但是子进程可能不清楚父进程的互斥锁的状态:互斥锁可能被加锁了,但是并非由调用fork的线程锁住的,而是由其他线程锁住的,这种情况再次对互斥锁加锁可能导致死锁。

    pthread_mutex_t mutex;
    
    void* func(void *argc){
        cout<<"In thread, wanna lock the locker"<<endl;
        pthread_mutex_lock(&mutex);
        cout<<"In thread, got the locker"<<endl;
        sleep(5);
        pthread_mutex_unlock(&mutex);
        cout<<"In thread, give up the locker"<<endl;
        pthread_exit(NULL);
    }
    
    int main(){
        pthread_mutex_init(&mutex, NULL);
        pthread_t id;
        pthread_create(&id, NULL, func, NULL);
        sleep(1);
    
        pid_t pid = fork();
        if(pid == 0){
            cout<<"Child wanna get the locker"<<endl;
            pthread_mutex_lock(&mutex);
            cout<<"Child wanna got the locker"<<endl;
            pthread_mutex_unlock(&mutex);
            exit(0);
        }else{
            waitpid(-1, NULL, 0);
        }
    
        pthread_join(id, NULL);
        pthread_mutex_destroy(&mutex);
        return 0;
    }
    /*
    In thread, wanna lock the locker
    In thread, got the locker
    Child wanna get the locker
    In thread, give up the locker
    */
    

    pthread提供了专门的函数pthread_atfork,确保fork调用后父进程和子进程都有一个清楚的锁状态

    int pthread_atfork(void(*prepare)(void), void(*parent)(void), boid (*child)(void));
    

    该函数建立三个fork句柄来清理互斥锁的状态。
    prepare句柄将在fork调用创建出子进程之前被执行,它可以锁住所有父进程中的互斥锁。
    parent句柄则是在fork调用创建出子进程之后,fork返回之前在父进程中执行。作用是释放所有在prepare句柄中被锁住的互斥锁。
    child句柄是在fork返回前,在子进程中执行,用于释放所有在prepare中被锁住的互斥锁。

    所以在之前的代码fork()前加上:

    void pre(){
        pthread_mutex_lock(&mutex);
    }
    
    void infork(){
        pthread_mutex_unlock(&mutex);
    }
    
    pthread_atfork(pre, infork, infork);
    pid_t pid = fork();
    // ......
    

    线程和信号

    每个线程都可以独立设置信号掩码。

    #include<pthread.h>
    #include<signal.h>
    int pthread_sigmask(int how, const sigset_t* newmask, sigset_t* oldmask);
    

    newmasl参数指定新的信号掩码,oldmask输出保存以前的旧的信号掩码(与set_nonblocking里面return oldopt一个意思),how参数指定设置进程信号掩码的方式

    进程中的所有线程都共享该进程的信号,所以线程库根据线程掩码决定将信号发送给具体的哪个线程。
    所以如果我们在每个子线程中都单独设置信号掩码,就很容易导致逻辑错误。
    所有线程共享信号处理函数,所以在一个线程中设置了信号处理函数,其他线程的对应信号处理函数也会被覆盖。所以我们应该单独设置一个线程来处理所有的信号:

    • ①在主线程创建出其他子线程前就调用pthread_sigmask设置好信号掩码,所有新的子线程都将自动继承这个信号掩码。那么之后的所有子线程都不会响应被屏蔽的信号了。
    • ②在线程中调用如下函数并等待信号并处理之:
    int sigwait(const sigset_t* set, int* sig);
    

    set参数指定等待的信号集,我们可以将其指定为第一步中创建的信号掩码,那么就只有这个线程会响应对应的信号了。sig这个整数参数存储该函数返回的信号值。当sigwait正确返回,就可以对接收到的信号进行处理。
    当我们使用sigwait时,就不应该再为信号设置信号处理函数了,因为当程序接收到信号时,二者中只有一个能起作用

    example

    inline void handle_err_en(int en, const char* msg) {
    	errno = en;
    	perror(msg);
    	exit(EXIT_FAILURE);
    }
    
    void* sig_thread(void* arg) {
    	// 仅在该线程处理如下信号
    	sigset_t* sigs = (sigset_t*)arg;
    	int s, sig;
    	for (;;) {
    		s = sigwait(sigs, &sig);
    		if (s != 0) {
    			handle_err_en(s, "sigwait");
    		}
    		printf("Signal handling thread got signal %d", sig);
    	}
    }
    
    int main() {
    	pthread_t th;
    	sigset_t sigs;
    	int s;
    
    	sigemptyset(&sigs);
    	sigaddset(&sigs, SIGQUIT);
    	sigaddset(&sigs, SIGUSR1);
    	// 其他线程屏蔽该信号集
    	s = pthread_sigmask(SIG_BLOCK, &sigs, NULL);
    	if (s != 0) {
    		handle_err_en(s, "pthread_sigmask");
    	}
    	s = pthread_create(&th, NULL, sig_thread, &sigs);
    	if (s != 0) {
    		handle_err_en(s, "pthread_create");
    	}
    
    	return 0;
    }
    

    pthread_kill

    发送信号给指定线程
    int pthread_kill(pthread_t th, int sig);
    sig指定待发送的信号,如果为0,则不发送信号,但是仍然执行错误检测,可以利用此来完成线程是否存在的检测。

    #include<pthread.h>
    #include<signal.h>
    #include<stdio.h>
    #include<errno.h>
    #include<unistd.h>
    void* func(void* arg){
        sigset_t sigs;
        sigaddset(&sigs, SIGALRM);
        int i = 0;
        bool run = true;
        while(run){
            printf("wait for signal\n");
            int sig;
            sigwait(&sigs, &sig);
            if(sig == SIGALRM){
                if(i++ == 1) run = false;
                printf("Recv sig %d\n", sig);
            }
        }
        pthread_exit(NULL);
    }
    
    int main(){
        int ret = -1;
        pthread_t th1;
        pthread_create(&th1, NULL, func, NULL);
        sleep(5);
        for(int i = 0; i < 4; i++){
        	// 发送两次后,子线程会退出,后续会失败
            sleep(3);
            ret = pthread_kill(th1, SIGALRM);
            if(ret != 0) printf("Error code %d\n", errno);
        }
        pthread_join(th1, NULL);
        printf("Over\n");
        return 0;
    }
    /*
    wait for signal
    Recv sig 14
    wait for signal
    Recv sig 14
    Error code 0
    Error code 0
    Over
    */
    
    展开全文
  • C#多线程编程实例 线程与窗体交互源码,该示例演示如何在线程安全的模式下调用Windows窗体上的控件。
  • C++多线程编程实战 ,姜佑译(2018年PDF高清).rar C++多线程编程实战 ,姜佑译(2018年PDF高清).rar
  • QT TCP多线程编程例子

    热门讨论 2013-06-06 14:50:20
    这是QT TCP多线程编程的例子,开启多个客户端可以从服务器中取得不同的数据。
  • Java 多线程编程基础(详细)

    万次阅读 多人点赞 2020-11-03 17:36:30
    Java多线程编程基础进程与线程多线程实现Thread类实现多线程Runnable接口实现多线程Callable接口实现多线程多线程运行状态多线程常用操作方法线程的命名和获取线程休眠线程中断线程强制执行线程让步线程优先级设定...

    1. 进程与线程

    进程与线程的基本认识

    进程(Process):进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位。

    线程(Thread):线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。

    为什么会有线程

    每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通,即使多进程可以提高硬件资源的利用率,但是进程的启动和销毁需要消耗大量的系统性能,从而导致程序的执行性能下降。所以为了进一步提升并发操作的处理能力,在进程的基础上又划分了多线程概念。

    以B站看视频为例:

    打开B站看视频可以理解为实现了一个“进程”,而在看的时候,会同时听到声音,看到图片,还有可以发弹幕等……这些都是多线程实现。

    简而言之:一个程序至少一个进程,一个进程至少一个线程。

    2. 多线程实现

    在java中,如果要实现多线程,就必须依靠线程主体类,而java.lang.Thread是java中负责多线程操作类,只需继承Thread类,就能成为线程主体类,为满足一些特殊要求,也可以通过实现Runnable接口或者Callable接口来完成定义。

    具体方式如下:

    1.继承Thread类,重写run方法(无返回值)

    2.实现Runnable接口,重写run方法(无返回值)

    3.实现Callable接口,重写call方法(有返回值且可以抛出异常)

    2.1 Thread类实现多线程

    通过继承Thread类,并重写父类的run()方法实现
    public void run()

    定义线程类:

    class MyThread extends Thread{
    	private String name;
    	public MyThread(String name) {
    		this.name = name;
    	}
    	@Override
    	public void run() {
    		for(int i = 0 ; i < 50 ; i++) {
    			System.out.println(this.name + "正在工作中……" + i);
    		}
    	}
    }
    

    多线程启动:

    public class testThread {
    	public static void main(String[] args) {
    		// 实例化线程对象
    		MyThread mt1 = new MyThread("线程一");
    		MyThread mt2 = new MyThread("线程二");
    		MyThread mt3 = new MyThread("线程三");
    		// 启动实例线程对象
    		mt1.start();
    		mt2.start();
    		mt3.start();
    	}
    }
    

    运行情况:

    在这里插入图片描述
    以上结果可以发现,它并不是按照顺序执行,而是以一种随机交替方式执行的,每次的结果都可能不一样。

    通过以上代码可以知道,要想实现多线程,就要依靠Thread类的start()方法执行,线程启动后会默认调用run()方法。

    2.2 Runnable接口实现多线程

    使用Thread类的确可以实现多线程,但是也容易发现它的缺陷:面向对象的单继承局限,因此才采用Runnable接口来实现多线程。

    该接口的定义如下(以下并不需要在代码中实现):

    @FunctionalInterface
    public interface Runnable{
    	public void run();
    }
    

    定义线程类:

    public class MyThread implements Runnable{
    	private String name;
    	public MyThread(String name) {
    		this.name = name;
    	}
    	@Override
    	public void run() {
    		for(int i = 0 ; i<50 ;i++) {
    			System.out.println(this.name + " 正在执行中……" + i);
    		}
    	}
    }
    

    多线程启动:

    public class testThread {
    	public static void main(String[] args) {
    		// 实例化继承Runnable接口的MyThread类
    		Runnable mt1 = new MyThread("线程一");
    		Runnable mt2 = new MyThread("线程二");
    		Runnable mt3 = new MyThread("线程三");
    		
    		// 多线程启动
    		new Thread(mt1).start();
    		new Thread(mt2).start(); 		
    		new Thread(mt3).start(); 		
    	}
    }
    

    运行情况:

    在这里插入图片描述

    以上程序实例化三个继承Runnable接口的MyThread类,然后通过Thread类的一个构造函数public Thread(Runnable target)分别实例化Thread类,再利用start()方法实现多线程。

    Thread方案实现和Runnable方案实现的区别:

    // Thread类定义:
    public class Thread extends Object implements Runnable {}
    

    也就是说,Thread类是Runable接口的子类,通过直接覆写Thread类的run方法实际上依然是覆写Runnable接口内的run方法,其实本质上是没有区别的,但是利用Runnable方案实现更加能体现面向对象思维,有点类似于代理设计模式。

    2.3 Callable接口实现多线程

    使用Runnable接口实现的多线程可以避免单继承的局限,但是还有一个问题就是run方法没有返回值,为了解决这个问题,所以提供了一个Callable接口java.util.concurrent.Callable

    其定义如下:

    @FunctionalIterface
    public interface Callable<T>{
    	public T call() throws Exception;
    }
    

    FutureTask类常用方法:

    import java.util.concurrent.ExecutionException; // 导入ExecutionException异常包
    public FutureTask(Callable<T> callable) // 构造函数:接收Callable接口实例
    public FutureTask(Runable runnable,T result) // 构造函数:接收Runnable接口实例,同时指定返回结果类型 
    public T get() throws InterruptedException,ExecutionException // 取得线程操作返回结果
    

    Thread类的一个构造方法:

    public Thread(FutureTask<T> futuretask) //构造方法:接收FutureTask实例化对象
    

    定义线程主体类:

    import java.util.concurrent.Callable;
    public class MyThread implements Callable<Integer>{
    	private String name;
    	public MyThread(String name) {
    		this.name = name;
    	}
    	@Override
    	public Integer call(){
    		Integer sum = 0;
    		for(int i = 0 ; i < 500;i++) {
    			System.out.println(this.name + i);
    			sum += i;
    		}
    		return sum;
    	}
    }
    

    多线程启动:

    import java.util.concurrent.FutureTask;
    public class testThread {
    	public static void main(String[] args)  throws Exception{
    		// 实例化继承Callable接口的MyThread类
    		MyThread mt1 = new MyThread("线程一");
    		MyThread mt2 = new MyThread("线程二");
    		MyThread mt3 = new MyThread("线程三");
    		
    		// FutureTask类接收继承Callable接口的MyThread的实例
    		FutureTask<Integer> ft1 = new FutureTask<Integer>(mt1);
    		FutureTask<Integer> ft2 = new FutureTask<Integer>(mt2);
    		FutureTask<Integer> ft3 = new FutureTask<Integer>(mt3);
    		
    		// 启动多线程
    		new Thread(ft1).start();
    		new Thread(ft2).start();
    		new Thread(ft3).start();
    		System.out.println(ft1.get());
    		System.out.println(ft2.get());
    		System.out.println(ft3.get());
    	}
    }
    

    运行情况:

    在这里插入图片描述
    通过以上代码容易了解到,Callable接口实现采用泛型技术实现,继承需要重写call方法,再通过FutureTask包装器包装,传入后实例化Thread类实现多线程。
    其中FutureTask类是Runnable接口的子类,所以才可以利用Thread类的start方法启动多线程,读者可以将call方法假设为有返回值的run方法。

    2.3 多线程运行状态

    任意线程具有5种基本的状态:

    1.创建状态

    实现Runnable接口和继承Thread类创建一个线程对象后,该线程对象处于创建状态,此时它已经有了内存空间和其它资源,但他还是处于不可运行的。

    2.就绪状态

    新建线程对象后,调用该线程的start方法启动该线程。启动后,进入线程队列排队,由CPU调度服务。

    3.运行状态

    就绪状态的线程获得处理器的资源时,线程就进入了运行状态,此时将自动调用run方法。

    4.阻塞状态

    正在运行的线程在某些特殊情况下,如:当前线程调用sleep、suspend、wait等方法时,运行在当前线程里的其它线程调用join方法时,以及等待用户输入的时候。只有当引起阻塞原因消失后,线程才能进入就绪状态。

    5.终止状态

    当线程run方法运行结束后,或者主线程的main()方法结束后,线程才能处于终止状态,线程一旦死亡就不能复生。

    3. 多线程常用操作方法

    1. 线程的命名与取得
    2. 线程休眠方法
    3. 线程中断方法
    4. 线程强制执行
    5. 线程让步
    6. 线程优先级

    3.1 线程的命名和获取

    线程是不确定的运行状态,名称就是线程的主要标记。因此,需要注意的是,对于线程的名字一定要在启动之前设置进程名称,不建议对已经启动的线程,进行更改名称,或者为不同线程设置重名。

    其主要方法:

    public Thread(Runnable runnable,String name) //构造函数:实例化线程对象,为线程对象设置名称
    public final void setName(String name) // 普通函数:设置线程名字
    public final String getName() // 普通函数:获取线程名字
    

    观察线程命名操作:

    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		System.out.println( Thread.currentThread().getName());
    		System.out.println( Thread.currentThread().getName());
    	}
    }
    
    public class ThreadDemo {
    	public static void main(String[] args) {
    		MyThread mt1 = new MyThread();
    		MyThread mt2 = new MyThread();
    		MyThread mt3 = new MyThread();
    		
    		new Thread(mt1,"线程一").start();
    		new Thread(mt2,"线程二").start();
    		new Thread(mt3).start(); // 使用默认的线程名称
    		mt1.run(); // 这里直接查看main方法的线程名称
    	}
    }
    

    运行情况:

    在这里插入图片描述
    需要注意的是主方法也是进程的一个线程。

    3.2 线程休眠

    sleep方法定义在java.lang.Thread中,由Thread.sleep()调用实现。其作用是需要暂缓线程的执行速度,则可以让当前线程休眠,即当前线程从“运行状态”进入到“阻塞状态”。sleep方法会指定休眠时间,线程休眠的时间会大于或等于该休眠时间,该线程会被唤醒,此时它会由“阻塞状态”变成“就绪状态”,然后等待CPU的调度执行。

    其主要方法:

    public static void sleep(long millis) throws InterruptedException // 普通函数:设置休眠时间的毫秒数
    public static void sleep(long millis,int nanos) throws InterruptedException // 普通函数:设置休眠毫秒数和纳秒数
    

    定义线程主体类:

    public class MyThread implements Runnable{
    	@Override
    	public void run() {
    		for(int i = 0 ; i<90 ; i++) {
    			System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i );
    			if(i == 20) {
    				try {
    					System.out.println(Thread.currentThread().getName() + " 等一会儿就要休息五秒钟了……");
    					Thread.sleep(5000); // 当前线程休眠五秒钟
    					 System.out.println(Thread.currentThread().getName() + " 已经休息五秒钟了……");
    				}catch (InterruptedException e) {
    					System.out.println(Thread.currentThread().getName() + " 休眠被打扰了……");
    				}
    			}
    		}
    	}
    }
    

    观察线程休眠操作:

    public class ThreadDemo {
    	public static void main(String[] args){
    		 MyThread mt1 = new MyThread();
    		 MyThread mt2 = new MyThread();
    		 MyThread mt3 = new MyThread();
    		 
    		 new Thread(mt1,"线程一").start();
    		 new Thread(mt2,"线程二").start();
    		 new Thread(mt3,"线程三").start();
    	}
    }
    

    运行情况:

    在这里插入图片描述
    由以上结果容易了解到,休眠期间该线程并没有工作,而其他未休眠的线程则在继续工作。

    3.3 线程中断

    interrupt方法定义在java.lang.Thread中,由Thread.interrupt()调用实现。该方法将会设置该线程的中断状态位,即设置为true,中断的结果线程是终止状态、还是阻塞状态或是继续运行至下一步,就取决于该程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(即中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

    其主要方法:

    // 以下均为Thread类的方法
    public boolean isInterrupted() //普通函数:判断线程是否被中断
    public void interrupt() //普通函数:中断线程执行
    

    定义线程主体类:

    public class MyThread implements Runnable {
    	@Override
    	public void run() {
    		int i = 0;
    		while(true) {
    			System.out.println(Thread.currentThread().getName() + " 正在努力工作中……" + i++);
    			try {
    				System.out.println(Thread.currentThread().getName() + " 准备休息5秒钟了……");
    				Thread.sleep(5000);  // 休眠5秒钟
    				System.out.println(Thread.currentThread().getName() + " 已经休息5秒钟了……");
    			}catch(InterruptedException e) {
    				System.out.println(Thread.currentThread().getName() + " 被打扰了,不想工作了……");
    				break;
    			}
    		}
    	}
    }
    

    观察线程中断操作:

    public class ThreadDemo {
    	public static void main(String[] args) throws Exception{
    		Runnable mt1 = new MyThread();
    		Runnable mt2 = new MyThread();
    		Runnable mt3 = new MyThread();
    		
    		Thread thread1 = new Thread(mt1,"线程一"); //线程一就绪
    		Thread thread2 = new Thread(mt2,"线程二"); //线程二就绪
    		Thread thread3 = new Thread(mt3,"线程三"); //线程三就绪
    		thread1.start(); //线程一启动
    		thread2.start(); //线程二启动
    		thread3.start(); //线程三启动
    		
    		// 以下通过利用main线程控制 线程一 中断
    		Thread.sleep(6000); //使main方法先休眠6秒钟,即让子线程先运行6秒钟
    		if(!thread1.isInterrupted()) {
    			System.out.println("吵闹~~~");
    			thread1.interrupt(); //中断线程一的执行
    		}
    	}
    }
    

    运行情况:
    在这里插入图片描述
    以上代码线程一在休眠期间被中断,然后直接break,也就是说以后就只有线程二和三工作了。这里每当中断执行都会产生InterruptedException异常。

    3.4 线程强制执行

    join方法定义在java.lang.Thread中,由Thread.join()调用实现。多线程启动后会交替进行资源抢占和线程体执行,如果此时某些线程异常重要,也就是说这个对象需要优先执行完成,则可以设置为线程强制执行,待其完成后其它线程继续执行。

    其主要方法:

    // Thread类方法
    public final void join() throws InterruptedException //普通函数:强制执行
    

    定义线程主体类:

    public class MyThread implements Runnable{
    	private Thread thread = null;
    	public MyThread() {}
    	public MyThread(Thread thread) {
    		this.thread = thread;
    	}
    	@Override
    	public void run() {
    		for(int i = 0; i<50 ; i++) {
    			if(i >= 20 && i <= 25) {
    					try {
    						System.out.println(thread.getName()  + "被迫参与 " + Thread.currentThread().getName() + " 的工作了……" +i);
    						thread.join();
    					}catch(InterruptedException e) {
    						e.printStackTrace();
    					}
    			}
    			// 以下语句不管上面判断语句是否执行都会执行的
    			System.out.println(Thread.currentThread().getName() + " 正在工作中……" +i);
    		}
    	}
    }
    

    观察线程强制执行操作:

    public class ThreadDemo {
    	public static void main(String[] args) {
    		Thread mainThread = Thread.currentThread();
    		MyThread mt1 = new MyThread(mainThread);
    		Thread thread1 = new Thread(mt1,"子线程");
    		
    		thread1.start();
    		for(int i = 0 ;i<20;i++) {
    			try {
    				Thread.sleep(1000); // 每次main线程休眠1秒
    				System.out.println(Thread.currentThread().getName() +  "正在工作中……" + i);
    			}catch(InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		System.out.println("我main线程已经完成了所有任务,从此无法再复生了……");
    	}
    }
    

    运行情况:
    在这里插入图片描述

    由以上结果容易了解到,当子线程经行到第20次时,开始强制执行main线程任务,直至全部完成为止,而且只能够完整地执行地一次。当main线程执行完毕后,子线程才能开始继续执行。

    3.5 线程让步

    yield方法定义在java.lang.Thread中,由Thread.yield()调用实现。多线程在彼此交替执行的时候往往需要进行资源的轮流抢占,如果某些不是很重要的线程抢占到资源但是又不急于执行时,就可以将当前的资源暂时让步出去,交给其它资源先执行。但是,因为yeild是将线程由“运行状态”转别为“就绪状态”,这样并不能保证在当前线程调用yield方法之后,其它具有相同优先级的线程就一定能获得执行权,也有可能是当前线程又进入到“运行状态”继续运行,因为还是要依靠CPU调度才可以。

    其主要方法:

    public static void yield() // 静态函数:线程让步
    

    定义线程主体类:

    public class MyThread implements Runnable{
    	private Thread thread = null;
    	public MyThread () {}
    	public MyThread(Thread thread) {
    		this.thread = thread;
    	}
    	@Override
    	public void run() {
    		for(int i = 0 ; i<50 ; i++) {
    			System.out.println(Thread.currentThread().getName()  + " 正在工作中……" + i);
    			if( i  == 30) {
    				System.out.println(Thread.currentThread().getName() + " 打算将工作交给 "+thread.getName() + "了……");
    				Thread.yield(); // 当前线程让步出去
    				System.out.println(Thread.currentThread().getName() + " 又想自己工作了……");
    			}
    		}
    	}
    }
    
    

    观察线程让步操作:

    public class ThreadDemo {
    	public static void main(String[] args) {
    		Thread mainThread = Thread.currentThread();
    		Runnable mt1 = new MyThread(mainThread);
    		Thread thread1 = new Thread(mt1,"子线程");
    		
    		thread1.start();
    		for(int i = 0 ; i < 40 ; i++) {
    	*运行情况:*		System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i);
    		}
    	}
    }
    

    运行情况:

    在这里插入图片描述
    由以上结果容易了解到,会短暂地将资源调度让给其它的线程,当然这并不是严格的,因为还是要CPU调度的。

    3.6 线程优先级

    所有创造的线程都是子线程,所有的子线程在启动时都会保持同样的优先级权限,但是如果现在某些重要的线程希望可以优先抢占到资源并且先执行,就可以修改优先级权限来实现。
    记住当线程的优先级没有指定时,所有线程都携带普通优先级。

    需要理解的是:
    优先级用从1到10的范围的整数指定。10表示最高优先级,1表示最低优先级,5是普通优先级,也就是默认优先级。
    优先级相对最高的线程在执行时被给予优先权限。但是不能保证线程在启动时就进入运行状态。
    优先级越高越有可能先执行。

    其主要方法以及常量:

    public static final int MAX_PRIORITY // 静态常量:最高优先级,数值为10
    public static final int NORM_PRIORITY //静态常量:普通优先级,数值为5
    public static final int MIN_PRIORITY // 静态常量:最低优先级,数值为1
    public final void setPriority(int newPriority) // 普通函数:设置优先级
    public final int getPriority() //普通函数:获取优先级
    

    定义线程主体类:

    public class MyThread implements Runnable{
    	@Override
    	public void run() {
    		for(int i  = 0 ; i<50 ; i++) {
    			System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i);
    		}
    	}
    }
    

    观察线程优先级操作:

    public class ThreadDemo {
    	public static void main(String[] args) {
    		Runnable mt1 = new MyThread();
    		Runnable mt2 = new MyThread();
    		
    		Thread thread1  = new Thread(mt1,"线程一");
    		Thread thread2  = new Thread(mt2,"线程二");
    		
    		// 设置优先级
    		thread2.setPriority(Thread.MAX_PRIORITY);
    		thread1.setPriority(Thread.MIN_PRIORITY);
    		
    		// 启动
    		thread1.start();
    		thread2.start();
    	}
    }
    

    运行情况:

    在这里插入图片描述
    由以上可以看出,线程二执行的概率高于线程一的执行概率,前面的执行情况大致为交替执行,是因为它们的优先级均相等,默认都等于5。

    4. 线程的同步和锁死

    当这样也导致了一个问题:在某一时刻,这一份资源在某一个线程发生改变时,其它线程正在执行的操作也会受到其影响。

    如下程序为售票员售票代码以及结果:

    定义线程主体类:

    public class MyThread implements Runnable{
    	private int ticket = 10; 
    	@Override
    	public void run() {
    		while(true) {
    				if(ticket<0) {
    					System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
    					break;
    				}
    				try {
    					Thread.sleep(1000); // 延迟1秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
    				}catch(InterruptedException e) {
    					e.printStackTrace();
    				}
    				System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
    			}
    	}
    }
    

    观察售票状态:

    public class ThreadDemo {
    	public static void main(String[] args) {
    		// 一份资源
    		Runnable mt1 = new MyThread();
    		
    		// 共享同一份资源
    		new Thread(mt1,"售票员A").start();
    		new Thread(mt1,"售票员B").start();
    		new Thread(mt1,"售票员C").start();
    	}
    }
    

    运行情况:

    在这里插入图片描述

    以上结果就很好地说明了多线程的不安全问题,票数本来应该大于等于0的,但是因为某一个A线程延迟,由于共享资源,此时可能其它某个线程已经将票数售完(ticket-- == 0),然后线程A继续执行就会使得票数小于0。要解决类似于上面的问题,就需要引入线程同步和死锁概念。

    4.1 线程同步

    解决数据共享问题必须使用同步,所谓的同步就是指多个线程在同一个时间段内只能有一个线程执行指定的代码,其他线程要等待此线程完成之后才可以继续进行执行,在Java中提供有synchronized关键字以实现同步处理,同步的关键是要为代码加上“锁”。

    而锁的操作有三种:
    1.同步代码块
    2.同步方法
    3.Lock实现

    同步代码块实现

    使用方式:

    synchronized(需要同步的对象){
        需要同步的操作
    }
    

    定义线程主体类:

    public class MyThread implements Runnable{
    	private int ticket = 10; 
    	@Override
    	public void run() {
    		while(true) {
    			// 同步代码块
    			synchronized(this) {
    				if(ticket<0) {
    					System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
    					break;
    				}
    				try {
    					Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
    				}catch(InterruptedException e) {
    					e.printStackTrace();
    				}
    				System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
    			}
    		}
    	}
    }
    

    观察售票状态:

    public class ThreadDemo {
    	public static void main(String[] args) {
    		// 一份资源
    		Runnable mt1 = new MyThread();
    		
    		// 共享同一份资源
    		new Thread(mt1,"售票员A").start();
    		new Thread(mt1,"售票员B").start();
    		new Thread(mt1,"售票员C").start();
    	}
    }
    

    运行情况:

    在这里插入图片描述
    读者从上面代码也容易了解到,虽然它的确起到了安全的作用,但是执行的效率却下降了,因为每次都只有一个线程才能访问同步代码块。

    同步方法实现

    使用方式:

    利用函数包装的形式实现,如下:
    修饰符 synchronized 返回类型 函数名()
    

    定义线程主体类:

    public class MyThread implements Runnable{
    	private int ticket = 10; 
    	@Override
    	public void run() {
    		while(this.sale()) {}
    	}
    	public synchronized boolean sale() {
    			if(ticket<0) {
    				System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
    				return false;
    			}
    			try {
    				Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
    			}catch(InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
    			return true;
    		}
    }
    

    观察售票状态:

    public class ThreadDemo {
    	public static void main(String[] args) {
    		// 一份资源
    		Runnable mt1 = new MyThread();
    		
    		// 共享同一份资源
    		new Thread(mt1,"售票员A").start();
    		new Thread(mt1,"售票员B").start();
    		new Thread(mt1,"售票员C").start();
    	}
    }
    

    运行情况:

    在这里插入图片描述

    Lock锁实现

    定义线程主体类:

    package cn.wu;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MyThread implements Runnable{
        private int ticket = 10;
        private final Lock lock = new ReentrantLock();
        @Override
        public void run() {
            while(this.sale()) {}
        }
        public boolean sale() {
            lock.lock();
            try{
                if(ticket<0) {
                    System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
                    return false;
                }
                Thread.sleep(200);
                System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            return true;
        }
    }
    

    观察售票状态:

    public class ThreadDemo {
    	public static void main(String[] args) {
    		// 一份资源
    		Runnable mt1 = new MyThread();
    		
    		// 共享同一份资源
    		new Thread(mt1,"售票员A").start();
    		new Thread(mt1,"售票员B").start();
    		new Thread(mt1,"售票员C").start();
    	}
    }
    

    运行情况:

    在这里插入图片描述

    4.2 线程死锁

    所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,死锁的操作一般是在程序运行时候才有可能出现,死锁是在多线程开发中较为常见的一种问题,过多的同步就有可能出现死锁。

    观察死锁状态:

    class firstCorssBridge{
    	public synchronized void tell(secondCorssBridge scb) {
    		System.out.println("张三告诉王五:我先过,你后过,否则你别想过这桥!");
    		scb.cross();
    	}
    	// 以下函数不会执行
    	public synchronized void cross() {
    		System.out.println("张三快快乐乐地过桥了……");
    	}
    }
    class secondCorssBridge{
    	public synchronized void tell(firstCorssBridge fcb) {
    		System.out.println("王五告诉张三:我先过,你后过,否则你别想过这桥!");
    		fcb.cross();
    	}
    	// 以下函数不会执行
    	public synchronized void cross() {
    		System.out.println("王五快快乐乐地过桥了……");
    	}
    }
    
    
    public class DeadLock implements Runnable{
    	private firstCorssBridge fcb = new firstCorssBridge();
    	private secondCorssBridge scb = new secondCorssBridge();
    	
    	public DeadLock() {
    		// 启动线程 并执行以下语句
    		new Thread(this).start(); // 会运行run函数
    		fcb.tell(scb); // 运行到里面时 fcb会等待scb
    	}
    	@Override
    	public void run() {
    		scb.tell(fcb); // 运行到里面时 scb会等待fcb
    	}
    	
    	public static void main(String[] args) {
    		new DeadLock();
    	}
    }
    

    运行情况:

    在这里插入图片描述
    上面红色的说明:两者已经处于相互等待状态,后续代码并不会执行。

    如果读者不太理解上面的代码,可以在new Thread(this).start(); // 会运行run函数语句的下面加上:

    try {
    		Thread.sleep(100); // 休眠0.1秒钟
    	}catch(InterruptedException e) {
    		e.printStackTrace();
    	}
    

    结果就为:

    在这里插入图片描述
    这实际上是“锁中有锁”的情况。

    5. 后台守护线程

    Java中的线程分为两类,用户线程和守护线程。守护线程(Daemon)是一种运行在后台的线程服务线程,当用户线程存在时,守护线程可以同时存在,但是,当用户线程不存在时,守护线程会全部消失。

    主要操作方法:

    public final setDaemon(boolean on) throws Exception // 普通函数:是否设置为守护线程
    public  final boolean isDaemon() //普通函数:  判断是否为
    

    观察守护线程操作:

    class MyThread implements Runnable{
    	private int times;
    	public MyThread(int times) {
    		this.times = times;
    	}
    	@Override
    	public void run() {
    		for(int i = 0 ; i<times;i++) {
    			if(Thread.currentThread().isDaemon()) {
    				try {
    					Thread.sleep(10); // 如果是守护线程,则休眠0.01秒钟
    				}catch(InterruptedException e) {
    					e.printStackTrace();
    				}
    			}
    			System.out.println(Thread.currentThread().getName() + " 正在工作中……"+i);
    		}
    	}
    }
    
    
    public class testDemo {
    	public static void main(String[] args) {
    		MyThread mt1 = new MyThread(4);
    		MyThread mt2 = new MyThread(100); //守护线程的循环次数远多于用户线程
    		
    		Thread thread1 = new Thread(mt1,"用户线程");
    		Thread thread2 = new Thread(mt2,"守护线程");
    		thread2.setDaemon(true); //thread2设置为守护线程
    		
    		thread1.start();
    		thread2.start();
    	}
    }
    

    运行情况:

    在这里插入图片描述
    由以上可以了解到,守护线程已经提前结束了,原因是main线程等用户线程全部消失了。

    守护线程的应用场景

    在主线程关闭后无需手动关闭守护线程,因为会自动关闭,避免了麻烦,Java垃圾回收线程就是一个典型的守护线程,简单粗暴地可以理解为所有为线程服务而不涉及资源的线程都能设置为守护线程。

    6. 线程池

    线程池的概念
    容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程的操作,无需反复创建线程而消耗过多的资源。

    为何引入线程池?
    如果并发的线程数量过多,并且每个线程都是执行一个时间很短的任务就结束,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要消耗时间,线程也属于宝贵的系统资源,因此,线程池就是为了能使线程可以复用而创建的。

    线程池的好处?

    • 降低资源的消耗,减少创建和销毁线程的次数,每个工作线程都可以被重复使用,可执行多个任务
    • 提高响应速度,不需要频繁地创建线程,如果有线程可以直接使用,避免了系统僵死
    • 提高线程的可管理性

    核心思想:线程复用

    Runnable简单实例:

    package cn.wu;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Test {
        public static void main(String[] args) {
            // 1.创建一个线程池,指定线程的数量为4
            ExecutorService pools = Executors.newFixedThreadPool(4);
            // 2.添加线程任务
            Runnable target = new MyRunnable();
            pools.submit(target); // 第一次提交任务,此时创建新线程
            pools.submit(target); // 第二次提交任务,此时创建新线程
            pools.submit(target); // 第三次提交任务,此时创建新线程
            pools.submit(target); // 第四次提交任务,此时创建新线程
            pools.submit(target); // 第五次提交任务,复用之前的线程
            pools.shutdown(); // 当所有任务全部完成后才关闭线程池
    //        pools.shutdownNow(); // 立即关闭线程池
    
        }
    }
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for(int i = 0 ; i<10 ; i++) {
                System.out.println(Thread.currentThread().getName()+"正在执行任务…  "+i);
            }
        }
    }
    

    结果:

    在这里插入图片描述
    Callable简单实例:

    package cn.wu;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class Test {
        public static void main(String[] args) {
            // 1.创建一个线程池,指定线程的数量为4
            ExecutorService pools = Executors.newFixedThreadPool(4);
            try{
                long start = System.currentTimeMillis();
                // 2.添加线程任务
                Future<String> t1 = pools.submit(new MyCallable(100000)); // 提交求出1-100000和的线程任务
                Future<String> t2 = pools.submit(new MyCallable(200000)); // 提交求出1-200000和的线程任务
                Future<String> t3 = pools.submit(new MyCallable(300000)); // 提交求出1-300000和的线程任务
                Future<String> t4 = pools.submit(new MyCallable(400000)); // 提交求出1-400000和的线程任务
                Future<String> t5 = pools.submit(new MyCallable(500000)); // 提交求出1-500000和的线程任务
                System.out.println(t1.get());
                System.out.println(t2.get());
                System.out.println(t3.get());
                System.out.println(t4.get());
                System.out.println(t5.get());
    
                long end = System.currentTimeMillis();
                System.out.println("采用多线程所耗时间为:"+(end-start)*1.0/1000+"s");
                start = System.currentTimeMillis();
                long sum = 0;
                for(int i = 1 ; i<=100000 ; i++) {
                    sum += i;
                }
                System.out.println("最终结果为:"+sum);
                sum = 0;
                for(int i = 1 ; i<=200000 ; i++) {
                    sum += i;
                }
                System.out.println("最终结果为:"+sum);
                sum = 0;
                for(int i = 1 ; i<=300000 ; i++) {
                    sum += i;
                }
                System.out.println("最终结果为:"+sum);
                sum = 0;
                for(int i = 1 ; i<=400000 ; i++) {
                    sum += i;
                }
                System.out.println("最终结果为:"+sum);
                sum = 0;
                for(int i = 1 ; i<=500000 ; i++) {
                    sum += i;
                }
                System.out.println("最终结果为:"+sum);
                end = System.currentTimeMillis();
                System.out.println("采用单线程所耗时间为:"+(end-start)*1.0/1000+"s");
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    class MyCallable implements Callable<String> {
        private int num;
        public MyCallable(int num) {
            this.num = num;
        }
        @Override
        public String call() throws Exception {
            long sum = 0;
            for(int i = 1 ; i <= num ; i++) {
                sum += i;
            }
            return Thread.currentThread().getName()+
                    "任务执行的最终结果为:"+sum;
        }
    }
    

    结果:

    在这里插入图片描述

    展开全文
  • C#多线程编程总结

    千次阅读 多人点赞 2020-07-20 11:26:36
    多线程编程的方式在WinForm开发中必不可少。 本文介绍在WinForm开发中如何使用多线程,以及在线程中如何通过Control.Invoke方法返回窗体主线程执行相关操作。 -. WinForm多线程编程 1. new Threa

    在WinForm开发中,我们通常不希望当窗体上点了某个按钮执行某个业务的时候,窗体就被卡死了,直到该业务执行完毕后才缓过来。一个最直接的方法便是使用多线程。多线程编程的方式在WinForm开发中必不可少。
    本文介绍在WinForm开发中如何使用多线程,以及在线程中如何通过Control.Invoke方法返回窗体主线程执行相关操作。

    -. WinForm多线程编程
    1. new Thread()
        新开一个线程,执行一个方法,没有参数传递:
        

    private  void DoWork() {
                Thread t =  new Thread( new ThreadStart( this.DoSomething));
                t.Start();
            }
             private  void DoSomething() {
                MessageBox.Show( " thread start ");
            }


        新开一个线程,执行一个方法,并传递参数:
        

    private  void DoWork() {
                Thread t =  new Thread( new ParameterizedThreadStart( this.DoSomething));
                t.Start( " guozhijian ");
            }
             private  void DoSomething( object o) {
                MessageBox.Show(o.ToString());
            }

        参数定义为object类型。
    2. ThreadPool
        众所周知,新开一个线程代价是很高昂的,如果我们每个操作都新开一个线程,那么太浪费了,于是,下面使用线程池。
        无参数传递:
        

    private  void DoWork() {
                ThreadPool.QueueUserWorkItem( new WaitCallback( this.DoSomething));
            }
             private  void DoSomething( object o) {
                MessageBox.Show( " thread start ");
            }

        有参数传递:
        

    private  void DoWork() {
                ThreadPool.QueueUserWorkItem( new WaitCallback( this.DoSomething),  " guozhijian ");
            }
             private  void DoSomething( object o) {
                MessageBox.Show(o.ToString());
            }

        使用匿名方法更灵活:
        

    private  void DoWork() {
                 string name =  " guozhijian ";
                ThreadPool.QueueUserWorkItem( new WaitCallback( delegate( object o){
                    MessageBox.Show(name);
                }));
            }

        在匿名代码段里面可以直接访问局部变量,不用在关心参数传递的问题
    二. Invoke
    1. this.Invoke
    现在,在业务线程里面执行完毕,要改变窗体控件的值了,此时,如果直接通过this得到控件的句柄,然后对它进行操作是会抛异常的,.Net WinForm Application里面是不允许这样的操作的。这是,可以调用Invoke方法

    2.Invoke方法签名:
    object Control.Invoke(Delegate Method)
    object Control.Invoke(Delegate Method, params object[] args)

    3.使用自定义委托

    private  void DoWork() {
                WaitCallback wc =  new WaitCallback( this.DoSomething);
                ThreadPool.QueueUserWorkItem(wc,  " Guozhijian ");
            }

             private  delegate  void MyInvokeDelegate( string name);
             private  void DoSomething( object o) {
                 this.Invoke( new MyInvokeDelegate( this.ChangeText), o.ToString());
            }

             private  void ChangeText( string name) {
                 this.textBox1.Text = name;
            }

    哦,太麻烦了,难道我每次都要定义一个委托啊,这样可不行。

    4.使用System.Action:

     

    private  void DoWork() {
                WaitCallback wc =  new WaitCallback( this.DoSomething);
                ThreadPool.QueueUserWorkItem(wc,  " Guozhijian ");
            }

             private  void DoSomething( object o) {
                 this.Invoke( new Action< string>( this.ChangeText), o.ToString());
            }

             private  void ChangeText( string name) {
                 this.textBox1.Text = name;
            }

    本例传递一个参数,System.Action有很多个重载,可以无参数(非泛型),而最多可以有四个参数,同样采用匿名方法,不使用泛型形式的System.Action,如下:

     

     

    private  void DoWork() {
                WaitCallback wc =  new WaitCallback( this.DoSomething);
                ThreadPool.QueueUserWorkItem(wc,  " Guozhijian ");
            }

             private  void DoSomething( object o) {
                 this.Invoke( new Action( delegate() {
                     this.textBox1.Text = o.ToString();
                }));
            }


    5.使用System.Func
    如果Invoke调用主窗体操作之后,还希望在调用完得到一个返回值:

     

     

    private  void DoWork() {
                WaitCallback wc =  new WaitCallback( this.DoSomething);
                ThreadPool.QueueUserWorkItem(wc,  " Guozhijian ");
            }

             private  void DoSomething( object o) {
                System.Func< stringint> f =  new Func< stringint>( this.GetId);
                 object result =  this.Invoke(f,o.ToString());
                MessageBox.Show(result.ToString());
            }

             private  int GetId( string name) {
                 this.textBox1.Text = name;
                 if (name ==  " Guozhijian ") {
                     return  999;
                }
                 else {
                     return  0;
                }
            }


    result的值为 999。
    System.Func同样有很多泛形重载,这里不赘述。

    6.关于Invoke的拥有者:Control

     

     

    本文例中都是用this来引用,这里this替换为窗体任何一个控件的句柄都是OK的,因为Control.Invoke含义是将方法委托给拥有该Control的线程去执行。

     

    很不错的文章。想当时自己学习多线程的时候绕了很久。

    展开全文
  • Java多线程编程Java多线程编程Java多线程编程Java多线程编程Java多线程编程Java多线程编程
  • Windows C++ 多线程编程示例

    千次阅读 2022-04-26 09:22:27
    题目:主线程创建两个辅助线程,辅助线程1使用选择排序算法对数组的前半部分排序,辅助线程2使用选择排序算法对数组的后半部分排序,主线程等待辅助线程运行結束后,使用归并排序算法归并子线程的计算结果 ...
  • C++之多线程编程

    千次阅读 2022-03-16 00:15:32
    2. 单进程中的线程并发(一个主线程+个子线程实现并发) ①一个进程中的所有线程共享内存空间 eg:全局变量,指针引用 二、线程的多种创建方式 1. 调用thread类去创建一个线程对象 ...
  • 【Android基础】多线程编程

    千次阅读 2022-04-05 23:12:57
    Android的多线程编程基本是和Java多线程编程使用相同的语法。 继承方式 定义一个线程,只需要新建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可。 class MyThread extends Thread { ...
  • 三、树莓派Pico 开发板MicroPython多线程编程实践举例 3.1 Pico RP2040双核芯片多线程同步的基本原理 3.2 Pico MicroPython多线程编程实践举例 3.3 Pico RP2040 MCU芯片双核MicroPython多线程编程小结 一、多线程...
  • 下载 多线程编程技术开发资料 高清完整PDF版

    千次下载 热门讨论 2017-05-16 20:01:41
    多线程编程技术开发资料.pdf 个人收集电子书,仅用学习使用,不可用于商业用途,如有版权问题,请联系删除!
  • Python多线程编程(详细:适合小白入门)

    千次阅读 多人点赞 2021-08-11 16:42:09
    前言 这篇博客的笔记摘录自B站黑马程序员的《python多线程编程》的视频,原视频的地址如下:(博客中的代码全是我手敲的,运行皆可通过,若有错误,欢迎评论指出) ... 在学习多线程编程之前,需要学会:基础语法、...
  • Linux多线程编程

    千次阅读 2019-06-21 17:40:29
    作为多任务实现的一种机制,多线程应用得非常广泛,相对于多进程,多线程不仅运行效率高,...一、多线程编程常用函数 1. int pthread_create(pthread_t * thread,const pthread_attr_t * attr,void * (*start_ro...
  • C#多线程编程实战Code源代码

    热门讨论 2015-05-31 12:26:21
    C#多线程编程实战Code源代码 资源是从华章出版社官网下载的
  • C++多线程编程实战 姜佑译.pdf

    千次下载 热门讨论 2017-02-18 10:07:53
    C++多线程编程实战 姜佑译.pdf
  • 【java多线程编程】三种多线程的实现方式

    万次阅读 多人点赞 2019-01-01 16:20:56
    文章目录前言进程与线程继承Thread类,实现多线程FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?基于Runnable接口实现多线程Thread 与 Runnable 的关系Callable实现多线程线程...
  • 作为开发者,我们除了能够熟练使用常用的多线程编程技术外,对于复杂场景的多线程及决方案也要有所了解,例如互相依赖的任务的线程分配,多队列组的应用、死锁场景的分析和优化等。 本课程将详细介绍pthread、...
  • java多线程编程实例

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

    2011-05-29 23:26:04
    Windows多线程编程Windows多线程编程Windows多线程编程Windows多线程编程Windows多线程编程Windows多线程编程Windows多线程编程
  • 《C#多线程编程实战》读书笔记

    千次阅读 多人点赞 2018-01-13 00:26:49
    本文是一篇读书笔记,由《C#多线程编程实战》一书中的内容整理而来,主要梳理了.NET中多线程编程相关的知识脉络,从Thread、ThreadPool、Task、async/await、并发集合、Parallel、PLINQ到Rx及异步I/O等内容,均有所...
  • c 多线程编程01

    千次阅读 2018-10-21 00:53:57
    Java 老师希望我们尝试进行 Java 的多线程编程,也希望我们能够去实现一下 C 语言的多线程编程。用以体会不同编程语言间的多线程编程。借此机会,初步学习一下 C 语言的多线程编程。 第一部分主要内容如下: ...
  • Linux多线程服务端编程:使用muduo C++网络库(陈硕 著).pdf
  • Linux 多线程编程 (典藏、含代码)

    千次阅读 多人点赞 2020-02-11 17:31:40
    目录 1.基础知识 2. 相关函数 2.1创建线程 ----------- pthread_create ...2.3等待线程结束 ----------- pthread_join / 线程的分离 ----------- pthread_detach 2.4线程退出 2.4.1 正常退出pthr...
  • 2.多线程简介 3.多线程实现LED闪烁+计时 4.总结 1.SCoop库下载   【点击这里】下载SCoop库,解压后放在arduino安装目录下的libraries文件夹下即可。     2.多线程简介   看了上一篇《arduino面向对象编程》...
  • Windows下C++多线程编程(入门实例)

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

    千次阅读 2016-05-22 13:27:18
    1、多线程的发展背景 随着计算机的发展,无论是硬件还是软件都在快速的发展。 在最开始的时候,计算机都是只有一个cpu来进行指令控制和运算,程序执行的时候都是一个进程一个进程的运行,也就是顺序执行的方式,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 789,572
精华内容 315,828
关键字:

多线程编程

友情链接: labe.zip