线程-线程同步

我们知道线程共享同一进程内的资源。如果每个线程使用的变量,其他线程都不会读取个修改,那么就不存在一致性问题。相同的,如果变量只读,多个线程同时读取该变量也不会存在一致性问题。但是,当一个线程可以修改变量,其他线程可以读取或修改变量,那么就要对这些线程进行同步,确保多个线程访问数据安全,不会访问到无效数据。
两个或多个线程同时修改同一变量时,也需要同步。跟前面信号讲的问题相同,考虑增量操作情况。增量操作通常可以分为三个步骤:
(1)从内存单元中读入寄存器
(2)从寄存器变量进行增量操作
(3)将寄存器的值写回内存
这就和信号量当时的可重入性是同样的问题。
对于多线程程序,访问冲突的问题是很普遍的,解决的办法就是引入互斥锁。

互斥量

互斥量本质来说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放 (解锁)互斥量。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程都会被阻塞,直到当前线程释放该互斥锁。
互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量之前,比如首先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(静态分配),也可以调用pthread_mutex_init函数进行初始化。动态分配互斥量,释放前需要调用pthread_mutex_destory。

 #include <pthread.h>

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
       pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
       两函数返回值:若成功,返回0;否则返回错误编码

attr设置为NULL,用默认初始化互斥量。

加锁解锁函数:
对互斥量进行加锁,调用pthread_mutex_lock。如果互斥量已经上锁,调用线程将会阻塞,知道互斥量被解锁。对互斥量解锁,嗲用pthread_mutex_unlock。

include <pthread.h>
       int pthread_mutex_lock(pthread_mutex_t *mutex);//
       int pthread_mutex_trylock(pthread_mutex_t *mutex);
       int pthread_mutex_unlock(pthread_mutex_t *mutex);
     返回值:若成功,返回;否则,返回错误编码

当然如果线程不希望被阻塞,可以调用trylock对互斥量加锁。

