Java中的字符串-String & StringBuilder

引言:

操作系统课程上学习的生产者消费者模型可以说是学习并发的最好例子。这里需要注意Java不支持进程,只支持多线程。本篇文章将以一个最简单的生产者消费者模型进行Java并发的讲解。学习了本篇博文你应该学会了一下几个内容

1. 多个线程如何正确并发对一个变量进行读和写

2. 生产者消费者模型的实现

Java并发:

上文说了Java中没有进程只有线程,所以Java的并发只涉及到线程。在Java里可以通过两种方法创建一个线程,第一种为继承Thread类,第二种为实现Runnable接口。两种方法个人更偏向于第二种,因为在Java中没有多继承,所以如果采用第一种继承Thread类,那么就不能继承其他的类了。当然如果一个类确实不需要继承其他的类只是一个线程任务类那么继承Thread也没关系。总体而言二者没什么区别。

生产者消费者模型

生产者消费者模型在工程中到处被应用到。这是一个应用广泛而且很简单的模型。通常会有一个“生产者”进行数据的生产或者添加由一个或者多个消费者进行数据的消费。比如我们想做一个装修订单的推送需求。这个场景的生产者就是订单的生产系统,每生产一个订单我们就将一个订单扔到一个集合中,同时创建若干个给商家推送的线程任务,这些任务就监听这订单集合,发现有订单就取出来(这就是消费行为)然后匹配商家进行推送。这么做的好处

1. 当订单产生的速度明显快过一个订单被推送给商家的速度时(当数据增多这个现象是必然要发生的),使用多个订单推送任务进行推送了的好处就不言而喻了。如果只是来一个订单就进行推送显然已经跟不上订单的产生速度了。

2. 松耦合。松耦合几乎是所有方法,模型,框架的最终目标。采用生产者消费者可以让修改订单系统和推荐系统互不干扰。不用牵一发而动全身

看下面的一个小例子。

public class ThreadTest {
    //可是理解为订单数量
    public static int count = 0;
    public static void main(String[] argvs) {
        Producer producer = new Producer();
        Consumer consumer1 = new Consumer();
         // 继承Thread创建线程通过start方法来启动线程
        producer.start();
        // 实现Runnable接口创建线程通过run方法启动线程
        consumer1.run();
        System.out.println(count);
    }
    //生产者生产一个订单
    public static void countInc() {
        count++;
    }
    // 消费者处理一个订单
    public static void countDec() {
        count--;
    }
}
class Producer extends Thread {

    // 这个线程采用继承Thread来实现需要重写run方法。 这个生产者没20MS就生产一个订单
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // TODO Auto-generated method stub
            ThreadTest.countInc();
            try {
                sleep(20);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}
class Consumer implements Runnable {

