9操作系统——线程的相关属性

Posted weixin_45981798

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9操作系统——线程的相关属性相关的知识,希望对你有一定的参考价值。

目录

一、线程相关属性的API

1、线程的特殊属性

2、设置线程的属性

3、注意事项

二、线程属性变量的使用步骤

三、相关的API

1、pthread_attr_init/pthread_attr_destroy(初始化、销毁线程属性变量)

2、pthread_attr_setdetachstate、pthread_attr_getdetachstate(获取、设置线程的分离属性)

3、使僵尸进程被系统回收,获取其退出的值的API

(1) pthread_exit(退出线程)

(2)pthread_join、pthread_tryjoin_np(接合指定线程)

四、结合属性代码

五、分离属性的线程结合

一、线程相关属性的API

1、线程的特殊属性

2、设置线程的属性

3、注意事项

  • 上述的API都是针对线程属性操作,线程属性是类型为 pthread_attr_t 的变量
  • 设置一个线程的属性时,通过以上的函数接口,将需要的属性添加到该类型变量里面,再通过pthread_create( )的第二个参数来创建相应属性的线程
  • 设置线程的属性必须要在创建先成功之前

二、线程属性变量的使用步骤

a、定义线程属性变量,并且使用 pthread_attr_init( )初始化。

b、使用 pthread_attr_setXXX( )来设置相关的属性。

c、使用该线程属性变量创建相应的线程。

d、使用 pthread_attr_destroy( )销毁该线程属性变量。

三、相关的API

1、pthread_attr_init/pthread_attr_destroy(初始化、销毁线程属性变量)

2、pthread_attr_setdetachstate、pthread_attr_getdetachstate(获取、设置线程的分离属性)

注意

(1)线程缺省的情况下是接合的

(2)线程若是接合的,意味着线程在退出时不会自动释放自身资源,会成为僵尸进程,则 该线程的推出值可以被其他线程获取。

(3)如果不需要某线程的退出值,则将线程设置为分离状态,保证线程不会成为僵尸进程

3、使僵尸进程被系统回收,获取其退出的值的API

缺省状态下退出后,会成为僵尸进程,并且保留退出值

其他线程可以通过pthread_exit使其资源被系统回收,还可以获取退出的值

(1) pthread_exit(退出线程)

缺省状态下退出后,会成为僵尸进程,并且保留退出值

其他线程可以通过pthread_exit使其资源被系统回收,还可以获取退出的值

(2)pthread_join、pthread_tryjoin_np(接合指定线程)

注意:
(1)如果线程退出时没有退出值,那么 retval 可以指定为 NULL。

(2)pthread_join( )指定的线程如果尚在运行,那么他将会阻塞等待。

(3)pthread_tryjoin_np( )指定的线程如果尚在运行,那么他将会立即出错返回。

四、结合属性代码

#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

void * func (void * arg)

    while(1)
    
        printf("这里是func线程,线程ID :%ld \\n" , pthread_self() );
        sleep(3);
        break ;
    

    int * p = calloc(1,4);
    *p = 1024444 ;
    // 退出本线程并设置返回值的地址(返回了num 的地址)
    pthread_exit((void *)p); //返回的内存地址应该时一个堆空间


int main(int argc, char const *argv[])

    
    // 创建线程
    pthread_t t_id  = -1 ;
        
    pthread_create( &t_id , //新线程ID号
                    NULL , // 线程属性, NULL 默认属性
                    func,  // 线程需要执行的例程(新线程需要执行的任务《函数》) 
                    NULL ); // 线程的参数

    printf("t_id : %ld\\n" , t_id) ;

    printf("这里是主函数,线程ID :%ld \\n" , pthread_self() );

    int * retval ;
    // 阻塞等待接合线程
    printf("等待 function 线程退出:\\n");
    pthread_join( t_id , (void*)&retval);
    printf("结合线程成功, 退出值为:%d\\n" , *retval);

    // 尝试接合线程  (非阻塞)
    // int ret_val = 0 ;
    // if( ret_val = pthread_tryjoin_np( t_id , (void*)&retval))
    // 
    //     fprintf(stderr , "接合失败:%s\\n" , strerror(ret_val));
    // 

    return 0;

