java基础串联
一、String VS StringBuilder、StringBuffer的区别
String 声明的是不可变的对象
// string底层是由一个final的字符数组来储存数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
StringBuilder StringBuffer 底层都是由一个可变数组实现
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
}
1.0 验证字符串是不可变的
public static void main(String[] args) {
String str1 = "abc";
System.out.println("str1-2 = " + System.identityHashCode(str1));
String str2 = "abc";
System.out.println("str1-2 = " + System.identityHashCode(str2));
str1 = str1 + "c";
System.out.println("str1+\"c\"=" + System.identityHashCode(str1));
char[] str3 = new char[10];
System.out.println("char[] =" +System.identityHashCode(str3));
str3[0] = 'a';
str3[1] = 'b';
str3[2] = 'c';
str3[3] = 'd';
System.out.println("char[] =" + System.identityHashCode(str3));
}
// 输出结果
// str1-2的内存地址一致,说明"abc"相同的字符串在常量池中,所以栈指向堆的是同一个地址
str1-2 = 1935365522
str1-2 = 1935365522
// str1+"c" 内存地址发生了变化 说明字符串本身是不能改变的,只能从新创建一个新的字符串对象
str1+"c" =1483022288
// char[]的内存地址一致,说明对象未发生改变
char[] =1159785389
char[] =1159785389
1.1 StringBuilder VS StringBuffer的区别
相同点:
StringBuilder StringBuffer 继承同一个父类, abstract class AbstractStringBuilder
StringBuilder StringBuffer 底层都是由一个可变长的动态字符数组来储存数据
StringBuilder StringBuffer 拥有相同的API
不同点
StringBuffer 是线程安全的,可以在高并发的场景下使用。
StringBuilder 是线程不安全,在高并发的场景可能发生,
数据丢失(数据覆盖)
数组越界(由于添加元素触发数组扩容,导致数组越界)
// 线程不安全的代码如下:
@Override
// java.lang.AbstractStringBuilder#append(char)
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c; // count++ 是线程不安全的
return this;
}
// 数组越界
/**
* This method has the same contract as ensureCapacity, but is
* never synchronized.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
1.1.1 代码验证以上观点
public static void main(String[] args) throws InterruptedException {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
stringBuffer.append("a");
}
}
}).start();
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
stringBuilder.append("a");
}
}
}).start();
}
Thread.sleep(1000);
String stringBufferToStr = stringBuffer.toString();
log.info("stringBufferToStr is {}", stringBufferToStr.length());
String stringBuilderToStr = stringBuilder.toString();
log.info("stringBuilderToStr is {}", stringBuilderToStr.length());
}
// 输出日志:
[main] INFO thread.pstring.StringTest - stringBufferToStr is 200000
[main] INFO thread.pstring.StringTest - stringBuilderToStr is 199701
1.2 为什么StringBuffer是线程安全的
StringBuffer的全部方法都被关键字synchronized 修饰。
// StringBuffer 部分代码片段
@Override
public synchronized StringBuffer append(int i) {
toStringCache = null;
super.append(i);
return this;
}
/**
* @since 1.5
*/
@Override
public synchronized StringBuffer appendCodePoint(int codePoint) {
toStringCache = null;
super.appendCodePoint(codePoint);
return this;
}
@Override
public synchronized StringBuffer append(long lng) {
toStringCache = null;
super.append(lng);
return this;
}
1.2.1 验证StringBuffer是安全的
/**
* 创建两组线程,分别调用方法print print2(print print2 均加锁,synchronized)
* 希望:
* 同一个对象t1, 在t1调用print方法时(阻塞),调用print2方法的线程会等待获取锁
* 结果:
* 与希望现象一致
*/
public static void main(String[] args) throws InterruptedException {
StringTest t1 = new StringTest();
t1.name = "StringTest1";
StringTest t2 = new StringTest();
t2.name = "StringTest2";
for (int i = 0; i < 10; i++) {
String finalI = String.valueOf(i);
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
for (int j = 0; j < 100; j++) {
t1.print(t1.name);
}
}
}).start();
}
Thread.sleep(111);
for (int i = 0; i < 10; i++) {
String finalI = String.valueOf(i);
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
for (int j = 0; j < 100; j++) {
t1.print2(t1.name);
}
}
}).start();
}
}
public synchronized void print(String str) throws InterruptedException {
if("StringTest1".equalsIgnoreCase(str)){
log.info("print is StringTest1 阻塞中...........................");
Thread.sleep(5000);
log.info("print is StringTest1 阻塞中...........................end");
}else {
log.info("print is {}", str);
}
}
public synchronized void print2(String str) throws InterruptedException {
if("StringTest1".equalsIgnoreCase(str)){
log.info("xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end");
}else {
log.info("xxxxxxxxxxxxxxxxxxxxprint is {}", str);
}
}
// 输出日志(日志有部分删减)
[Thread-0] - print is StringTest1 阻塞中...........................
[Thread-0] - print is StringTest1 阻塞中...........................end
[Thread-0] - print is StringTest1 阻塞中...........................
[Thread-0] - print is StringTest1 阻塞中...........................end
[Thread-19] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
[Thread-19] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
[Thread-19] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
... 省略部分日志
[Thread-18] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
[Thread-10] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
[Thread-10] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
[Thread-9] - print is StringTest1 阻塞中...........................
[Thread-9] - print is StringTest1 阻塞中...........................end
[Thread-9] - print is StringTest1 阻塞中...........................
[Thread-9] - print is StringTest1 阻塞中...........................end
[Thread-8] - print is StringTest1 阻塞中...........................
1.2.2 验证StringBuilder是不安全的
1.3 关联点 synchronized 关键字的含义是什么?
每一个类对象都对应一把锁,当某个线程t1调用类对象O中的synchronized方法m1时,必须获得对象O的锁才能够执行M方法,否则线程t1阻塞。一旦线程t1开始执行m1方法,将独占对象O的锁。使得其它需要调用O对象的全部被synchronized修饰的方法阻塞。只有线程t1执行完毕,释放锁后。那些阻塞线程才有机会重新调用m1方法。这就是解决线程同步问题的锁机制。
持有锁的对象O在访问方法m1时,会与该对象的全部被synchronized修饰的方法阻塞。锁可以通过方法间传递(循环调用时避免使用死锁)
1.4 关联点 创建线程有哪几种方式?
1.继承Thread
2.实现Runable 无返回值
3.实现callable 有返回值,需要isDone来循环判断,消耗cpu
4.线程池
5.java8提供的stream.parallel
待完善
由String StringBuilder联想到redis的字符,可变长数组。
线程安全的集合 线程不安全的集合
synchronized的作用是什么?
常用的加锁方式有哪些?
ThreadLocalRandom使用了哪些技术点 cas lock等?
线程的声明周期