    @Override
    // 这个线程采用实现Runnable接口来实现,需要重写run方法,这个消费者没20MS就消费一个订单
    public void run() {
        // TODO Auto-generated method stub
        for(int i = 0; i < 10;i++) {
            ThreadTest.countDec();
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

synchronized实现Java并发保护

这个实现特别简单。只要在函数countInc以及countDec都加上一个synchronized关键字就可以了。修改如下

public class ThreadTest {
    //可是理解为订单数量
    public static int count = 0;
    public static void main(String[] argvs) {
        Producer producer = new Producer();
        Consumer consumer1 = new Consumer();
         // 继承Thread创建线程通过start方法来启动线程
        producer.start();
        // 实现Runnable接口创建线程通过run方法启动线程
        consumer1.run();
        System.out.println(count);
    }
    //生产者生产一个订单
    public static synchronized void countInc() {
        count++;     // save some thing to mysql
    }
    // 消费者处理一个订单
    public static synchronized void countDec() {
        count--;     // save some thing to mysql
    }
}

其余代码不变即可。这次再运行则每次都是0,实现了多线程对count访问的线程安全目的。关键字synchronized虽然好用方便但是这个锁是对一个对象上锁的,锁的粒度比较大,因此效率很低。比如函数countInc如果不仅仅是对count+1这么简单呢,比如做了一些数据库操作,那么关键字synchronized会导致调用countInc的时候整个ThreadTest的其他所有的带有关键字synchronized的方法都不可以调用了,因为被锁住了。这样效率很低的,因为我们的目的仅仅是对count++这一行代码加锁就可以了。因此Java里提供了各种各样的锁。

Lock实现Java并发

使用Lock后修改的代码如下

public class ThreadTest {
    //可是理解为订单数量
    public static int count = 0;
    public static Lock lock = new ReentrantLock();
    public static void main(String[] argvs) {
        Producer producer = new Producer();
        Consumer consumer1 = new Consumer();
         // 继承Thread创建线程通过start方法来启动线程
        producer.start();
        // 实现Runnable接口创建线程通过run方法启动线程
        consumer1.run();
        System.out.println(count);
    }
    //生产者生产一个订单
    public static void countInc() {
        lock.lock();
        count++;
        lock.unlock();
        // 一些其他不需要锁的代码
    }
    // 消费者处理一个订单
    public static void countDec() {
        lock.lock();
        count--;
        lock.unlock();
        // 一些其他不需要锁的代码
    }
}

可以看到这里new了一个ReentrantLock,Java还有很多其他类型的锁,以后有机会再详细说明这里暂且不提。可以看到对于count++以及count--这两行代码运行之前都想要进行加锁,这样这两行代码的执行就互质了,谁得到lock谁才能执行。这里一定要记住,调用了lock()函数一定要在对应的地方调用unlock()函数,不然就废了,整个系统因为这一个小小的锁就卡死了。当然了你可能还会觉得这么做比较麻烦,有没有原子的整形的,原子的整形就是线程安全的不需要进行保护。答案是肯定有的。

AtomicInteger

代码修改如下

public class ThreadTest {
    //可是理解为订单数量
    public static AtomicInteger count = new AtomicInteger(0);public static void main(String[] argvs) {
        Producer producer = new Producer();
        Consumer consumer1 = new Consumer();
         // 继承Thread创建线程通过start方法来启动线程
        producer.start();
        // 实现Runnable接口创建线程通过run方法启动线程
        consumer1.run();
        System.out.println(count);
    }
    //生产者生产一个订单
    public static void countInc() {     // count加1
        count.incrementAndGet();
        // 一些其他不需要锁的代码
    }
    // 消费者处理一个订单
    public static void countDec() {     //count减1
        count.decrementAndGet();
        // 一些其他不需要锁的代码
    }
}

AtomicInteger与int的区别就像hashtable与hashmap的区别一样,一个是线程安全的,一个是线程不安全的。AtomicInteger是Java实现的支持原子操作的int类型,所谓原子操作就是在操作期间一定不会发生线程切换一定是线程安全的。除了AtomicInteger之外在java.util.concurrent.atomic包下有各种数据类型的原子实现。

OK,这篇的内容就这么多,这篇多而杂,设计的话题比较高大上,需要好好吸收消化。其实这只是Java并发编程的冰山一角,但是学会了这个小小的生产者消费者模型却一定会让你受益匪浅的。加油吧,少年。

时间: 2024-11-03 01:23:11

Java中的字符串-String & StringBuilder的相关文章

java中的字符串String

一.String简介d 参考:https://www.cnblogs.com/zhangyinhua/p/7689974.html String类代表字符串. java.lang.String: Java程序中的所有字符串文字(例如"abc" )都被实现为此类的实例. 字符串String的值在创建后不能被更改 String源码 1 /** String的属性值 */ 2 private final char value[]; 3 4 /** The offset is the firs

JAVA中,字符串STRING与STRINGBUILDER的效率差异

如果可变字符串操作较多的话,用STRINGBUILDER显然优势得多. public class HelloJava { public static void main(String[] args) { // TODO Auto-generated method stub String str = "a"; long starTime = System.currentTimeMillis(); for(int i = 0; i<10000;i++){ str = str + i;

[转]java中的字符串相关知识整理

字符串为什么这么重要 写了多年java的开发应该对String不陌生,但是我却越发觉得它陌生.每学一门编程语言就会与字符串这个关键词打不少交道.看来它真的很重要. 字符串就是一系列的字符组合的串,如果写过C/C++的应该就了解,在字符串的操作上会有许多操作的函数与类,用于简化代码的开发.一方面是因为字符串在代码中会频繁用到,另一方面是因为字符串的操作非常麻烦. 最初我知道String的特殊待遇就是在delphi中,因为String在delphi里是一个关键字存在,与其他的基本类型是不一样的.那时

java中的StringBuffer和StringBuilder

/* java.lang.StringBuffer; java.lang.StringBuilder; 1.StringBuffer和StringBuilder是什么? 是一个字符串缓冲区. 2.工作原理 预先在内存中申请一块空间,以容纳字符序列, 如果预留的空间不够用,则进行自动扩容,以 容纳更多字符序列. 3.StringBuffer,StringBuilder  和  String最大的区别? String是不可变得字符序列,存储字符串常量池中. StringBuffer底层是一个char

6.Java中的字符串

1.String的特性 特性一:不可变性 String s=new String("yangyun") s=s.toUpperCase(); 这里的s,s占用的空间是不一样的(地址不相同),前提是toUpperCase函数确实改变了原始s的内容. 为什么String是不可变对象?-----cankai 1.效率问题 如果你知道一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址,复制地址(通常一个指针的大小)需要很小的内存效率也很高. 2.安全性 不可

2.1号Java复习题目——Java中的字符串(基础知识整理)

Java中的字符串基础知识 作为程序开发当中,使用最频繁的类型之一,字符串有着与基础类型相同的地位,甚至在 JVM(Java 虚拟机)编译的时候会对字符串做特殊的处理,比如拼加操作可能会被 JVM 直接合成为一个最终的字符串,从而到达高效运行的目的. 1 String 特性 String 是标准的不可变类(immutable),对它的任何改动,其实就是创建了一个新对象,再把引用指向该对象: String 对象赋值之后就会在常量池中缓存,如果下次创建会判定常量池是否已经有缓存对象,如果有的话直接返

Java中的字符串常量池

最近做到一个题目: 问题:String str = new String("abc"),"abc"在内存中是怎么分配的?    答案是:堆,字符串常量区. 题目考查的为Java中的字符串常量池和JVM运行时数据区的相关概念."abc"为字面量对象,其存储在堆内存中.而字符串常量池则存储的是字符串对象的一个引用. Java中的字符串常量池 Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid&qu

protobuf在java中的字符串化

最近由于项目需要,大致研究了一下protobuf的java使用.说实话,习惯了C++的protobuf,java用起来真别扭. 由于需要将protobuf序列化后,存入redis,而且redis没法直接存储非字符串的数据,所以我只能想办法将protobuf序列化成字符串. protobuf的java实现里,并没有直接序列化成String类型变量的方法,但是提供了toByteArray()方法,可以序列化成byte[]. 于是乎很容易想到可以这么做: byte[] raw_bytes = prot

java学习-关于字符串String

有必要总结记录一下java的学习,否则,永远只是记忆碎片化和always google(费时) 刚好,小伙伴给了一份自己做的review,在学习的过程中,update一下自己的见解和学习内容: 关于String: 1 package string_keywords; 2 /** 3 * 参考url: http://developer.51cto.com/art/201106/266454.htm 4 * 5 * 常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.cla