若线程退出,则退出值为*p的值

五、分离属性的线程结合

#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

void * func (void * arg)

    while(1)
    
        printf("这里是func线程,线程ID :%ld \\n" , pthread_self() );
        sleep(3);
        break ;
    

    int * p = calloc(1,4);
    *p = 1024444 ;
    // 退出本线程并设置返回值的地址(返回了num 的地址)
    pthread_exit((void *)p); //返回的内存地址应该时一个堆空间


int main(int argc, char const *argv[])


    // 创建线程属性变量
    pthread_attr_t attr ;
    // 初始化线程属性
    pthread_attr_init(&attr);
    // 设置属性信息 (分离属性)
    if(pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED))
    
        printf("设置分离属性失败!!\\n");
    
    else
        printf("设置分离属性成功!!\\n");
    

    
    // 创建线程
    pthread_t t_id  = -1 ;
        
    pthread_create( &t_id , //新线程ID号
                    &attr , // 线程属性(当前设置为分离)
                    func,  // 线程需要执行的例程(新线程需要执行的任务《函数》) 
                    NULL ); // 线程的参数

    printf("t_id : %ld\\n" , t_id) ;

    printf("这里是主函数,线程ID :%ld \\n" , pthread_self() );

    int * retval ;
    int ret_val = 0 ;
    // 阻塞等待接合线程
    printf("等待 function 线程退出:\\n");
    if(ret_val = pthread_join( t_id , (void*)&retval))
    
        fprintf(stderr , "接合失败:%s\\n" , strerror(ret_val));
        return -1 ;
    
    printf("结合线程成功, 退出值为:%d\\n" , *retval);

    // 尝试接合线程  (非阻塞)
    // int ret_val = 0 ;
    // if( ret_val = pthread_tryjoin_np( t_id , (void*)&retval))
    // 
    //     fprintf(stderr , "接合失败:%s\\n" , strerror(ret_val));
    // 

    return 0;

输出:结合失败:Invalid atgument

设置为分离属性了,不可以结合

 

复习多线程相关知识

多线程

操作系统相关

冯诺依曼体系结构

输⼊单元:包括键盘,⿏标,扫描仪,写板等
寄存器:内存
中央处理器(CPU):含有运算器和控制器等
输出单元:显示器,打印机等

总结:所有设备都只能直接和内存打交道

进程

概念

系统分配资源的最⼩单位,⼀个任务就是⼀个进程

进程的组成

  1. PID:进程的唯⼀身份标识。
  2. 进程状态包括: 新建状态 就绪状态 运⾏状态 阻塞状态 销毁状态
  3. 优先级:决定进程的执⾏顺序。
  4. 记账信息:为了保证进程执⾏的相对公平。
  5. 上下⽂:保存本次的执⾏状态,以便下次继续执⾏,整个过程就称之为⼀个上下⽂。
  6. ⼀组内存:指定进程需要使⽤的资源。

进程状态

  • 就绪:进程处于可运行的状态,只是CPU时间片还没有轮转到该进程,则该进程处于就绪状态。
  • 运行:进程处于可运行的状态,且CPU时间片轮转到该进程,该进程正在执行代码,则该进程处于运行状态。
  • 阻塞:进程不具备运行条件,正在等待某个事件的完成

时间片

任务执⾏的⼀⼩段时间叫做时间⽚,任务正在执⾏的状态叫做运⾏状态,任务暂停叫做就绪状态,就绪状态的任务要等待下⼀个时间⽚。

并发与并行

  • 并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间之内,让多个进程都得以推进, 称之为并发(只有一个资源,轮流执行就叫并发运行)
  • 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行(所有的应用一起运行,就叫并行运行)

内核态与用户态

  • 操作系统内核作为直接控制硬件设备的底层软件,权限最高,称为内核态,或核心态。
  • 用户程序的权限最低,称为用户态