/*************************************************************************
    > File Name: mutex.c
    > Author: 
    > Mail: 
    > Created Time: Thu 15 Jun 2017 11:35:04 PM PDT
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int count=0;
void *thread(void *tval)
{
    int i=0;  
    while(i<5001)
    {
        int tmp=count;
        i++;
        printf("pthread:%lu,count:%d\n",pthread_self(),count);
        count=tmp+1;
    }  
    return NULL;
}
int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1,NULL,&thread,NULL);
    pthread_create(&tid2,NULL,&thread,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("count:%d",count);
    return 0;
}

这里写图片描述
很明显这不对,两个线程tid1和tid2在跑while中的逻辑的时候,这个时候就会发生访问冲突,count本来应该是10001的,但是因为访问冲突,所以最终的结果count结果要小于10000,原因上面我已经介绍过了,就不再重复,要想实现互斥,我们加上互斥锁就好了。
使用互斥量之后:

/*************************************************************************
    > File Name: mutex.c
    > Author: 
    > Mail: 
    > Created Time: Thu 15 Jun 2017 11:35:04 PM PDT
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int count=0;
static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *thread(void *tval)
{
    int i=0;  
    while(i<5001)
    {
        pthread_mutex_lock(&mutex);
        int tmp=count;
        i++;
        printf("pthread:%lu,count:%d\n",pthread_self(),count);
        count=tmp+1;
        pthread_mutex_unlock(&mutex);
    }  
    return NULL;
}
int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1,NULL,&thread,NULL);
    pthread_create(&tid2,NULL,&thread,NULL)    pthread_join(tid2,NULL);
    pthread_join(tid1,NULL);
     pthread_join(tid2,NULL);
    printf("coun`
:%d",count);
    return 0;
}

这里写图片描述
加入互斥锁之后的值才是对的。

死锁

什么是死锁?
所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
什么时候可能产生死锁?
(1)当一个线程对互斥量加锁两次。
(2)两个线程都互相加锁,并且不释放。因为两个都在互相请求另一个线程资源,所以两线程都无法向下运行,也是产生死锁。
产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
怎么避免死锁呢?
(1)加锁顺序(线程按照一定的顺序加锁)
(2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
(3)死锁检测(每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph)等等将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。
当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生)

条件变量

条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件。
条件变量相关函数和互斥量相关函数大同小异。

  • 初始化和释放条件变量
 #include <pthread.h>

       int pthread_cond_destroy(pthread_cond_t *cond);
       int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
       返回值:若成功,返回0;否则,返回错误编号

和互斥量一样,可以直接用宏来初始化。attr可以为NULL。

  • 等待条件变量为真
#include <pthread.h>

       int pthread_cond_timedwait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abstime);
       int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);

     返回值:若成功,返回0;否则,返回错误编码

传递给pthread_cond_wait的互斥量对条件进行保护。调用者把锁住的互斥量传递给函数,函数然后自动把调用线程放到等待条件的线程列表上,并对互斥量解锁。
pthread_cond_timedwait函数功能与pthread_cond_wait函数相似,只是多了一个超时。

  • 通知线程条件满足
 #include <pthread.h>

       int pthread_cond_broadcast(pthread_cond_t *cond);
       int pthread_cond_signal(pthread_cond_t *cond);
       返回值:若成功,返回0;否则,返回错误编码

下篇通过一个基于消费者生产者的模型来做例子。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 您好!如果我理解正确的话,您的问题是关于JMU Java 07多线程同步访问的。以下是我的回答: 多线程编程是指在一个程序中同时执行多个线程的编程技术。多线程编程在提高程序性能、增强程序交互性等方面有很大的优势,但也会引入一些问题,比如线程安全问题。 当多个线程同时访问共享资源时,可能会出现线程不安全的问题,比如数据竞争、死锁等。为了避免这些问题,我们需要使用同步机制来确保线程安全。 Java中提供了多种同步机制,比如synchronized关键字、ReentrantLock类、Semaphore类等。这些同步机制都可以用来保证多线程访问共享资源的安全性。 其中,synchronized是最常用的同步机制之一。在Java中,synchronized关键字可以用来修饰方法或代码块,以实现同步访问共享资源。synchronized可以保证在同一时间只有一个线程可以访问共享资源,从而避免了数据竞争等线程安全问题。 除了synchronized之外,Java还提供了一些其他的同步机制,比如ReentrantLock类和Semaphore类。ReentrantLock类提供了比synchronized更灵活的锁机制,可以实现可重入锁、公平锁等功能。Semaphore类可以控制同时访问共享资源的线程,从而避免资源被过度占用的问题。 总的来说,多线程编程是一项复杂而重要的技术,需要仔细研究和实践。在实际开发中,我们需要根据具体的需求选择合适的同步机制来确保多线程访问共享资源的安全性。 ### 回答2: 多线程编程是在当前计算机领域中最为常见的技术之一,它可以利用计算机中的多核处理器来使程序运行更加高效。但是,多线程编程中可能会出现的最大问题就是线程安全,因为线程之间可能会访问相同的资源,从而导致竞态条件。 在Java中,可以通过使用synchronized关键字来实现同步访问,从而避免线程安全问题。synchronized关键字可以用于两种不同的情形:同步方法和同步块。在同步方法中,方法是同步的,即每个线程在执行该方法时都需要获取该对象的锁,如果该锁已经被其他线程获取,则需要等待直到此锁被释放。在同步块中,需要手动指定锁,即每个线程在执行同步块时需要获取该指定锁,其他线程如果需要访问该代码块中的共享资源也需要获取该指定锁,这样就保证了该代码块中的所有共享资源的同步访问。 除了synchronized关键字外,Java还提供了其他一些同步机制来实现线程安全,如ReentrantLock类和CountDownLatch类等。ReentrantLock类可以实现更为灵活的同步访问控制,但需要手动释放锁;而CountDownLatch类则用于同步一个或多个线程,使这些线程在某个条件满足之前一直处于等待状态。 在进行多线程编程时,应该尽避免对同步访问造成瓶颈,应该通过减小同步代码块的范围等方式来提高程序的效率。此外,多线程编程时还应该进行线程安全性的测试,以确保程序能够正确地运行。 ### 回答3: 在Java中,多线程是一种非常常见的编程方式。由于多线程的特点,对共享资源的访问会出现竞争的情况,这种竞争可能会导致数据不一致或程序异常等问题。因此,在多线程编程中,我们需要采取一些措施来保证共享资源的访问能够正确、有序地进行,这就是同步机制同步机制包括两种方式:锁和信号。锁是最基本的同步机制。锁有两种类型:互斥锁(Mutex)和读写锁(ReadWriteLock)。互斥锁用于保护共享资源,保证同一时间只有一个线程可以访问它,其他线程需要等待锁释放后才能继续访问。读写锁用于读写分离场景,提高了多线程访问共享资源的并发性。读写锁支持多个线程同时读取共享资源,但只允许一个线程写入共享资源。 信号是一种更加高级的同步机制。信号可以用来控制并发线程数和限制访问共享资源的最大数。在Java中,Semaphore类提供了信号的实现。Semaphore可以控制的线程可以是任意的,线程可以一起执行,也可以分批执行。 除了锁和信号,Java还提供了一些其他同步机制,比如阻塞队列、Condition等。阻塞队列是一种特殊的队列,它支持线程在插入或者删除元素时阻塞等待。Condition是一种锁的增强,它可以让线程在某个特定条件下等待或者唤醒。 在多线程编程中,使用同步机制需要注意以下几点。首先,同步机制要尽可能的保证资源访问的公平性,避免因为某些线程执行时间过长导致其他线程等待时间过长。其次,同步机制要尽可能的避免死锁的发生,尤其要注意线程之间的依赖关系。最后,同步机制的实现要尽可能地简单,避免过于复杂的代码实现带来的维护成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值