精华内容
下载资源
问答
  • 线程安全和线程同步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用来干啥?:( )

    展开全文
  • 线程安全在Java中是一个很重要的课题。Java提供的多线程环境支持使用Java线程。我们都知道多线程共享一些对象实例的话,可能会在读取更新共享数据的事后产生数据不一致问题。线程安全之所以会产生数据的不一致问题...

    线程安全在Java中是一个很重要的课题。Java提供的多线程环境支持使用Java线程。我们都知道多线程共享一些对象实例的话,可能会在读取和更新共享数据的事后产生数据不一致问题。

    线程安全

    之所以会产生数据的不一致问题,是因为更新实例变量等类似的行为并非是原子操作。这类操作会有三个步骤:

    • 读取当前的值
    • 做一些必要的操作来获取更新的值
    • 将更新的值写会变量之中

    我们来看如下程序中多线程如何更新和共享数据:

    package com.sapphire.threads;
    
    public class ThreadSafety {
    
        public static void main(String[] args) throws InterruptedException {
    
            ProcessingThread pt = new ProcessingThread();
            Thread t1 = new Thread(pt, "t1");
            t1.start();
            Thread t2 = new Thread(pt, "t2");
            t2.start();
            //wait for threads to finish processing
            t1.join();
            t2.join();
            System.out.println("Processing count="+pt.getCount());
        }
    }
    
    class ProcessingThread implements Runnable{
        private int count;
    
        @Override
        public void run() {
            for(int i=1; i < 5; i++){
                processSomething(i);
                count++;
            }
        }
    
        public int getCount() {
            return this.count;
        }
    
        private void processSomething(int i) {
            // processing some job
            try {
                Thread.sleep(i*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    在上面的程序中的的for循环里面,count的值每次是自增为1的,执行了四次,因为我们有两个线程,count的值在两个线程执行完毕以后应该是8,但是当你运行上面的程序多次的话,你会发现count的值总是在6,7,8这几个数字之间。这是因为尽管count++操作看起来是一个原子操作,但是实际上它并不是,所以导致了数据的冲突。

    Java中的线程安全

    线程安全就是指通过对一些处理令我们的程序能够安全的使用多线程的编程模型。以下有一些不同的方式来令我们的程序保证线程安全。

    • 线程同步是最简单和常用的方法来确保线程安全
    • 通过使用java.util.concurrent.atomic包中的原子类,可以确保操作的原子性
    • 通过使用java.util.concurrent.locks包中的锁可以确保线程安全
    • 使用线程安全的并发集合,比如ConcurrentHashMap来确保线程安全
    • 通过volatile关键字来确保每次的变量使用都从内存中访问数据,而非访问线程缓存

    Java 同步

    同步是我们来获得线程安全的常用方法。JVM会保证同步的代码只会在同一时间仅仅由一个线程来执行。Java的关键字synchronized就是用来创建同步代码的,在内部的执行的时候,synchronized关键字会锁定对象或者类来确保只有一个线程来进入同步的代码块。

    • Java的同步是通过锁定/解锁资源来实现的。在任何线程进入同步代码之前,线程必须请求对象的锁,而在代码执行结束的时候,线程再释放掉该锁,这样其他线程可以再次获取到这个锁。在某个线程执行同步代码的时候,其他的线程只能处于等待状态来等待被锁定的资源。
    • synchronized关键字有两种用法,其一是在方法级别上声明,另一种是创建同步代码块。
    • 当方法被同步的时候,JVM锁定的是对象,如果方法是静态的,那么就会锁定这个。所以,通常最佳的实践是使用同步代码块来锁定需要同步的代码。
    • 当创建同步代码块时,我们需要提供锁定的资源,可以是类本身,也可以是类的成员变量。
    • synchronized(this)会在进入同步代码块之前锁定整个对象。
    • 开发者应该使用最低级别的锁。举例来说,如果类中存在多个需要同步的地方,如果一个方法的访问就锁定了整个对象,那么其他同步代码块就无法被访问了。当我们锁定对象的时候,线程请求的锁是针对对象所有的成员变量的。
    • Java的同步机制提供数据一致性的代价就是性能的损失,所以最好仅仅在最需要的时候使用。
    • Java的同步机制仅仅在同一个JVM中生效的,所以当开发者尝试锁定不同JVM中的多个资源的时候,Java的同步机制是不会有效的。
    • Java的同步机制可能会导致死锁的,需要注意防止产生死锁。
    • Java的synchronized关键字不能同用于变量和构造函数。
    • 在使用Java同步代码块的时候,最好通过创建一个额外的私有对象用来锁定,因为这个引用的对象并不会影响其他的代码。比如,如果开发者针对引用的对象包含一些set方法的调用的话,那么并行的执行可能会导致同步对象的改变。
    • 开发者不应该使用任何常量池中的对象,比如String对象就不应该用来作为同步锁,因为大量的代码可能依赖于相同的字符串,线程就会尝试去请求String pool中的对象锁,这样就会令不同的毫不相关的代码锁定相同的资源。

    Java中不少的库也是通过synchronized来实现简单的同步,比如与ArrayList相对应的Vector,和HashMap相对应的HashTable甚至是常用的StringBufferStringBuilder,如果开发者查看过对应的源码,就会发现那些线程安全的类只是在方法上加上了synchronized关键字而已。

    下面是一些我们保证线程安全的做法:

    //dummy object variable for synchronization
    private Object mutex=new Object();
    ...
    //using synchronized block to read, increment and update count value synchronously
    synchronized (mutex) {
        count++;
    }

    下面是一些代码帮助我们了解同步的机制:

    public class MyObject {
    
      // Locks on the object's monitor
      public synchronized void doSomething() { 
        // ...
      }
    }
    
    // Hackers code
    MyObject myObject = new MyObject();
    synchronized (myObject) {
      while (true) {
        // Indefinitely delay myObject
        Thread.sleep(Integer.MAX_VALUE); 
      }
    }

    可以看出Hacker的代码是试着锁定myObject的实例,而一旦获得了对应的对象锁,就永远不会释放对象锁,导致doSomething()方法会永远阻塞,一直等待对象锁的释放。这就会导致系统死锁,导致服务拒绝(Denial of Service)。

    再参考如下代码:

    public class MyObject {
      public Object lock = new Object();
    
      public void doSomething() {
        synchronized (lock) {
          // ...
        }
      }
    }
    
    //untrusted code
    
    MyObject myObject = new MyObject();
    //change the lock Object reference
    myObject.lock = new Object();

    需要注意的是锁定的对象是一个共有的变量,一旦我们改变原对象所引用的对象,我们就可以任意的并行执行同步代码块中的内容了。如果开发者为私有的锁对象提供setter方法的话,也会导致一样的问题。

    再参考如下代码:

    public class MyObject {
      //locks on the class object's monitor
      public static synchronized void doSomething() { 
        // ...
      }
    }
    
    // hackers code
    synchronized (MyObject.class) {
      while (true) {
        Thread.sleep(Integer.MAX_VALUE); // Indefinitely delay MyObject
      }
    }

    这段代码与第一段代码很类似,前文已经提到了,静态的static方法会锁定类,所以一旦hacker代码的获得了MyObject的类锁,那么就会形成死锁。

    下面是另一个例子:

    package com.sapphire.threads;
    
    import java.util.Arrays;
    
    public class SyncronizedMethod {
    
        public static void main(String[] args) throws InterruptedException {
            String[] arr = {"1","2","3","4","5","6"};
            HashMapProcessor hmp = new HashMapProcessor(arr);
            Thread t1=new Thread(hmp, "t1");
            Thread t2=new Thread(hmp, "t2");
            Thread t3=new Thread(hmp, "t3");
            long start = System.currentTimeMillis();
            //start all the threads
            t1.start();t2.start();t3.start();
            //wait for threads to finish
            t1.join();t2.join();t3.join();
            System.out.println("Time taken= "+(System.currentTimeMillis()-start));
            //check the shared variable value now
            System.out.println(Arrays.asList(hmp.getMap()));
        }
    
    }
    
    class HashMapProcessor implements Runnable{
    
        private String[] strArr = null;
    
        public HashMapProcessor(String[] m){
            this.strArr=m;
        }
    
        public String[] getMap() {
            return strArr;
        }
    
        @Override
        public void run() {
            processArr(Thread.currentThread().getName());
        }
    
        private void processArr(String name) {
            for(int i=0; i < strArr.length; i++){
                //process data and append thread name
                processSomething(i);
                addThreadName(i, name);
            }
        }
    
        private void addThreadName(int i, String name) {
            strArr[i] = strArr[i] +":"+name;
        }
    
        private void processSomething(int index) {
            // processing some job
            try {
                Thread.sleep(index*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }

    代码的执行结果如下:

    Time taken= 15005
    [1:t2:t3, 2:t1, 3:t3, 4:t1:t3, 5:t2:t1, 6:t3]

    可以看出,String的数组出现了不一致问题,因为共享数据以及缺少同步。下面的代码可以改变addThreadName(...)方法来令程序运行正确:

    private Object lock = new Object();
    private void addThreadName(int i, String name) {
        synchronized(lock){
        strArr[i] = strArr[i] +":"+name;
        }
    }

    在我们修改了上面的代码以后,程序的输出结果如下:

    Time taken= 15004
    [1:t1:t2:t3, 2:t2:t1:t3, 3:t2:t3:t1, 4:t3:t2:t1, 5:t2:t1:t3, 6:t2:t1:t3]
    展开全文
  • 线程同步线程安全

    千次阅读 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之后,子进程拿到的锁都是解锁状态,避免了死锁。




    展开全文
  • NoHttp核心架构之多线程通信、线程安全、线程同步;synchronized锁,Lock锁;具体讲解请移步博客:http://blog.csdn.net/yanzhenjie1003/article/details/50992468
  • 线程安全问题 线程的安全问题主要体现在,当需要访问公共资源是两个多个线程可能会出现问题 举个例子 class YdThread implements Runnable { private int num = 10; public void run() { while(num &...

    线程安全问题

    线程的安全问题主要体现在,当需要访问公共资源是两个多个线程可能会出现问题
    举个例子

    class YdThread implements Runnable {
    
        private int num = 10;
    
        public void run() {
            while(num > 0) {
                try {
                    Thread.sleep(100);//加个sleep便于观察
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.print(num+" ");
                num--;
            }
        }
    }
    /**
    *主函数中调用
    */
    public class Main {
    
        /**
         * 主函数
         */
        public static void main(String[] args) {
            YdThread td = new YdThread();
            Thread  t1 = new Thread(td);
            Thread  t2 = new Thread(td);
            t1.start();
            t2.start();
        }
    }

    最终打印的结果:
    10 9 8 8 6 6 4 4 2 2 0
    不仅仅出现了0 其中2 4 6 8 都出现的多次

    当num=1时
    因为在线程1进入while(num > 0)之后睡眠,然后线程2 进入此时num 还是大于0的
    因此两个线程都处于while循环中
    接下来一个线程执行下面代码另一个线程也执行因此会出现打印0和重复的情况

    线程安全问题出现的条件:

    • 多个线程操作共享数据
    • 操作共享数据的语句有多条

    这就是线程的安全问题,因此就出现的关键字synchronized,相当于加上了一把锁 ,产生两种同步线程的方法:


    同步函数和同步代码块synchronized

    1、同步函数

    public synchronized void sale(){
    }
    在方法前加synchronized关键字,为sale方法加上一把锁,当一个线程进入执行的时候获取的该对象锁,其他线程就不能进入,只有当该线程执行完之后释放对象锁,其他线程才能进入
    同步函数的对象锁是this是固定的,也就是当前的对象

    2、同步代码块:

    synchronized(对象锁){
    . 代码
    }

    同步代码块是采取ssynchronized修饰代码块来进行同步
    需要再synchronized()中加入任意对象作为对象锁,该锁可以是任意的对象

    同步代码块和同步函数的区别:

    同步代码块的锁是任意的,同步函数的锁固定是this
    建议使用同步代码块

    同步能解决线程安全问题,但是会降低效率,因为同步外的线程都需要判断同步锁


    关键字volatile

    当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

    并发编程会遇到三个问题。原子性问题,可见性问题,有序性问题

    原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
    可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
    有序性:即程序执行的顺序按照代码的先后顺序执行


    线程的死锁问题

    如果线程进行了同步的嵌套,就会产生死锁问题:
    示例:

    面试时可能会需要写一个死锁

    public class LockDemo {
    
        /**
        *主函数
        */
        public static void main(String args[]){
            Demo d = new Demo();
            Thread t1 = new Thread(d);
            Thread t2 = new Thread(d);
            t1.start();
            d.flag = false;//将flag赋值为false让线程二执行else中的代码
            t2.start();
        }
    
    
    }
    class Demo implements Runnable{
         static boolean flag = true;
        Object locka = new Object();
        Object lockb = new Object();
    
        public void run(){
    
            if(flag){
                while(true){
                    synchronized(locka){
                        System.out.println(Thread.currentThread().getName()+"----get----locka");
                        try{
                             Thread.sleep(100);
                         }catch(InterruptedException e){}//让线程1睡眠一段时间加大产生思索概率便于观察
                    synchronized(lockb){
                        System.out.println(Thread.currentThread().getName()+"----get----lockb");
                    }
                }
                }
            }else{
                while(true){
                    synchronized(lockb){
                    System.out.println(Thread.currentThread().getName()+"----get----lockb");
                    synchronized(locka){
                        System.out.println(Thread.currentThread().getName()+"----get----locka");
                    }
                }
                }
            }
        }
    }

    加入循环也是为了便于观察,当1线程获得对象锁locka,等待对象锁lockb;
    2线程获得对象锁lockb,等待对象锁locka,因此处于死锁状态


    Java单例化的两种方式

    java编程中可能会遇到需要单例化的情况,比如多个客户端但是只能有一个服务端
    因此服务端对象需要进行单例化
    

    两种方式都需要把构造方法变成私有,不能通过构造方法获取对象

    1、饿汉式

    饿汉式

    public class SingleThread {
        /**
         * 饿汉式
         */
        private static final SingleThread s = new SingleThread();//该对象为final只能赋值一次
        private SingleThread() {
        }
        public static SingleThread getInstance() {
            return s;
        }
    }

    懒汉式:

    public class SingleThread {
        /**
         * 懒汉式
         */
        private static SingleThread s = null;
        private SingleThread() {
        }
        public static SingleThread getInstance() {
            if(s == null){
                s = new SingleThread();//创建对象
            }
            return s;//返回对象
        }
    }

    懒汉式会出现安全问题,因为对共享数据s有多条操作语句,因此需要用同步解决,
    利用同步函数

    public class SingleThread {
        /**
         * 懒汉式
         */
        private static SingleThread s = null;
        private SingleThread() {
        }
        public synchronized static SingleThread getInstance() {//加上了同步锁
            if(s == null){
                s = new SingleThread();//创建对象
            }
            return s;//返回对象
        }
    }

    此方法虽然解决了安全问题但是以后每次都需要判断同步锁,降低了效率,因此改用同步代码块

    public class SingleThread {
        /**
         * 懒汉式
         */
        private static SingleThread s = null;
        private  SingleThread() {
        }
        public  static SingleThread getInstance() {
            if(s == null){
                synchronized(SingleThread.class){
                    if(s == null){
                        s = new SingleThread();//创建对象
                    }
                }
            }
            return s;//返回对象
        }
    }

    此处因为该方法是静态的,因此在类创建时就已经存在,此时还没有产生该类的实例,所以不能用this作为对象锁,使用 类名+class 获取的是该类字节码所属对象(java被转化为字节码文件时会分配当前的class文件所处的对象)

    以上代码看似没问题,其实不然
    在 Java 中双重检查模式无效的原因是在不同步的情况下引用类型不是线程安全的。对于除了 long 和 double 的基本类型,双重检查模式是适用 的。。JDK5.0以后版本若s为volatile则可行

    public class SingleThread {
        /**
         * 懒汉式
         */
        private volatile static SingleThread s = null;
        private SingleThread() {
        }
        public  static SingleThread getInstance() {
            if(s == null){
                synchronized(SingleThread.class){
                    if(s == null){
                        s = new SingleThread();//创建对象
                    }
                }
            }
            return s;//返回对象
        }
    }

    饿汉式比较简单,也不会出现安全问题,因此推荐使用饿汉式,但是面试时一般都考懒汉式

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

    千次阅读 2016-02-21 15:18:46
    如进程、线程同步,可理解为进程或线程AB一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。 所谓同步,就是在发出一个功能调用时,在没有得到...
  • 同步锁-线程安全问题解决方案

    万次阅读 多人点赞 2021-03-21 15:12:05
    经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象. 上节笔记点这里-进程与线程笔记 我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件: 在多线程程序中 +...
  • 获取线程基本信息_暂停线程_线程的优先级_多线程安全性_线程同步1.线程操作的常用方法2.暂停线程执行 sleep_yield_join_stop3.线程的优先级问题4.多线程安全性问题5.线程同步1.同步代码块2.同步方法 1.线程操作...
  • golang:线程定义、同步线程安全

    千次阅读 2020-09-07 16:42:12
    线程编程时一种比多线程编程更灵活、高效的并发编程关系 go并发编程模型在底层使用的是内核提供的posix线程库 进程总结 线程 线程可以看成是进程中的控制流。 一个进程中至少包含一个线程【主线程】,进程中至少...
  • 一、线程并发同步概念线程同步其核心就在于一个“同”。所谓“同”就是协同、协助、配合,“同步”就是协同步调昨,也就是按照预定的先后顺序进行运行,即“你先,我等, 你做完,我再做”。线程同步,就是当线程...
  • 线程安全与线程同步及lock的使用

    千次阅读 2019-12-30 17:41:58
    线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全:就是不提供...
  • 所以为了避免资源抢占的问题,在使用线程同步时应该考虑线程安全 1.不安全线程: 同一进程下不同的线程是共享系统资源的,资源好比一个独木桥,如果两个线程,同时过独木桥的话就会发生不安全的情况(资源的抢...
  • 【多线程】线程安全、锁的同步和异步

    千次阅读 多人点赞 2018-01-22 20:13:36
     非线程安全:非线程主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。 synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为...
  • 最近在学习的时候,一直很疑惑VectorList的两个容器的区别。 在网上查了好多资料,
  • 线程间的通信和同步

    千次阅读 多人点赞 2019-06-15 11:21:07
    最近看了很多关于网络编程多线程的书,为了以后查看相关...四、线程安全 五、线程的同步 (一)互斥量mutex (二)临界区 critical section (三)信号量 semaphore (四)事件 event 一、什么是多线程? ...
  • JAVA线程同步方法和同步代码块

    千次阅读 2018-08-08 16:35:58
    线程安全和线程安全 脏读 非线程安全:多个线程对同一个对象的中的实例变量进行并发访问,产生后果就是脏读,也就是获取的数据被更改。 非线程安全问题存在与“实例变量”中,如果是方法内部的私有变量,就不...
  • 继续讨论多窗口卖票问题中的线程安全问题: 方式二: 一、问题: 出现了线程安全问题 ? 二、问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程...
  • 线程同步:解决线程安全问题

    千次阅读 2018-02-09 18:50:45
    线程安全问题,是由于多个线程在访问共享的数据(共享的资源),并且操作共享数据的语句不止一条。那么这样在多条操作共享数据的之间线程就可能发生切换,从而引发线程安全问题。例如如下情况:public class ...
  • Python多线程线程同步

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

    千次阅读 2018-07-01 16:36:10
    (最安全,但给没必要同步的方法使用,会浪费等待时间)异步:发送一个请求,不等待返回,随时可以在发送下一个请求。同步可以避免出现死锁,读脏数据的发生。一般共享某一资源时使用(公共变量等)同步需要等待,...
  • Java线程安全和线程安全

    万次阅读 多人点赞 2013-05-16 14:09:47
    ArrayListVector有什么区别?...面对这样的问题,回答是:ArrayList是非线程安全的,Vector是线程安全的;HashMap是非线程安全的,HashTable是线程安全的;StringBuilder是非线程安全的,StringBuff
  • java线程安全同步,异步

    千次阅读 2016-08-09 12:44:48
    线程是比进程更小的执行单位,是在进程基础上进行的进一步划分。所谓多线程是指进程在执行...1、什么是线程安全? 进程在运行时为了更好的利用资源提高运行效率,会产生多个线程,但是这多个线程运行时,会对同一个资源
  • 什么是线程同步和线程异步?

    千次阅读 2019-05-31 13:41:39
    1、什么是线程同步和线程异步 线程同步:是多个线程同时访问同一资源,等待资源访问结束,浪费时间,效率不高 线程异步:访问资源时,如果有空闲时间,则可在空闲等待同时访问其他资源,实现多线程机制 异步处理...
  • 高并发下的线程安全实现——互斥同步好久没来csdn上写博客了,去年(16年)来到杭州后,忙得沉淀的时间都没有了,这段时间空闲下来,慢慢补上! 线程允许多个活动同时进行,并发下有很多东西可能出错,比如数据错误...
  • 哪些类是线程安全同步)的?

    千次阅读 2018-09-16 12:23:38
    线程同步:喂,SHE 喂(Vector) S(Stack) H(hashtable) E(enumeration)
  • 线程安全
  • 知识的学习在于点滴记录,坚持不懈;知识的学习要有深度广度,不能只流于表面,坐井观天;知识要善于总结,不仅能够理解,更知道如何表达! 目录 如果需要录屏资料,请私信邮箱shitou3000@qq.com获取。 ...
  • 什么是线程安全和线程不安全

    千次阅读 2019-12-02 16:57:42
    1、线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 2、线程不安全: 是指不提供加锁机制保护,有可能出现多个线程...
  • Java 多线程同步和异步详解

    千次阅读 2018-05-31 10:00:32
    转载自 https://www.cnblogs.com/mengyuxin/p/5358364.htmljava线程 同步与异步 线程池1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了...
  • 什么是线程安全?如何保证线程安全

    万次阅读 多人点赞 2019-05-27 23:22:44
    如果不用考虑这些线程在运行时环境下的调度交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。 《深入理解Java虚拟机...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 412,245
精华内容 164,898
关键字:

线程安全和同步