线程

概念

系统执⾏的最⼩单位,它被包含在进程之中,是进程中的实际运作单位。

线程优势

线程的创建和消耗⽐进程成本低很多,效率更⾼,⽽且同⼀个进程中的多个线程是可以共享资源⽂件的,⽽进 程和进程之间是不能共享资源的,因此这就是线程的诞⽣的意义。

共享资源

线程间可以共享的资源:

  1. 内存可以共享
  2. 打开的⽂件可以共享

线程间不可⽤共享的资源:

  1. 线程的上下⽂、状态、优先级、记账信息不共享。
  2. 每个线程有⼀个栈空间不共享。

进程 vs 线程

1.进程是资源分配的最小单位,线程是系统调度的最小单位
2.一个进程中至少包含一个线程,线程是依附线程存在的,而进程的实际执行单位是线程
3.进程之间不能共享资源,而线程之间可以共享资源

线程的创建方式

  1. 继承 Thread 重写 run ⽅法实现;
  2. 实现 Runnable 接⼝重写 run ⽅法实现;
  3. 实现 Callable 接⼝重写 call ⽅法实现。

线程的构造函数

  1. 定义线程名称
  2. 定义线程分组
  3. 定义线程的任务

常见属性

  1. 定义优先级(5) 1-10
  2. 设置线程名称
  3. 定义线程类型
    a)守护线程
    b)用户线程

常用方法

  1. 启动 start / run
  2. 休眠 sleep
  3. 等待执行完 join
  4. 线程通讯 wait / notify / notifyAll | LockSupport park / unpark
  5. 出让cpu执行权:yiled

注意事项:
a)wait 要配合 synchronized 使用
b)wait 和synchorized 必须要保证是一个对象

start VS run

  1. run 为普通⽅法,start 启动线程的⽅法;
  2. run 可以执⾏多次,⽽ start 只能执⾏⼀次;
  3. start 会开启新线程执⾏,⽽ run 使⽤当前线程执⾏。

wait VS sleep
⼆者的相同点:

  1. 都可以让线程休眠;
  2. 都可以响应 interrupt 的响应。

⼆者的不同点:

  1. wait 必须在 synchronized 中使⽤,⽽ sleep 却不⽤;
  2. sleep 是 Thread 的⽅法,⽽ wait 是 Object 的⽅法;
  3. sleep 不释放锁,wait 释放锁;
  4. sleep 有明确的终⽌等待时间,⽽ wait 有可能⽆限期的等待下去;
  5. sleep 和 wait 产⽣的线程状态是不同的,sleep 是 TIMED_WAITING 状态,⽽ wait 是 WAITING 状态。

线程终止

  1. 使用全局变量
  2. 使用 interrupte() 终止线程
  3. stop 【已过时】

isInterrupted VS interrupted

  • interrupted:静态⽅法,判断当前线程的中断标志位是否设置,调⽤后清除标志位;
  • isInterrupted:实例⽅法,判断对象关联的线程的标志位是否设置,调⽤后不清除标志位。

线程状态图

线程安全问题

导致线程不安全的原因

  • ① 抢占式执⾏
  • ② ⾮原⼦操作
  • ③ 内存可⻅性
  • ④ 指令重排序
  • ⑤多个线程同时修改同⼀个变量

线程安全解决方案

  1. volatile :解决内存可见性问题和禁止指令重排序
  2. 加锁
    a)synchronized
    b)Lock
  3. ThreadLocal

synchronized

实现原理

  1. 在操作系统层面来说,它是使用互斥锁来实现的。
  2. 在JVM层面来说,使用 synchronized 修饰代码的时候会在编译之后在代码的前面和后面加上监视器锁。
  3. 在 Java 层面来说,他就是将锁信息存在对象头中,每次判断线程 id 和对象头中的锁 id 是否相同,相同则表示为拥有者。

执行流程 / 锁优化

