精华内容
下载资源
问答
  • Java面试--线程同步方法

    千次阅读 2018-08-12 11:37:17
    面试题:线程同步有几种方法(百度面试题) 面试题:线程安全解释一下(大疆面试题) 为什么要线程同步? 当使用多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的...

    面试题:线程同步有几种方法(百度面试题)
    面试题:线程安全解释一下(大疆面试题)

    为什么要线程同步?
    当使用多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。
    举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。
    演示一下上述不同步的银行账户的例子:
    银行类:

    public class Bank {
        private int count =0;//账户余额
        public  void addMoney(int money){   //存钱
            count +=money;
            System.out.println(System.currentTimeMillis()+"存进:"+money);
        }
        public  void subMoney( int money ){   //取钱
            if( count - money < 0){
                System.out.println("余额不足");   //如果钱不够则输出余额不足
                return;
            }
            count -= money;    //否则取出钱
            System.out.println(+System.currentTimeMillis()+"取出:"+money);
        }
        public void lookMoney() {    //查询
            System.out.println("账户余额:" + count);
        }
    }

    两个账户取钱:

    public class SyncThreadTest {
        public static void main(String args[]){
            final Bank bank=new Bank();     //创建银行类
            Thread tadd=new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        bank.addMoney(100);      //取出钱
                        bank.lookMoney();        //查看钱
                        System.out.println("\n");
                    }
                }
            });
            Thread tsub = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        bank.subMoney(100);
                        bank.lookMoney();
                        System.out.println("\n");
                    }
                }
            });
            tsub.start();
            tadd.start();
        }
    }

    输出结果:

    余额不足
    账户余额:0
    
    1532158239237存进:100
    账户余额:100
    
    1532158240237取出:100
    账户余额:0
    
    1532158240240存进:100
    账户余额:100

    结果比较乱。接下来我们使用同步的方法:
    (1)Synchronized关键字
    Java语言中,每个对象都有一个对象锁与之对应,这个锁表明,任何时候只允许被一个线程拥有,当一个线程调用对象的一段Synchronized代码时,需要先获取这个锁,然后执行这段代码,执行结束后,释放锁。
    这样我们就能将Bank这个类进行修改:

    public class Bank {
        private int count =0;      //账户余额
        public  synchronized void addMoney(int money){   //存钱
            count += money;
            System.out.println(System.currentTimeMillis()+"存进:"+money);
        }
        public synchronized void subMoney( int money ){   //取钱
            if( count - money < 0){
                System.out.println("余额不足");
                return;
            }
            count -= money;
            System.out.println(+System.currentTimeMillis()+"取出:"+money);
        }
        public void lookMoney() {    //查询
            System.out.println("账户余额:" + count);
        }
    }
    //运行结果:
    余额不足
    账户余额:0
    
    1532158790233存进:100
    账户余额:100
    
    1532158791238取出:100
    账户余额:0

    上面的方法,synchronized修饰在了方法上面,如果修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
    synchronized还可以修饰语句块,称之为同步代码块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步:

    public class Bank {
        private int count = 0;//账户余额
        public void addMoney(int money) {   //存钱
            synchronized (this) {
                count += money;
            }
            System.out.println(System.currentTimeMillis() + "存进:" + money);
        }
        public void subMoney(int money) {   //取钱
            synchronized (this) {
                if (count - money < 0) {
                    System.out.println("余额不足");
                    return;
                }
                count -= money;
            }
            System.out.println(+System.currentTimeMillis() + "取出:" + money);
        }
        public void lookMoney() {  //查询
            System.out.println("账户余额:" + count);
        }
    }

    运行效果和同步方法差不多。
    注意:
    同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

    (2)使用Volatile关键字实现线程同步
    Volatile关键字的主要作用有两个:
    ① 内存可见性,即线程A对volatile变量的修改,其他线程获取的volatile变量都是最新的。
    ② 可以禁止指令重排序

    public class Bank {
        private volatile int count =0;      //账户余额
        public void addMoney(int money){   //存钱
            count += money;
            System.out.println(System.currentTimeMillis()+"存进:"+money);
        }
        public void subMoney( int money ){   //取钱
            if( count - money < 0){
                System.out.println("余额不足");
                return;
            }
            count -= money;
            System.out.println(+System.currentTimeMillis()+"取出:"+money);
        }
        public void lookMoney() {    //查询
            System.out.println("账户余额:" + count);
        }
    }
    //运行结果正常

    每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。
    (3)使用重入锁实现线程同步
    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法具有相同的基本行为和语义,并且扩展了其能力。
    ReenreantLock类的常用方法有:
        ReentrantLock() : 创建一个ReentrantLock实例
        lock() : 获得锁
        unlock() : 释放锁

    public class Bank {
        private int count =0;      //账户余额
        //需要声明这个锁
        private Lock lock = new ReentrantLock();
        public void addMoney(int money){   //存钱
            lock.lock();
            try {
                count += money;
                System.out.println(System.currentTimeMillis() + "存进:" + money);
            }
            finally {
                lock.unlock();
            }
        }
        public void subMoney( int money ) {   //取钱
            lock.lock();
            try {
                if (count - money < 0) {
                    System.out.println("余额不足");
                    return;
                }
                count -= money;
                System.out.println(+System.currentTimeMillis() + "取出:" + money);
            }finally {
                lock.unlock();
            }
        }
        public void lookMoney() {    //查询
            System.out.println("账户余额:" + count);
        }
    }

    如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
    (4)使用局部变量实现线程同步
    使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

    public class Bank {
        //创建一个线程本地变量
        private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue() {    //返回此线程局部变量的当前线程的"初始值"
                return 0;
            }
        };
        public void addMoney(int money){       //存钱
            count.set(count.get()+money);
            System.out.println(System.currentTimeMillis() + "存进:" + money);
    
        }
        public void subMoney( int money ) {    //取钱
            if (count.get() - money < 0) {
                System.out.println("余额不足");
                return;
            }
            count.set(count.get()- money);
            System.out.println(+System.currentTimeMillis() + "取出:" + money);
        }
        public void lookMoney() {    //查询
            System.out.println("账户余额:" + count.get());
        }
    }

    (5)使用阻塞队列实现线程同步
    前面几种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。
    使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。
    这里使用LinkedBlockingQueue来实现线程的同步
    LinkedBlockingQueue是一个先进先出的顺序(FIFO)的阻塞队列
    LinkedBlockingQueue 类常用方法:

    //LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
    //put(E e) : 在队尾添加一个元素,如果队列满则阻塞 
    //size() : 返回队列中的元素个数 
    //take() : 移除并返回队头元素,如果队列空则阻塞 

    (6)使用原子变量实现线程同步
    需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
    那么什么是原子操作呢?
    原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
    即-这几种行为要么同时完成,要么都不完成。
    在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值。
    AtomicInteger类常用方法:
    AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
    addAddGet(int dalta) : 以原子方式将给定值与当前值相加
    get() : 获取当前值

    class MyThread implements Runnable {
        static AtomicInteger ai=new AtomicInteger(0);
        public void run() {
            for (int m = 0; m < 1000000; m++) {
                ai.getAndIncrement();
            }
        }
    };
    public class TestAtomicInteger {
        public static void main(String[] args) throws InterruptedException {
            MyThread mt = new MyThread();
            Thread t1 = new Thread(mt);
            Thread t2 = new Thread(mt);
            t1.start();
            t2.start();
            Thread.sleep(500);
            System.out.println(MyThread.ai.get());
        }
    }
    //运行结果都是:20000000,证明时线程安全的。

    (7)wait()方法与notify() 方法

    转载请标明出处,原文地址:https://blog.csdn.net/weixin_41835916 如果觉得本文对您有帮助,请点击支持一下,您的支持是我写作最大的动力,谢谢。
    这里写图片描述

    展开全文
  • Android-线程常用方法-线程同步

    千次阅读 2019-02-16 15:45:52
    线程常用方法: 1.start():线程调用该方法将启动线程从新建状态进入就绪,一旦轮到享用CPU资源时,就开始自己的生命周期 2.run():Thread类的run()方法与Runnable接口的run()方法的功能和作用相同,都用来定义线程对象...

    线程常用方法:

    1.start():线程调用该方法将启动线程从新建状态进入就绪,一旦轮到享用CPU资源时,就开始自己的生命周期

    2.run():Thread类的run()方法与Runnable接口的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法

    3.sleep(int millsecong):优先级高的线程可以在它的run()方法中调用sleep方法来使自己放弃CPU资源,休眠一段时间

    4.isAlive():线程处于"新建"状态时,线程调用isAlive()方法返回false,在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true

    5.currentThread():该方法是Thread类中的类方法,可以用类名调用,该方法返回当前正在使用CPU资源的线程

    6.interrupt():一个正在使用CPU资源的线程可以让休眠的线程调用interrupt()方法"吵醒"自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源

    线程同步

    所谓线程同步就是用synchronized关键字修饰同步方法,即程序中的若干个线程都需要使用一个方法,而这个方法用synchronized给予了修饰

    多个线程调用synchronized方法必须遵守同步机制:当一个线程使用这个方法时,其他线程想使用这个方法就必须等待,直到线程使用完该方法,使用多线程解决许多实际问题时,可能要把某些修改数据的方法用关键字synchronized来修饰

    线程同步的方式和机制:

    临界区、互斥量、事件、信号量四种方式

    临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别

    1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。

    2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享

    3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目

    4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作

    public class Demo45{

    public static void main(String[] args){
        Bank bank = new bank();
        bank.setMoney(200);
        System.out.println("银行账上还有200");
    
        Thread accountant = new Thread(bank);
        accountant.setName("会计");
        Thread cashier = new Thread(bank);
        cashier.setName("出纳");
    
        accountant.start();
        cashier.start();
    }
    

    }

    public class Bank implements Runnable{

    int money;
    
     public void setMoney(int n){
        money = n;
    }
    
    @Override
    public void run(){
        if(Thread.currentThread().getMoney().equals("会计")){
            saveOrTake(300);
        }else if (Thread.currentThread().getMoney().equals("出纳")){
            saveOrTake(150);
        }
    }
    
    public synchronized void saveOrTake(int money){
        if(Thread.currentThread().getMoney().equals("会计")){
            for(int i = 0;i < 3; i++){
               money = money+m/3;
               System.out.println(Thread.currentThread().getName()+"存入"+m/3",账上还有"+money+",休息一会再存");
    
               try{
               Thread.sleep(1000);
            }catch(InterruptedException e){
               e.printStackTrace();
            }
          }
        }else if (Thread.currentThread().getMoney().equals("出纳")){
            for(int i = 0;i < 3; i++){
               money = money-m/3;
               System.out.println(Thread.currentThread().getName()+"取出"+m/3",账上还有"+money+",休息一会再取");
    
               try{
               Thread.sleep(1000);
            }catch(InterruptedException e){
               e.printStackTrace();
            }
        }
    }
    

    }
    运行结果:
    在这里插入图片描述

    展开全文
  • linux中实现线程同步的6种方法

    万次阅读 多人点赞 2020-10-22 16:37:21
    linux线程同步方法 下面是一个线程不安全的例子: #include<stdio.h> #include<pthread.h> int ticket_num=10000000; void *sell_ticket(void *arg) { while(ticket_num>0) { ticket_num--; }...

    linux线程同步的方法

    下面是一个线程不安全的例子:

    #include<stdio.h>
    #include<pthread.h>
    
    int ticket_num=10000000;
    
    void *sell_ticket(void *arg) {
        while(ticket_num>0) {
    	ticket_num--;
        }
    }
    
    int main() {
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, &sell_ticket, NULL);
        pthread_create(&t2, NULL, &sell_ticket, NULL);
        pthread_create(&t3, NULL, &sell_ticket, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_join(t3, NULL);
        printf("ticket_num=%d\n", ticket_num);
        return 0;
    }
    

    运行结果如下:

    # gcc no_lock_demo.c -o no_lock_demo.out -pthread
    # ./no_lock_demo.out 
    ticket_num=-2
    

    最后运行的结果不是固定的,有可能是0、-1,如果有这个ticket_num变量代表是库存的话,那么就会出现库存为负数的情况,所以需要引入线程同步来保证线程安全。

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、自旋锁、信号量。

    互斥锁

    互斥锁本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,当互斥锁由某个线程持有后,这个互斥锁会锁上变成lock状态,此后只有该线程有权力打开该锁,其他想要获得该互斥锁的线程都会阻塞,直到互斥锁被解锁。

    互斥锁的类型:

    • 普通锁(PTHREAD_MUTEX_NORMAL):互斥锁默认类型。当一个线程对一个普通锁加锁以后,其余请求该锁的线程将形成一个 等待队列,并在该锁解锁后按照优先级获得它,这种锁类型保证了资源分配的公平性。一个 线程如果对一个已经加锁的普通锁再次加锁,将引发死锁;对一个已经被其他线程加锁的普 通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期的后果。

    • 检错锁(PTHREAD_MUTEX_ERRORCHECK):一个线程如果对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLK;对一个已 经被其他线程加锁的检错锁解锁或者对一个已经解锁的检错锁再次解锁,则解锁操作返回 EPERM。

    • 嵌套锁(PTHREAD_MUTEX_RECURSIVE):该锁允许一个线程在释放锁之前多次对它加锁而不发生死锁;其他线程要获得这个锁,则当前锁的拥有者必须执行多次解锁操作;对一个已经被其他线程加锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次解锁,则解锁操作返回EPERM。

    • 默认锁(PTHREAD_MUTEX_ DEFAULT):一个线程如果对一个已经加锁的默认锁再次加锁,或者虽一个已经被其他线程加锁的默 认锁解锁,或者对一个解锁的默认锁解锁,将导致不可预期的后果;这种锁实现的时候可能 被映射成上述三种锁之一。

    相关方法:

    
    // 静态方式创建互斥锁
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
    
    // 动态方式创建互斥锁,其中参数mutexattr用于指定互斥锁的类型,具体类型见上面四种,如果为NULL,就是普通锁。
    int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
    
    int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁,阻塞
    int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁,非阻塞
    int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁
    

    例子:

    #include<stdio.h>
    #include<pthread.h>
    
    int ticket_num=10000000;
    
    pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
    
    void *sell_ticket(void *arg) {
        while(ticket_num>0) {
    	pthread_mutex_lock(&mutex);
    	if(ticket_num>0) {
    	    ticket_num--;
    	}
    	pthread_mutex_unlock(&mutex);
        }
    }
    
    int main() {
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, &sell_ticket, NULL);
        pthread_create(&t2, NULL, &sell_ticket, NULL);
        pthread_create(&t3, NULL, &sell_ticket, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_join(t3, NULL);
        printf("ticket_num=%d\n", ticket_num);
        return 0;
    }
    

    自旋锁

    自旋锁顾名思义就是一个死循环,不停的轮询,当一个线程未获得自旋锁时,不会像互斥锁一样进入阻塞休眠状态,而是不停的轮询获取锁,如果自旋锁能够很快被释放,那么性能就会很高,如果自旋锁长时间不能够被释放,甚至里面还有大量的IO阻塞,就会导致其他获取锁的线程一直空轮询,导致CPU使用率达到100%,特别CPU时间。

    相关方法:

    int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 创建自旋锁
    
    int pthread_spin_lock(pthread_spinlock_t *lock); // 加锁,阻塞
    int pthread_spin_trylock(pthread_spinlock_t *lock); // 尝试加锁,非阻塞
    int pthread_spin_unlock(pthread_spinlock_t *lock); // 解锁
    

    例子:

    #include<stdio.h>
    #include<pthread.h>
    
    int ticket_num=10000000;
    
    pthread_spinlock_t spinlock;
    
    void *sell_ticket(void *arg) {
        while(ticket_num>0) {
    	pthread_spin_lock(&spinlock);
    	if(ticket_num>0) {
    	    ticket_num--;
    	}
    	pthread_spin_unlock(&spinlock);
        }
    }
    
    int main() {
        pthread_spin_init(&spinlock, 0);
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, &sell_ticket, NULL);
        pthread_create(&t2, NULL, &sell_ticket, NULL);
        pthread_create(&t3, NULL, &sell_ticket, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_join(t3, NULL);
        printf("ticket_num=%d\n", ticket_num);
        return 0;
    }
    

    信号量

    信号量是一个计数器,用于控制访问有限共享资源的线程数。

    相关方法:

    // 创建信号量
    // pshared:一般取0,表示调用进程的信号量。非0表示该信号量可以共享内存的方式,为多个进程所共享(Linux暂不支持)。
    // value:信号量的初始值,可以并发访问的线程数。
    int sem_init (sem_t* sem, int pshared, unsigned int value);
    
    int sem_wait (sem_t* sem); // 信号量减1,信号量为0时就会阻塞
    
    int sem_trywait (sem_t* sem); // 信号量减1,信号量为0时返回-1,不阻塞
    
    int sem_timedwait (sem_t* sem, const struct timespec* abs_timeout); // 信号量减1,信号量为0时阻塞,直到abs_timeout超时返回-1
    
    int sem_post (sem_t* sem); // 信号量加1
    

    例子:

    #include<stdio.h>
    #include<pthread.h>
    #include <semaphore.h>
    
    int ticket_num=10000000;
    
    sem_t sem;
    
    void *sell_ticket(void *arg) {
        while(ticket_num>0) {
    	sem_wait(&sem);
    	if(ticket_num>0) {
    	    ticket_num--;
    	}
    	sem_post(&sem);
        }
    }
    
    int main() {
        sem_init(&sem, 0, 1); // value=1表示最多1个线程同时访问共享资源,与互斥量等价
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, &sell_ticket, NULL);
        pthread_create(&t2, NULL, &sell_ticket, NULL);
        pthread_create(&t3, NULL, &sell_ticket, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_join(t3, NULL);
        printf("ticket_num=%d\n", ticket_num);
        return 0;
    }
    

    条件变量

    条件变量可以让调用线程在满足特定条件的情况下运行,不满足条件时阻塞等待被唤醒,必须与互斥锁搭配使用。

    条件变量常用于生产者与消费者模型。

    相关方法:

    pthread_cond_t cond=PTHREAD_COND_INITIALIZER; // 创建条件变量,一个互斥锁可以对应多个条件变量
    
    int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex); // 阻塞等待条件满足,同时释放互斥锁mutex
    
    int pthread_cond_timedwait (pthread_cond_t* cond,
        pthread_mutex_t* mutex,
        const struct timespec* abstime); // 带超时的阻塞等待条件满足,同时释放互斥锁mutex
    
    // 从条件变量cond中唤出一个线程,令其重新获得原先的互斥锁
    // 被唤出的线程此刻将从pthread_cond_wait函数中返回,但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
    int pthread_cond_signal (pthread_cond_t* cond);
    
    // 从条件变量cond中唤出所有线程
    int pthread_cond_broadcast (pthread_cond_t* cond);
    

    例子:

    #include<stdio.h>
    #include<pthread.h>
    
    int max_buffer=10;
    int count=0;
    
    pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t notempty=PTHREAD_COND_INITIALIZER;
    pthread_cond_t notfull=PTHREAD_COND_INITIALIZER;
    
    void *produce(void *args) {
        while(1) {
            pthread_mutex_lock(&mutex);
            while(count == max_buffer) {
                printf("buffer is full, wait...\n");
                pthread_cond_wait(&notfull, &mutex);
            }
            printf("produce ...\n");
            count++;
            sleep(1);
            pthread_cond_signal(&notempty);
            pthread_mutex_unlock(&mutex);
        }
    
    }
    
    void *consumer(void *args) {
        while(1) {
            pthread_mutex_lock(&mutex);
            while(count == 0) {
                printf("buffer is empty, wait...\n");
                pthread_cond_wait(&notempty, &mutex);
            }
            printf("consumer ...\n");
            count--;
            sleep(1);
            pthread_cond_signal(&notfull);
            pthread_mutex_unlock(&mutex);
        }
    
    }
    
    int main() {
        pthread_t t1,t2,t3,t4;
        pthread_create(&t1, NULL, &produce, NULL);
        pthread_create(&t2, NULL, &produce, NULL);
    
        pthread_create(&t3, NULL, &consumer, NULL);
        pthread_create(&t4, NULL, &consumer, NULL);
    
        pthread_join(t1, NULL);
        return 0;
    }
    

    读写锁

    读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的,当它以写模式锁住时,它是以独占模式锁住的,读读共享,读写互斥。

    相关方法:

    // 创建读写锁
    pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;
    
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 加读锁,阻塞
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 加写锁,阻塞
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 释放读锁或者写锁
    
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // 尝试加读锁,非阻塞
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 尝试加写锁,非阻塞
    

    例子:

    #include <stdio.h>
    #include <pthread.h>
    
    pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;
    
    void *read(void *arg) {
        while(1) {
            pthread_rwlock_rdlock(&rwlock);
            rintf("read message.\n");
            sleep(1);
            pthread_rwlock_unlock(&rwlock);
            sleep(1);
        }
    }
    void *write(void *arg) {
        while(1) {
            pthread_rwlock_wrlock(&rwlock);
            printf("write message.\n");
            sleep(1);
            pthread_rwlock_unlock(&rwlock);
            sleep(1);
        }
    }
    
    int main(int argc,char *argv[]) {
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, &read, NULL);
        pthread_create(&t2, NULL, &read, NULL);
    
        pthread_create(&t3, NULL, &write, NULL);
    
        pthread_join(t1, NULL);
        return 0;
    }
    

    屏障

    屏障(barrier)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后所有线程都从该点继续执行。pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出。但屏障对象的概念更广,允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出,当所有的线程达到屏障后可以接着工作。

    相关方法:

    // 创建屏障
    int pthread_barrier_init(pthread_barrier_t *barrier,const pthread_barrrierattr_t *attr,unsigned int count)
    
    // 阻塞等待,直到所有线程都到达
    int pthread_barrier_wait(pthread_barrier_t *barrier)
    

    例子:

    #include <stdio.h>
    #include <pthread.h>
    
    pthread_barrier_t barrier;
    
    void *go(void *arg){
        sleep (rand () % 10);
        printf("%lu is arrived.\n", pthread_self());
        pthread_barrier_wait(&barrier);
        printf("%lu go shopping...\n", pthread_self());
    }
    
    int main() {
        pthread_barrier_init(&barrier, NULL, 3);
    
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, &go, NULL);
        pthread_create(&t2, NULL, &go, NULL);
        pthread_create(&t3, NULL, &go, NULL);
    
        pthread_join(t1, NULL);
        return 0;
    }
    
    展开全文
  • 线程同步(7种同步方法

    万次阅读 2018-09-20 18:27:10
    转载:"... 为什么要使用同步? java允许多线程并发控制,当多线程同时操作一个可共享的资源变量时,将会导致数据不准确,相互之间产生冲突,因此...1、同步方法 synchronized关键字修饰方法。由于java的每个对...

    转载:"http://www.cnblogs.com/XHJT/p/3897440.html"

    为什么要使用同步?

    java允许多线程并发控制,当多线程同时操作一个可共享的资源变量时,将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程调用,从而保证了该变量的唯一性和准确性

    1、同步方法

    synchronized关键字修饰方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    代码如下:

    public synchronized void save(){}

    注:synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

    2、同步代码块

    synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动加上内置锁,从而实现同步

    代码如下:

    synchronized(object){}

    注:同步是一种高开销的操作,因此应该尽量减少同步内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可

    代码实例:

    package com.xhj.thread;
    
        /**
         * 线程同步的运用
         * 
         * @author XIEHEJUN
         * 
         */
        public class SynchronizedThread {
    
            class Bank {
    
                private int account = 100;
    
                public int getAccount() {
                    return account;
                }
    
                /**
                 * 用同步方法实现
                 * 
                 * @param money
                 */
                public synchronized void save(int money) {
                    account += money;
                }
    
                /**
                 * 用同步代码块实现
                 * 
                 * @param money
                 */
                public void save1(int money) {
                    synchronized (this) {
                        account += money;
                    }
                }
            }
    
            class NewThread implements Runnable {
                private Bank bank;
    
                public NewThread(Bank bank) {
                    this.bank = bank;
                }
    
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        // bank.save1(10);
                        bank.save(10);
                        System.out.println(i + "账户余额为:" + bank.getAccount());
                    }
                }
    
            }
    
            /**
             * 建立线程,调用内部类
             */
            public void useThread() {
                Bank bank = new Bank();
                NewThread new_thread = new NewThread(bank);
                System.out.println("线程1");
                Thread thread1 = new Thread(new_thread);
                thread1.start();
                System.out.println("线程2");
                Thread thread2 = new Thread(new_thread);
                thread2.start();
            }
    
            public static void main(String[] args) {
                SynchronizedThread st = new SynchronizedThread();
                st.useThread();
            }
    
        }

    3、使用特殊域变量(volatile)实现线程同步

    a.volatile关键字为域变量的访问提供一种免锁机制

    b.使用volatile修饰域相当于告诉虚拟机该域可能被其他现象更新

    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值

    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

    例如:上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步

    代码实例:

    //只给出要修改的代码,其余代码与上同
            class Bank {
                //需要同步的变量加上volatile
                private volatile int account = 100;
    
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized 
                public void save(int money) {
                    account += money;
                }
            }

    注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。用final域,有锁保护的域和volatile域可以避免非同步的问题

    4:使用重入锁实现线程同步

    在javaSE5.0新增了一个java.concurrent包来支持同步。ReentrantLock类可以重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具体相同的基本行为和语义,并且扩展了其能力

    ReentrantLock类的常用方法有:

    ReentrantLock():创建一个ReentrantLock实例      lock():获得锁  unlock():释放锁

    注:ReentrantLock()还有一个可以创建公共锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

    代码实例:

    //只给出要修改的代码,其余代码与上同
            class Bank {
                
                private int account = 100;
                //需要声明这个锁
                private Lock lock = new ReentrantLock();
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized 
                public void save(int money) {
                    lock.lock();
                    try{
                        account += money;
                    }finally{
                        lock.unlock();
                    }
                    
                }
            }

    注:关于Lock对象和synchronized关键字的选择:

    a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码

    b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码

    c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁

    5.使用局部变量实现线程同步

    如果使用ThreadLocal管理变量,则每一个使用变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

    ThreadLocal类的常用方法

    ThreadLocal():创建一个线程本地变量

    get():返回此线程局部变量的当前线程副本中的值

    initialValue():返回此线程局部变量的当前线程的“初始值”

    set(T value):将此线程局部变量的当前线程副本中的值设置为value

    实例代码:

    //只改Bank类,其余代码与上同
            public class Bank{
                //使用ThreadLocal类管理共享变量account
                private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                    @Override
                    protected Integer initialValue(){
                        return 100;
                    }
                };
                public void save(int money){
                    account.set(account.get()+money);
                }
                public int getAccount(){
                    return account.get();
                }
            }

    注:ThreadLocal与同步机制

    a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题

    b.前者采用以“空间换时间”的方法,后者采用以“时间换空间”的方式

    6.使用阻塞队列实现线程同步

    前面5种同步方法都是在底层实现的线程同步,但还是我们在实际开发当中,应当尽量远离底层结构。使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。本小节主要使用LinkedBlockingQueue<E>来实现线程的同步,LinkedBlocking<E>来实现线程同步LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue 队列是先进先出的顺序。

     LinkedBlockingQueue 类常用方法 
        LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
        put(E e) : 在队尾添加一个元素,如果队列满则阻塞 
        size() : 返回队列中的元素个数 
        take() : 移除并返回队头元素,如果队列空则阻塞 
        
       代码实例: 
            实现商家生产商品和买卖商品的同步

    package com.xhj.thread;
    
    import java.util.Random;
    import java.util.concurrent.LinkedBlockingQueue;
    
    /**
     * 用阻塞队列实现线程同步 LinkedBlockingQueue的使用
     * 
     * @author XIEHEJUN
     * 
     */
    public class BlockingSynchronizedThread {
        /**
         * 定义一个阻塞队列用来存储生产出来的商品
         */
        private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
        /**
         * 定义生产商品个数
         */
        private static final int size = 10;
        /**
         * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
         */
        private int flag = 0;
    
        private class LinkBlockThread implements Runnable {
            @Override
            public void run() {
                int new_flag = flag++;
                System.out.println("启动线程 " + new_flag);
                if (new_flag == 0) {
                    for (int i = 0; i < size; i++) {
                        int b = new Random().nextInt(255);
                        System.out.println("生产商品:" + b + "号");
                        try {
                            queue.put(b);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("仓库中还有商品:" + queue.size() + "个");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                } else {
                    for (int i = 0; i < size / 2; i++) {
                        try {
                            int n = queue.take();
                            System.out.println("消费者买去了" + n + "号商品");
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("仓库中还有商品:" + queue.size() + "个");
                        try {
                            Thread.sleep(100);
                        } catch (Exception e) {
                            // TODO: handle exception
                        }
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
            LinkBlockThread lbt = bst.new LinkBlockThread();
            Thread thread1 = new Thread(lbt);
            Thread thread2 = new Thread(lbt);
            thread1.start();
            thread2.start();
    
        }
    
    }

    注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:

      add()方法会抛出异常

      offer()方法返回false

      put()方法会阻塞

    7.使用原子变量实现线程同步

    需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

    那么什么是原子操作呢?
    原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
    即-这几种行为要么同时完成,要么都不完成。

    在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类
    使用该类可以简化线程同步。

    其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
    但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

    AtomicInteger类常用方法:
    AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
    addAddGet(int dalta) : 以原子方式将给定值与当前值相加
    get() : 获取当前值

    代码实例:
    只改Bank类,其余代码与上面第一个例子同

    class Bank {
            private AtomicInteger account = new AtomicInteger(100);
    
            public AtomicInteger getAccount() {
                return account;
            }
    
            public void save(int money) {
                account.addAndGet(money);
            }
        }

    补充--原子操作主要有:
      对于引用变量和大多数原始变量(long和double除外)的读写操作;
      对于所有使用volatile修饰的变量(包括long和double)的读写操作。

    展开全文
  • Java实现线程同步的五种方法

    千次阅读 2019-03-08 11:47:52
    一.使用synchronized关键字 由于每个java对象都有一个内置锁,用synchronized修饰...由于同步是一种高开销的工作,所以尽量减少同步的内容,只需同步代码块就可以。 1.修饰方法 public class Test implements Run...
  • java线程同步的实现方式

    万次阅读 2019-03-08 01:47:21
    这里抛砖引玉,为何要使用同步? 当多个线程同时操作一个可共享的资源时会出现线程安全问题,...1.同步方法 使用 synchronized关键字,可以修饰普通方法、静态方法,以及语句块。由于java的每个对象都有一个内置锁...
  • 实现线程同步的几种方法

    万次阅读 2018-09-27 09:50:52
    因此引入多线程同步,也就是说多个线程只能一个对共享的资源进行更改,其他线程不能对数据进行修改。 如下一个两个线程对一个银行账户进行存钱的小实例,其中使用synchornized修饰方法实现线程的同步 代码如下: `...
  • java多线程同步5种方法

    万次阅读 多人点赞 2018-05-28 22:11:07
    二、为什么要线程同步因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作...
  • 实现Java线程同步的五种方法

    千次阅读 2017-03-29 01:16:09
    线程同步概念 Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不明确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的...
  • java多线程同步的五种方法

    千次阅读 2017-09-12 10:15:13
    二、为什么要使用同步? 因为当我们有多个线程要同时访问同一个变量或对象时,如果这些线程中午既有读又有写操作时,就会导致变量值或者对象的状态出现混乱,从而导致程序异常,举个例子:如果同一个银行账户被连...
  • C++线程同步的四种方式(Windows)

    万次阅读 多人点赞 2017-07-03 23:20:56
    为什么要进行线程同步? 在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的...
  • 实现线程同步的几种方式总结

    万次阅读 多人点赞 2018-07-25 21:31:26
    在多线程线程的执行顺序是依靠哪个线程先获得到CUP的执行权谁就先执行,虽然说可以通过线程的优先权进行设置,但是他只是获取CUP执行权的概率高点,但是也不一定必须先执行。在这种情况下如何保证线程按照一定的...
  • 线程同步的四种方式

    千次阅读 2019-10-15 14:05:26
    一、为什么要进行线程同步? 多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的...
  • C 线程同步的四种方式(Windows)

    千次阅读 2020-02-21 18:08:31
    一、为什么要进行线程同步? 在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理...
  • 线程同步常用方式与区别

    千次阅读 2017-06-20 14:02:12
    在介绍线程同步/互斥之前,我们先要理解同步与互斥的概念,引用书上的解释来说明这2个概念: 1、线程(进程)同步的主要任务 在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,...
  • C#多线程——线程同步

    万次阅读 2018-08-25 13:11:53
    一、为什么要线程同步?...二、实现线程同步方法: • 使用Mutex类 • 使用SemaphoreSlim类 • 使用AutoResetEvent类 • 使用ManualResetEventSlim类 • 使用CountDownEvent类 • 使用Barrier类 • 使用Reade...
  • JAVA多线程——实现同步

    千次阅读 2018-07-26 17:20:33
    为何要使用同步? java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作...线程同步的方...
  • kotlin 线程同步

    千次阅读 2018-08-21 11:01:00
    为什么80%的码农都做不了架构师?>>> ...
  • 一、多线程同步关键字-synchronized1.概念 synchronized保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。当多个并发线程访问同一个对象object中的同步...
  • 线程同步方法有哪些(面试题)

    千次阅读 2017-04-18 21:38:23
    线程同步:  由于同一个进程的多个线程共享同一片存储空间。在带来方便的同时,也带来了如访问冲突问题,如何解决,解决方法有几种?  第一种:  采用synchronized同步锁  第二种:  单利设计模式中的静态...
  • java线程解决同步问题的5种方法

    千次阅读 2018-09-10 21:13:09
    线程创建的两种方法: 1. 继承runnable接口传入Thread 实例对象作为单实例传递给Thread 所以创建多个线程共用一个实例对象,里面的属性也都是共享的。 2. 继承Thread 多线程工作原理: 线程1:操作步骤–工作...
  • 线程同步 各个关键字和方法的使用

    万次阅读 2015-04-15 10:21:05
    1、volatile关键词:用来对共享变量的访问进行同步,上一次写入操作的结果对下一次读取操作是肯定可见的。(在写入volatile变量值之后,CPU缓存中的内容会被写回内存;在读取volatile变量时,CPU缓存中的对应内容会...
  • C++11 多线程同步

    千次阅读 2016-11-09 21:28:05
    出现数据竞争,一般会用临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)这四种方法来完成线程同步。 1、临界区 对于临界资源,多线程必须互斥地对它进行访问。每个...
  • 15、线程同步方式有哪些?优缺点?

    千次阅读 2018-08-08 10:11:19
    进程中线程同步的四种常用方式: 一、 临界区(CCriticalSection) 当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,...
  • java中实现多线程同步方式

    千次阅读 2018-05-22 14:46:21
    线程实现方式 1)、实现Runnable接口,并实现run()方法 以下是主要步骤: 1、自定义类并实现Runnable接口,并实现run()方法。 2、创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象。 3...
  • 分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!...- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常; - notify():唤醒一个处...
  • 背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?通过多线程模拟多窗口售票为例:#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #...
  • C++实现线程同步的几种方式

    千次阅读 2018-04-22 14:50:44
    线程同步是指同一进程中的多个线程互相协调工作从而达到一致性。之所以需要线程同步,是因为多个线程同时对一个数据对象进行修改操作时,可能会对数据造成破坏,下面是多个线程同时修改同一数据造成破坏的例子: 1 #...
  • 线程同步方法的区别

    千次阅读 2010-07-23 23:48:00
    最近看多线程应用多线程同步方法,多线程同步方法有四种,分别是是互斥量,临界段,事件和信号量;这些方法都有各自的特点,用于不同的场合。这些方法的异同如下(在多线程编程书上看的): <br /> ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 876,714
精华内容 350,685
关键字:

线程同步的方法