zoukankan      html  css  js  c++  java
  • StringBuilder StringBuffer

    当我们查看JDK API的时候,总会发现一些类说明写着,线程安全或者线程不安全,比如说到StringBuilder中,有这么一句,“将StringBuilder 的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用StringBuffer。”,提到StringBuffer时,说到“StringBuffer是线程安全的可变字符序列,一个类似于String的字符串缓冲区,虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致”。StringBuilder是一个可变的字符序列,此类提供一个与StringBuffe兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。将StringBuilder的实例用于多个线程是不安全的,如果需要这样的同步,则建议使用StringBuffer。

       根据以上JDK文档中对StringBuffer和StringBuilder的描述,得到对String、StringBuilder与StringBuffer三者使用情况的总结:
       1、如果要操作少量的数据用String
       2、单线程操作字符串缓冲区下操作大量数据StringBuilder
       3、多线程操作字符串缓冲区下操作大量数据StringBuffer

       那么下面手动创建一个线程不安全的类,然后在多线程中使用这个类,看看有什么效果。

    public class Count {  
        private int num;  
        public void count() {  
            for(int i = 1; i <= 10; i++) {  
                num += i;  
            }  
            System.out.println(Thread.currentThread().getName() + "-" + num);  
        }  
    } 
     

       在这个类中的count方法计算1一直加到10的和,并输出当前线程名和总和,我们期望的是每个线程都会输出55。

    public class ThreadTest {  
        public static void main(String[] args) {  
            Runnable runnable = new Runnable() {  
                Count count = new Count();  
                public void run() {  
                    count.count();  
                }  
            };  
    
            for(int i = 0; i < 10; i++) {  
                new Thread(runnable).start();  
            }  
        }  
    }  

       这里启动了10个线程,看一下输出结果:

    Thread-0-55  
    Thread-1-110  
    Thread-2-165  
    Thread-4-220  
    Thread-5-275  
    Thread-6-330  
    Thread-3-385  
    Thread-7-440  
    Thread-8-495  
    Thread-9-550  
    •  

       只有Thread-0线程输出的结果是我们期望的,而输出的是每次都累加的,要想得到我们期望的结果,有几种解决方案:

       1、将Count类中的成员变量num变成count方法的局部变量;

    public class Count {  
        public void count() {  
            int num = 0;  
            for(int i = 1; i <= 10; i++) {  
                num += i;  
            }  
            System.out.println(Thread.currentThread().getName() + ”-“ + num);  
        }  
    }  
    •  

       2、将线程类成员变量拿到run方法中,这时count引用是线程内的局部变量;

    public class ThreadTest4 {  
        public static void main(String[] args) {  
            Runnable runnable = new Runnable() {  
                public void run() {  
                    Count count = new Count();  
                    count.count();  
                }  
            };  
            for(int i = 0; i < 10; i++) {  
                new Thread(runnable).start();  
            }  
        }  
    }   
    •  

       3、每次启动一个线程使用不同的线程类,不推荐。

       通过上述测试,我们发现,存在成员变量的类用于多线程时是不安全的,不安全体现在这个成员变量可能发生非原子性的操作,而变量定义在方法内也就是局部变量是线程安全的。想想在使用struts1时,不推荐创建成员变量,因为action是单例的,如果创建了成员变量,就会存在线程不安全的隐患,而struts2是每一次请求都会创建一个action,就不用考虑线程安全的问题。所以,日常开发中,通常需要考虑成员变量或者说全局变量在多线程环境下,是否会引发一些问题

       要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性

       多个线程之间是不能直接传递数据进行交互的,它们之间的交互只能通过共享变量来实现。拿上面的例子来说明,在多个线程之间共享了Count类的一个实例,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存count对象的一个副本,当线程操作count对象时,首先从主内存复制count对象到工作内存中,然后执行代码count.count(),改变了num值,最后用工作内存中的count刷新主内存的 count。当一个对象在多个工作内存中都存在副本时,如果一个工作内存刷新了主内存中的共享变量,其它线程也应该能够看到被修改后的值,此为可见性

       多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。

  • 相关阅读:
    第36课 经典问题解析三
    第35课 函数对象分析
    67. Add Binary
    66. Plus One
    58. Length of Last Word
    53. Maximum Subarray
    38. Count and Say
    35. Search Insert Position
    28. Implement strStr()
    27. Remove Element
  • 原文地址:https://www.cnblogs.com/kakaisgood/p/9530648.html
Copyright © 2011-2022 走看看