(JDK1.6 后)
四个阶段:

  1. 无锁:在没有人访问的时候是无锁的
  2. 偏向锁:第一次有人访问,升级为偏向锁,在对象头中把线程 id 记录到对象头中
  3. 轻量级锁:如果再次访问的对象不是锁的拥有者,则自旋变为轻量级锁
  4. 重量级锁:多次自旋还没有得到锁,升级为重量级锁

Lock

lock.lock(); 
try 
 
 finally  
	lock.unlock() 

  1. lock 放在 try 上⾯;
  2. finally ⾥⾯⼀定要释放锁。

synchronized VS Lock

  1. synchronized 是 JVM 提供的解决方案
  2. synchronized 和 Lock 修饰的代码块范围不同,Lock 只能修饰代码块,⽽ Synchronized 可以修饰⽅法、静态⽅法、代码块;
  3. synchronized 无需是手动加锁和释放锁,而 Lock 需要
  4. synchronized 是非公平锁,而 Lock 既可以是公平锁也可以是非公平锁

死锁

定义

死锁是指两个或两个以上的线程在执⾏过程中,由于竞争资源或者由于彼此等待⽽造成的⼀种阻塞的现 象。

条件

  1. 互斥条件:⼀个资源只能被⼀个线程占有,当这个资源被占⽤之后其他线程就只能等待。
  2. 请求拥有条件:线程已经拥有了⼀个资源之后,有尝试请求新的资源。
  3. 不可剥夺条件:当⼀个线程不主动释放资源时,此资源⼀直被拥有线程占有。
  4. 环路等待条件:产⽣死锁⼀定是发⽣了线程资源环形链。

解决方案

通过破坏条件 ② 或者 条件 ④ 来解决死锁的问题

线程池

使用池化技术来管理线程

优点

  1. 线程数量和任务数量可控
  2. 线程池可以友好的拒绝任务
  3. 可以复用线程
  4. 线程池拥有更多功能,比如执行定时任务。

创建方式

  1. 创建固定线程数的线程池;
  2. 创建带缓冲的线程池;
  3. 创建延迟任务线程池;
  4. 创建单个延迟任务线程池;
  5. 创建单个线程池;
  6. 根据当前 CPU 和任务创建线程池(JDK 1.8);
  7. 原⽣创建线程池的⽅式 ThreadPoolExecutor;

ThreadPoolExecutor

七大参数

  1. 核心线程数
  2. 最大线程数
  3. 最大存活使劲啊
  4. 存活时间的时间单位
  5. 任务队列
  6. 线程工厂(设置优先级、命名规则等)
  7. 拒绝策略

执行流程

添加一个任务

  1. 核心线程数未满 —— 创建一个线程执行任务
  2. 检测任务队列未满 —— 将任务存放到队列
  3. 最大线程数是未满 —— 创建线程执行任务
  4. 拒绝策略

拒绝策略

JDK 提供 4 种

  1. 默认执行策略,不执行任务抛出异常
  2. 使用主线程执⾏来执行任务
  3. 放弃任务
  4. 放弃最老任务

自定义拒绝策略

线程池的状态

  • RUNNING -> SHUTDOWN:当调⽤了 shutdown() 后,会发⽣这个状态转换,这也是最重要的;
  • (RUNNING or SHUTDOWN) -> STOP:当调⽤ shutdownNow() 后,会发⽣这个状态转换,这下 要清楚 shutDown() 和 shutDownNow() 的区别了;
  • SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING;
  • STOP -> TIDYING:当任务队列清空后,发⽣这个转换; TIDYING -> TERMINATED:这个前⾯说了,当 terminated() ⽅法结束后。

ThreadLocal

线程级别的私有变量

常⽤⽅法

  1. set
  2. get
  3. remove
  4. initialValue
  5. withInitial

典型使⽤场景

  1. 解决线程安全问题;
  2. 线程级别的数据传递。

注意事项

  1. ThreadLocal 使用的时候必须要进行 remove ,否则会造成脏读、内存溢出的问题
  2. 如果 ThreadLocal 默认情况下线程是得不到父线程的数据的【解决⽅案:InheritableThreadLocal】

