精华内容
下载资源
问答
  • 线程同步与线程安全

    千次阅读 2018-02-09 16:47:32
    1线程同步 同步:多线程访问临界资源时,必须进行同步控制,多进程或者多线程的执行并不完全是绝对的并行运行,又可能主线程需要等待函数线程的某些条件的发生。 多线程的临界资源有全局数据,堆区数据,文件描述...

    1线程同步

       同步:多线程访问临界资源时,必须进行同步控制,多进程或者多线程的执行并不完全是绝对的并行运行,又可能主线程需要等待函数线程的某些条件的发生。

      多线程的临界资源有全局数据,堆区数据,文件描述符

     

       同步控制方式:

      1.1信号量

            需要用到头文件semaphore.h

         获取: 

           int sem_init(sem_t *sem,int pshared,ussigned int vale);

                    第一个参数是一个sem_t类型指针,指向信号量对象

                    第二个参数是多进程间是否线程共享,linux暂不支持,所以为0

                     第三个参数是信号量的初始值

         p操作:

                 int sem_wait(sem_t *sem)

         V操作:

              int sem_post(sem_t *sem)

         删除:

         int sem_destroy(sem_t *sem)

    举个栗子

    主线程获取用户输入,函数线程统计用户输入的字符个数。



       


    1.2互斥锁   还有读写锁,自旋锁....

      完全控制临界资源,如果一个线程完成加锁操作,则其他线程无论如何都无法进行加锁,也就无法对临界资源进行访问。相当于最大值为1的信号量,但比信号量的临界控制更加严格。

    初始化:int pthread_mutex_init(pthread_mutex_t *mutx,pthread_mutex_attr_t *attr);

           第二个参数pthread_mutex_attr_t *attr为锁的属性,一般为NULL的默认属性。

     

    加锁:int pthread_mutex_lock(pthread_mutex_t *mutx); //阻塞运行的版本,即无法加锁就等待

          int pthread_mutex_trylock(pthread_mutex_t *mutx);//非阻塞的版本。能加锁就加锁进入临界区,加不了走人不干等着

    解锁:int pthread_mutex_unlock(pthread_mutex_t *mutx);

     

    释放(删除):int pthread_mutex_destory(pthread_mutex_t *mutx);

       


    2线程安全

      可重入函数:

       某些库函数会使用线程间共享的数据,如果没有同步控制,线程操作是不安全的。

       所以,我们使用这样一些函数时,就必须使用其安全的版本,即可重入函数;

    例如 Strtok的 可重入版本 strtok_r

    我们知道切割函数一般只用第一次传入字符数组的首地址,之后的切割传入NULL就可以了

    为什么可以函数只用传NULL就知道我们上次字符串切割后的首地址,那是由于系统已经有个全局变量储存了。

    因此如果两个线程同时使用这个函数去切割不同的字符串,那么全局变量的储存就会出问题。

    导致后切割的字符串的切后首地址覆盖了先切割的,最终结果是只切割了第一个字符串一次,然后两个线程共同 去切割另一个字符串了,和我们的预期不符。





    因此我们就要用到处理方式更加妥善的可重入函数








    线程中fork 

        在线程中,子进程只会启用调用fork函数的那条线程,其他线程不会启用。

    需要注意的是

    子进程会继承其父进程的锁及锁的状态,但是父子进程用到不是同一把锁,父进程解锁并不会影响到子进程的锁。所以子进程有可能死锁




    可以看到子进程的锁卡住了

    解决方案为应对这种情况设计的atfork函数,函数如下

     Pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));

     指定在fork调用之后,创建子进程之前,调用prepare函数,获取所有锁,

    然后创建子进程,子进程创建以后,父进程环境中调用parent解所有的锁,子进程环境中调用child解所有的锁,然后fork函数再返回,这样保证了fork之后,子进程拿到的锁都是解锁状态,避免了死锁。




    展开全文
  • 非线程安全与线程同步及lock的使用

    千次阅读 2019-12-30 17:41:58
    线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程安全:就是不提供...

    线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

    线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

    其实普通的多线程确实很简单,但是一个安全的高效的多线程却不那么简单。所以很多时候不正确的使用多线程反倒会影响程序的性能。下面先看一个例子 :

    class Program
        {
            static int Num = 1;
            static void Main(string[] args)
            {
                Stopwatch stopWatch = new Stopwatch();
                //开始计时
                stopWatch.Start();
                for (int i = 0; i < 5; i++)
                {
                    Thread thread = new Thread(Run);
                    thread.Start();
                }
    
                Num++;
                Console.WriteLine("Num:" + Num);
                Console.WriteLine("主线程ID是:" + Thread.CurrentThread.ManagedThreadId.ToString());
                //停止计时
                stopWatch.Stop();
    
                //输出执行的时间,毫秒数
                Console.WriteLine("执行时间为:" + stopWatch.ElapsedMilliseconds + " 毫秒");
                Console.ReadKey();
            }
    
            public static void Run()
            {
                Num++;
                Console.WriteLine("Num:" + Num);
                Console.WriteLine("子线程ID是:" + Thread.CurrentThread.ManagedThreadId.ToString());
            }
        }

    输出结果为:

    我们可以从输出的结果可以看出,变量Num的值不是连续递增,输出的也没有顺序,如果多次执行而且每次输出的值都是不一样。这是因为异步线程同时访问一个成员造成,所以这样的多线程对于我们来说是不可控的。以上这个例子是非线程安全,要做到线程安全就需要用到线程同步。线程同步的方法有很多,下面我们看代码:

    这次的输出结果看起来是不是舒服多了,代码中只加了thread.Join();就实现了简单的同步。Join()方法用于阻止当前线程,直到前面的线程执行完成。这样虽然是实现了同步,但是也阻塞了主线程的继续执行,就好比收费站只设了一个收费窗口,这样车辆的通行效率就降低了很多。c#为同步访问变量提供了一个非常简单的方式,即使用c#语言的关键字Lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在c#中,关键字Lock定义如下:

    Lock(expression)
    {
        statement_block
    }
    expression代表你希望跟踪的对象。
    statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

    更改之后的代码:

     class Program
        {
            private static object locker = new object();
            static int Num = 1;
            static void Main(string[] args)
            {
                Stopwatch stopWatch = new Stopwatch();
                //开始计时
                stopWatch.Start();
                for (int i = 0; i < 5; i++)
                {
                    Thread thread = new Thread(Run);
                    thread.Start();
                    //thread.Join();
                }
    
                Num++;
                Console.WriteLine("Num:" + Num);
                Console.WriteLine("主线程ID是:" + Thread.CurrentThread.ManagedThreadId.ToString());
                //停止计时
                stopWatch.Stop();
    
                //输出执行的时间,毫秒数
                Console.WriteLine("执行时间为:" + stopWatch.ElapsedMilliseconds + "毫秒");
                Console.ReadKey();
            }
    
            public static void Run()
            {
                lock (locker) {
                    Num++;
                    Console.WriteLine("Num:" + Num);
                    Console.WriteLine("子线程ID是:" + Thread.CurrentThread.ManagedThreadId.ToString());
                }
              
            }
        }

    输出结果为:

    锁的执行过程:假设线程A先执行,线程B稍微慢一点。线程A执行到lock语句,判断locker是否已申请了互斥锁,判断依据是逐个与已存在的锁进行object.ReferenceEquals比较,如果不存在,则申请一个新的互斥锁,这时线程A进入lock里面了。
    这时假设线程B启动了,而线程A还未执行完lock里面的代码。线程B执行到lock语句,检查到locker已经申请了互斥锁,于是等待;直到线程A执行完毕,释放互斥锁,线程B才能申请新的互斥锁并执行lock里面的代码。

    lock 是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。可以看到这种方式的确没有阻塞主线程,而且成员变量的值也是连续递增的,说明是线程安全的。lock 锁机制表示在同一时刻只有一个线程可以锁定同步对象(在这里是locker),任何竞争的其它线程都将被阻止,直到这个锁被释放。

    lock使用注意事项
    1、lock 的参数必须是基于引用类型的对象,不要是基本类型,比如 bool、int,这样根本不能同步,原因是lock的参数要求是对象,如果传入 int,势必要发生装箱操作,这样每次lock的都将是一个新的不同的对象。
    2、最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。
    3、最好不要锁字符串;使用lock同步时,应保证lock的是同一个对象,而给字符串变量赋值并不是修改它,而是重新创建了新的对象,这样多个线程以及每个循环之间所lock的对象都不同,因此达不到同步的效果。
    4、常用做法是创建一个object对象,并且永不赋值。应lock一个不影响其他操作的私有对象。

     

    Demo链接:https://github.com/wangongshen/Wgs.CSDN.Demo2019

    展开全文
  • 线程安全线程同步Synchronized

    万次阅读 多人点赞 2016-07-23 15:50:37
    线程不安全的产生和线程同步,volatile的使用仍然留有疑问

    部分转载:
    线程安全:http://blog.csdn.net/ghevinn/article/details/37764791
    Synchronized: http://blog.csdn.net/ghsau/article/details/7421217
    Volatile:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

    线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
    线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

    1. 概念

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
    或者说,一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
    线程安全问题都是由全局变量及静态变量引起的。(这句还未考证,但对全局变量和静态变量操作在多线程模型中会引发线程不安全)
    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    2. 安全性

    比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
    在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
    而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
    那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

    3. 线程同步

    3.1 Synchronized(同步)

    public class TraditionalThreadSynchronized {
        public static void main(String[] args) {
            final Outputter outputter = new Outputter();
            // 运行两个线程分别输出名字zhangsan和lisi
            new Thread() {
                public void run() {
                    outputter.output("zhangsan");
                }
            }.start();      
            new Thread() {
                public void run() {
                    outputter.output("lisi");
                }
            }.start();
        }
    }
    class Outputter {
        public void output(String name) {
            // TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
            for(int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
                // Thread.sleep(10);
            }
        }
    }

    运行结果:zhlainsigsan

    显然输出的字符串被打乱了,我们期望的输出结果是zhangsanlisi,这就是线程同步问题,我们希望output方法被一个线程完整的执行完之后再切换到下一个线程,Java中使用synchronized保证一段代码在多线程执行时是互斥的,有两种用法:
    方法 1: 使用synchronized将需要互斥的代码包含起来,并上一把锁。

    {
        synchronized (this) {
            for(int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
            }
        }
    }

    这把锁必须是需要互斥的多个线程间的共享对象,像下面的代码是没有意义的。

    {
        Object lock = new Object();
        synchronized (lock) {
            for(int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
            }
        }
    }

    方法2:将synchronized加在需要互斥的方法上。

    public synchronized void output(String name) {
        // TODO 线程输出方法
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
        }
    }

    这种方式就相当于用this锁住整个方法内的代码块,如果用synchronized加在静态方法上,就相当于用××××.class锁住整个方法内的代码块。使用synchronized在某些情况下会造成死锁,死锁问题以后会说明。使用synchronized修饰的方法或者代码块可以看成是一个 原子操作

    每个锁对象(JLS(java语言规范)中叫monitor)都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒,这个涉及到线程间的通信。看我们的例子,当第一个线程执行输出方法时,获得同步锁,执行输出方法,恰好此时第二个线程也要执行输出方法,但发现同步锁没有被释放,第二个线程就会进入就绪队列,等待锁被释放。一个线程执行互斥代码过程如下:

        1. 获得同步锁;
    
        2. 清空工作内存;
    
        3. 从主内存拷贝对象副本到工作内存;
    
        4. 执行代码(计算或者输出等);
    
        5. 刷新主内存数据;
    
        6. 释放同步锁。
    
        所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
    

    #### 3.2 Volatile
    用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。(那应该如何使用呢??)

    public class Counter {
    
        public static int count = 0;
    
        public static void inc() {
    
            //这里延迟1毫秒,使得结果明显
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
            }
            count++;
        }
    
        public static void main(String[] args) {
    
            //同时启动1000个线程,去进行i++计算,看看实际结果
    
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Counter.inc();
                    }
                }).start();
            }
    
            //这里每次运行的值都有可能不同,可能为1000
            System.out.println("运行结果:Counter.count=" + Counter.count);
        }
    }

    运行结果:Counter.count=995

    实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count=995,可以看出,在多线程的环境下,Counter.count并没有期望结果是1000
    很多人以为,这个是多线程并发问题,只需要在变量count之前加上volatile就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望。

    public class Counter {
        // 在count上使用volatile关键字
        public volatile static int count = 0;
    
        public static void inc() {
    
            //这里延迟1毫秒,使得结果明显
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
            }
    
            count++;
        }
    
        public static void main(String[] args) {
    
            //同时启动1000个线程,去进行i++计算,看看实际结果
    
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Counter.inc();
                    }
                }).start();
            }
    
            //这里每次运行的值都有可能不同,可能为1000
            System.out.println("运行结果:Counter.count=" + Counter.count);
        }
    }

    运行结果:Counter.count=992

    运行结果还是没有我们期望的1000,下面我们分析一下原因
    在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是JVM栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图描述这些交互:
    jvm栈

    read and load 从主存复制变量到当前工作内存
    use and assign 执行代码,改变共享变量值
    store and write 用工作内存数据刷新主存相关内容

    其中use and assign 可以多次出现

    但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样

    对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是当前最新的

    例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值

    在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6

    线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6

    导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。(所以volatile用来干啥?:( )

    展开全文
  • 线程同步:解决线程安全问题

    千次阅读 2018-02-09 18:50:45
    线程安全问题,是由于多个线程在访问共享的数据(共享的资源),并且操作共享数据的语句不止一条。那么这样在多条操作共享数据的之间线程就可能发生切换,从而引发线程安全问题。例如如下情况:public class ...
        多线程安全问题,是由于多个线程在访问共享的数据(共享的资源),并且操作共享数据的语句不止一条。那么这样在多条操作共享数据的之间线程就可能发生切换,从而引发线程安全问题。
    
    例如如下情况:
    public class ThreadDemo {
      public static void main(String[] args){
        Apple apple = new Apple();
        Thread t1 = new Thread(apple, "小明");
        Thread t2 = new Thread(apple, "二明");
        Thread t3 = new Thread(apple, "大明");
        Thread t4 = new Thread(apple, "小兰");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
      }
    
    }
    
    class Apple implements Runnable{
      /*有6个苹果用于分配*/
      int num = 6;
      @Override
      public void run() {
    
        while (true){
          if (num > 0) {
            try {
              Thread.sleep(10);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿了第" + num-- + "个苹果");
          }else {
            break;
          }
        }
      }
    }
    运行结果:
    	小兰拿了第5个苹果
    	小明拿了第6个苹果
    	大明拿了第4个苹果
    	二明拿了第3个苹果
    	小明拿了第2个苹果
    	大明拿了第1个苹果
    	小兰拿了第0个苹果
    	二明拿了第-1个苹果
    	小明拿了第-2个苹果
        这个问题中,为什么会出现有人拿到了0,-1,-2个苹果的情况呢?因为当二明进入run()方法时,还没来得及做num-- 操作时,此时num=1,另外两个线程也进入到run()方法,执行拿苹果的动作,所以就出现了出现-1,-2的结果。
        要解决上述多线程并发访问临界资源出现的安全性问,就要保证sleep()和拿苹果的操作同步完成。也即是说,在第一个线程拿完苹果,进行num--之前,其他线程不能进行 num>0 的判。
        想要知道你的多线程程序有没有安全问题,只要看线程任务中是否有多条代码在处理共享数据。
        解决多线程并发访问资源的安全问题,有三种方式:同步代码块、同步函数、锁机制(Lock)。
    方法一、同步代码块
    语法:
    synchronized(同步锁){
        //需要同步操作的代码
    }
    同步锁:
    为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制.也称为同步监听对象/同步锁/同步监听器/互斥锁。
    实际上,对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁,谁拿到锁,谁就可以进入代码块,其他线程只能在代码块外面等着,而且注意,在任何时候,最多允许一个线程拥有同步锁.
    Java程序运行可以使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象。
    public class ThreadDemo {
      public static void main(String[] args){
        Apple apple = new Apple();
        Thread t1 = new Thread(apple, "小明");
        Thread t2 = new Thread(apple, "二明");
        Thread t3 = new Thread(apple, "大明");
        Thread t4 = new Thread(apple, "小兰");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
      }
    
    }
    
    class Apple implements Runnable{
      /*有6个苹果用于分配*/
      int num = 6;
    //  Object obj = new Object();
      @Override
      public void run() {
        while (true){
          synchronized (Apple.class){
            if (num > 0) {
              try {
                Thread.sleep(10);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName() + "拿了第" + num-- + "个苹果");
            }else {
              break;
            }
          }
        }
      }
    }
    运行结果:
    小兰拿了第5个苹果
    小明拿了第6个苹果
    大明拿了第4个苹果
    二明拿了第3个苹果
    小明拿了第2个苹果
    大明拿了第1个苹果
    方法二、同步函数;
    使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着.
    Synchronized public void doFunction(){
        //需要同步的内容
    }
    同步锁是谁:
          对于非static方法,同步锁就是this.  
          对于static方法,我们使用当前方法所在类的字节码对象(xxx.class).
    public class ThreadDemo {
      public static void main(String[] args){
        Apple apple = new Apple();
        Thread t1 = new Thread(apple, "小明");
        Thread t2 = new Thread(apple, "二明");
        Thread t3 = new Thread(apple, "大明");
        Thread t4 = new Thread(apple, "小兰");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
      }
    
    }
    
    class Apple implements Runnable{
      /*有6个苹果用于分配*/
      int num = 6;
    //  Object obj = new Object();
      @Override
      public void run() {
        while (true){
          doRun();
        }
      }
    
      public synchronized void doRun(){
        synchronized (Apple.class){
          if (num > 0) {
            try {
              Thread.sleep(10);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿了一个苹果,还剩" + num-- + "个");
          }
        }
      }
    }
    运行结果:
    小明拿了一个苹果,还剩6个
    小明拿了一个苹果,还剩5个
    小明拿了一个苹果,还剩4个
    小明拿了一个苹果,还剩3个
    小明拿了一个苹果,还剩2个
    小明拿了一个苹果,还剩1个
    方法三、锁机(同步锁)
        Lock锁代替了同步代码块。在多线程中能够使用同步代码块的地方都可以使用Lock接口来代替,在java.util.concurrent.locks包下有接口Lock它是专门放负责描述锁这个事务。
        当我们使用Lock接口代替同步代码块的时候,就需要程序员手动的来获取锁和释放锁,如果在程序中出现了获取锁之后,没有释放锁,导致其他程序无法进入这个同步语句中。需要使用try-finally语句在finally中书写释放锁的代码。
        在Lock接口中lock方法和unLock方法分别是获取锁和释放锁。
    class AppleAllot implements Runnable{
      int num = 6;
      Lock lock = new ReentrantLock();
      @Override
      public void run() {
        while (true){
          //获取锁
          lock.lock();
          try{
            if (num > 0){
              System.out.println(Thread.currentThread().getName() + "拿了第" + num+ "个苹果");
              num--;
            }
          }finally {
            //释放锁
            lock.unlock();
          }
        }
      }
    }
    
    public class LockDemo{
      public static void main(String[] args){
        AppleAllot apple = new AppleAllot();
        Thread t1 = new Thread(apple, "小明");
        Thread t2 = new Thread(apple, "二明");
        Thread t3 = new Thread(apple, "大明");
        Thread t4 = new Thread(apple, "小兰");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
      }
    }
    运行结果:
    小明拿了第6个苹果
    小明拿了第5个苹果
    小明拿了第4个苹果
    小明拿了第3个苹果
    小明拿了第2个苹果
    小明拿了第1个苹果
    说明:
    Synchronized:
    使用synchronized关键字修饰方法,会导致加锁,虽然可以使该方法线程安全,但是会极大的降低该方法的执行效率,故要慎用该关键字。
    它无法中断一个正在等候获得锁的线程;
    也无法通过投票得到锁,如果不想等下去,也就没法得到锁;
    同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
    锁机制:
    Java.util.concurrent.lock 中的Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
    ReentrantLock类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。

    参考:http://blog.csdn.net/caidie_huang/article/details/52748973
    展开全文
  • 线程的同步与互斥(同步线程与异步线程,线程同步和异步问题) Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现, 无论是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同...
  • 线程同步和线程安全

    千次阅读 2016-02-21 15:18:46
    线程同步 同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。 “同”字从字面上容易理解为一起动作 其实不是,“同”字应是指协同、协助、互相配合。 如进程、线程同步,可理解为进程或...
  • 同步锁-线程安全问题解决方案

    万次阅读 多人点赞 2021-03-21 15:12:05
    上节笔记点这里-进程与线程笔记 我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件: 在多线程程序中 + 有共享数据 + 多条语句操作共享数据 多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起...
  • 线程同步与异步

    千次阅读 2018-08-29 11:50:32
    线程同步与异步 线程 同步 (synchronized) 异步 (asynchronized) 特点 A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去 A线程要请求...
  • Linux——线程同步和线程安全

    千次阅读 2017-10-30 22:33:34
    Linux——线程同步和线程安全 1、 线程同步 同步:多进程或者多线程访问临界资源时,必须进行同步控制。多进程或者多线程的 执行并不完全是绝对的并行运行,有可能主线程需要等待函数线程的某些条件的发生。 ...
  • Java 线程同步与死锁 学习笔记

    千次阅读 2016-09-25 10:02:24
    Java 线程同步与死锁 学习笔记Java 线程同步与死锁 学习笔记 1 多线程共享数据 2 线程同步 3 同步准则 4 线程死锁 1、 多线程共享数据 在多线程操作中,多个线程可能同时处理同一个资源,这就是多线程中的共享数据。...
  • 秒杀多线程第六篇 经典线程同步 事件Event

    万次阅读 多人点赞 2012-04-11 09:06:57
    阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇 一个经典的多线程同步问题》《秒杀多线程第五篇 经典线程同步关键段CS》 上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性...
  • 多线程七 经典线程同步与互斥总结

    万次阅读 2012-08-30 22:23:53
    前面《多线程二 多线程中的隐蔽问题揭秘》提出了一个经典的多线程同步互斥问题,这个问题包括了主线程子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后面分别用了四篇 《多线程三 ...
  • 线程同步:解决线程不安全问题

    千次阅读 2016-10-07 12:30:16
    当多个线程并发访问同一个资源对象时,可能会出现线程安全的问题,比如现有50个苹果,现在有请三个童鞋(小A,小B,小C)上台表演吃苹果.因为A,B,C三个人可以同时吃苹果,此时使用多线程技术来实现这个案例. class ...
  • 前面《秒杀多线程第四篇一个经典的多线程同步问题》提出了一个经典的多线程同步互斥问题,这个问题包括了主线程子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后面分别用了四篇《秒杀多...
  • 线程安全与数据同步 1.synchronized 关键字 synchronized 关键字可以实现一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么对该对象的所有读 或者写都将通过同步的方式来 进行 ...
  • Java线程(二):线程同步synchronized和volatile

    万次阅读 多人点赞 2012-04-04 10:49:28
    要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。拿上篇博文中的例子来说明,在多个线程之间共享了Count类的一个...
  • (转)Java面试——线程同步volatilesynchronized详解注:本文转载地址http://blog.csdn.net/seu_calvin/article/details/523700680. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时...
  • Python多线程—线程同步

    千次阅读 2019-03-25 23:05:17
    线程同步的真实意思和字面意思恰好相反。 线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。 Python threading模块提供了Lock/RLock、Condition、queue...
  • 线程同步辅助类

    万次阅读 2017-05-17 00:04:55
    前言关于线程的基础知识可以查看《有关线程的相关知识(上)》和《有关线程的相关知识(下)》,线程同步synchronized和Lock可以查看《线程同步synchronized》和《线程同步Lock》,在并发工具类中提供
  • 秒杀多线程第五篇 经典线程同步 关键段CS

    万次阅读 多人点赞 2012-04-11 09:06:40
    上一篇《秒杀多线程第四篇 一个经典的多线程同步问题》提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题。本文首先介绍下如何使用关键段,然后再深层次的分析下关键段的实现...
  • 秒杀多线程第八篇 经典线程同步 信号量Semaphore

    万次阅读 多人点赞 2012-05-03 09:30:00
    阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇一个经典的多线程同步问题》《秒杀多线程第五篇经典线程同步关键段CS》《秒杀多线程第六篇经典线程同步事件Event》《秒杀多线程第七篇经典线程同步互斥量Mutex》...
  • 线程同步机制

    千次阅读 2018-06-24 01:08:04
    线程同步主要用于协调对临界资源的访问,临界资源可以是硬件设备(比如打印机)、磁盘(文件)、内存(变量、数组、队列等)。 线程同步有4种机制: 临界区 互斥量 事件 信号量 他们的主要区别在于: ...
  • 线程同步常用方式区别

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

    万次阅读 2018-02-13 00:40:33
    线程同步是利用互斥锁(mutex)条件(condition)变量的结合,经常出现于生产者消费者模式场景中。先定义相关变量:#include &lt;pthread.h&gt; //生产者线程 pthread_t thread_product; //消费者线...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 871,870
精华内容 348,748
关键字:

线程同步与线程安全