实现原理

Thread -> ThreadLocal -> ThreadLocalMap -> Entry -> key,value

ThreadLocal 哈希处理

ThreadLocalMap 使⽤的是开放地址法来解决哈希冲突的,不像 HashMap使⽤的是链表法来解决哈希冲突。

单例模式

  1. 将构造函数设置成私有
  2. 创建一个私有的对象
  3. 创建一个公共提供单例对象的方法

DCL双重效验锁

锁策略

乐观锁

它认为一般情况下不会发生并发冲突,所以只有在进行数据更新的时候,才会检测并发冲突

CAS

”比较并交换“,是乐观锁的一种机制

组成:
V:内存值
A:旧值
B:新值

机制:
V == A ?
true(没有并发冲突) -> V = B :
false(并发冲突) -> A = B , continue
如果内存中的值等于旧值,则没有并发冲突,直接将旧值替换为新值即可
如果内存中的值不等于旧值,则进行自旋,将自己的旧值修改为内存值,然后再次进行比较

ABA问题


解决ABA问题
增加版本号,每次修改之后更新版本号

悲观锁

它认为通常情况下会出现并发冲突,所以它一开始就会加锁

synchronized就是悲观锁

共享锁/非共享锁(独占锁)

共享锁:一把锁可以被多个程序拥有,这就叫共享锁

非共享锁:一把锁只能被一个线程拥有,这就叫非共享锁

共享锁有哪些?读写锁中的读锁

非共享锁有哪些?synchronized锁

读写锁

定义:就是将一把锁分为两个,一个用于读数据的锁(也叫做读锁),另一把锁叫作写锁。

特点:读锁是可以被多个线程同时拥有的,而写锁则只能被一个线程拥有

java中的读写锁:ReentrantReaderWriterLock(默认是非公平的,true-公平)

读写锁的优势:锁的粒度更加小,性能也更高

读写锁中的读锁和写锁是互斥的

公平锁和非公平锁

公平锁:锁的获取顺序必须和线程方法的先后顺序保持一致,就叫做公平锁

非公平锁:锁的获取顺序和线程获取锁的前后顺序无关,就叫做非公平锁(默认所策略)

非公平锁的优点:性能比较高

公平锁的优点:执行是有序的,所以结果也是可以预期的

自旋锁

通过死循环一直尝试获取锁

synchronized (轻量级锁) 是自旋锁

可重入锁

当一个线程获取到一个锁之后,可以重复的进入

代表:synchronized,Lock

JUC 包

  1. 可重入互斥锁 ReentrantLock
  2. 信号量 Semaphore -》 实现限流 acquire() 和 release()
  3. 计数器 CountDownLatch -》 保证一组线程同时完成某个任务 await() 和 countDown()
  4. 循环屏障 CyclicBarrier -》 可以多次进行计数

HashMap、ConcurrentHashMap 、Hashtable 区别

  1. HashMap 是非线程安全的容器,他在 JDK1.7 会造成死循环,JKD1.8 会造成数据覆盖;Hashtable 和 ConcurrentHashMap 都是线程安全的。
  2. Hashtable 实现线程安全的手段比较简单,它是再 put 方法上整体加了一把锁,使用 synchronized 修饰,因此性能不高,所以使用频率比较低;而 ConcurrentHashMap 是 HashMap 在多线程下的替代方案,它在 JDK 1.7 的时候使用的 Lock 加分段锁的方案来实现线程安全问题的保障的,而在 JKD 1.8 的时候使用了大量的 CAS、volatile 来实现线程的,并且在 JDK 1.8 的时候读取的时候不加锁(读取的数据可能不是最新的,因为读取和写入可以同时进行),只有在写的时候才加锁。

以上是关于9操作系统——线程的相关属性的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统编程-(pthread)线程的使用案例(分离属性清理函数等)

Linux 用户文件属性等相关练习

Linux之线程线程控制线程属性

Linux多任务编程——线程

linux 文件属性

linux服务器开发二(系统编程)--线程相关