4.线程安全与非安全
StringBuffer是线程安全的,而StringBuilder是非线程安全的,至于原因我们依然可以从它们的源码中找到。
StringBuffer类的部分源码
1 public synchronized int length() { 2 return count; 3 } 4 5 @Override 6 public synchronized void ensureCapacity(int minimumCapacity) { 7 super.ensureCapacity(minimumCapacity); 8 } 9 10 @Override 11 public synchronized void trimToSize() { 12 super.trimToSize(); 13 } 14 15 @Override 16 public synchronized void setLength(int newLength) { 17 toStringCache = null; 18 super.setLength(newLength); 19 } 20 21 @Override 22 public synchronized char charAt(int index) { 23 if ((index < 0) || (index >= count)) 24 throw new StringIndexOutOfBoundsException(index); 25 return value[index]; 26 } 27 28 @Override 29 public synchronized int codePointAt(int index) { 30 return super.codePointAt(index); 31 } 32 33 @Override 34 public synchronized int codePointBefore(int index) { 35 return super.codePointBefore(index); 36 } 37 38 @Override 39 public synchronized int offsetByCodePoints(int index, int codePointOffset) { 40 return super.offsetByCodePoints(index, codePointOffset); 41 } 42 43 @Override 44 public synchronized void getChars(int srcBegin, int srcEnd, char[] dst, 45 int dstBegin) 46 { 47 super.getChars(srcBegin, srcEnd, dst, dstBegin); 48 } 49 50 @Override 51 public synchronized void setCharAt(int index, char ch) { 52 if ((index < 0) || (index >= count)) 53 throw new StringIndexOutOfBoundsException(index); 54 toStringCache = null; 55 value[index] = ch; 56 } 57 58 @Override 59 public synchronized StringBuffer append(Object obj) { 60 toStringCache = null; 61 super.append(String.valueOf(obj)); 62 return this; 63 } 64 65 @Override 66 public synchronized StringBuffer append(String str) { 67 toStringCache = null; 68 super.append(str); 69 return this; 70 }
StringBuilder类的部分源码
1 @Override 2 public StringBuilder append(int i) { 3 super.append(i); 4 return this; 5 } 6 7 @Override 8 public StringBuilder append(long lng) { 9 super.append(lng); 10 return this; 11 } 12 13 @Override 14 public StringBuilder append(float f) { 15 super.append(f); 16 return this; 17 } 18 19 @Override 20 public StringBuilder append(double d) { 21 super.append(d); 22 return this; 23 } 24 @Override 25 public StringBuilder insert(int index, char[] str, int offset, 26 int len) 27 { 28 super.insert(index, str, offset, len); 29 return this; 30 } 31 32 @Override 33 public StringBuilder insert(int offset, Object obj) { 34 super.insert(offset, obj); 35 return this; 36 }
我们可以发现StringBuffer类中的大部分成员方法都被synchronized关键字修饰,而StringBuilder类没有出现synchronized关键字;至于StringBuffer类中那些没有用synchronized修饰的成员方法,如insert()、indexOf()等,通过源码上的注释可以知道,它们是调用StringBuffer类的其他方法来实现同步的。注意:toString()方法也是被synchronized关键字修饰的。
至于synchronized关键字的使用范围及其作用,这里做了一下较为全面的总结:
一、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 当两个并发线程访问同一个对象中的synchronized(this){}同步代码块时,同一时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块;
1 package code; 2 3 public class Thread0 implements Runnable{ 4 @Override 5 public void run() { 6 synchronized (this) { 7 for (int i = 0; i < 5; i++) { 8 System.out.println(Thread.currentThread().getName()+" "+i); 9 } 10 } 11 } 12 public static void main(String[] args) { 13 Thread0 target = new Thread0(); 14 Thread thA = new Thread(target,"Thread A"); 15 Thread thB = new Thread(target,"Thread B"); 16 thA.start(); 17 thB.start(); 18 } 19 20 }
- 当一个线程访问对象中的一个synchronized(this){}同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this){}同步代码块;
1 package code; 2 3 public class Thread0 implements Runnable{ 4 @Override 5 public void run() { 6 synchronized (this) { 7 for (int i = 0; i < 5; i++) { 8 System.out.println(Thread.currentThread().getName()+" "+i); 9 } 10 } 11 12 for (int j = 0; j < 5; j++) { 13 System.out.println(Thread.currentThread().getName()+" "+j); 14 } 15 16 } 17 public static void main(String[] args) { 18 Thread0 target = new Thread0(); 19 Thread thA = new Thread(target,"Thread A"); 20 Thread thB = new Thread(target,"Thread B"); 21 thA.start(); 22 thB.start(); 23 } 24 }
- 当一个线程访问对象中的一个synchronized(this){}同步代码块时,其他线程对对象中所有其它synchronized(this){}同步代码块的访问将被阻塞。
1 package code; 2 3 public class Thread0 { 4 public void fun0() { 5 synchronized (this){ 6 for (int i = 0; i < 5; i++) { 7 System.out.println(Thread.currentThread().getName()+" "+i); 8 } 9 } 10 } 11 12 public void fun1() { 13 synchronized (this){ 14 for (int j = 0; j < 5; j++) { 15 System.out.println(Thread.currentThread().getName()+" "+j); 16 } 17 } 18 } 19 20 public static void main(String[] args) { 21 Thread0 target = new Thread0(); 22 Thread thA = new Thread(new Runnable() { 23 public void run() { 24 target.fun0(); 25 } 26 }, "Thread A"); 27 Thread thB = new Thread(new Runnable() { 28 public void run() { 29 target.fun1(); 30 } 31 },"Thread B"); 32 thA.start(); 33 thB.start(); 34 } 35 }
二、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- synchronized修饰方法时,其作用和修饰代码块类似,此处不再赘述
1 package code; 2 3 public class Thread0 { 4 public synchronized void fun0() { 5 for (int i = 0; i < 5; i++) { 6 System.out.println(Thread.currentThread().getName()+" "+i); 7 } 8 } 9 10 public synchronized void fun1() { 11 for (int j = 0; j < 5; j++) { 12 System.out.println(Thread.currentThread().getName()+" "+j); 13 } 14 } 15 16 public static void main(String[] args) { 17 Thread0 target = new Thread0(); 18 Thread thA = new Thread(new Runnable() { 19 public void run() { 20 target.fun0(); 21 } 22 }, "Thread A"); 23 Thread thB = new Thread(new Runnable() { 24 public void run() { 25 target.fun1(); 26 } 27 },"Thread B"); 28 thA.start(); 29 thB.start(); 30 } 31 }
三、修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 因为静态方法(或变量)是属于其所属类的,而不是属于该类的对象的,所以synchronized关键字修饰的静态方法锁定的是这个类的所有对象,即所有对象都是同一把锁。
1 package code; 2 3 public class MyThread { 4 public synchronized static void function() { 5 for (int i = 0; i < 5; i++) { 6 System.out.println(Thread.currentThread().getName()+" "+i); 7 } 8 } 9 10 public static void main(String[] args) { 11 MyThread target0 = new MyThread(); 12 MyThread target1 = new MyThread(); 13 14 Thread thA = new Thread(new Runnable() { 15 public void run() { 16 target0.function(); 17 } 18 }, "Thread A"); 19 20 Thread thB = new Thread(new Runnable() { 21 public void run() { 22 target1.function(); 23 } 24 }, "Thread B"); 25 thA.start(); 26 thB.start(); 27 } 28 }
四、修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
- synchronized关键字还可以用来修饰类,其作用与修饰静态方法类似,即所有类用的是同一把锁,用法如下:
1 package code; 2 3 public class MyThread { 4 5 public static void function() { 6 synchronized(MyThread.class){ 7 for (int i = 0; i < 5; i++) { 8 System.out.println(Thread.currentThread().getName()+" "+i); 9 } 10 } 11 } 12 13 public static void main(String[] args) { 14 MyThread target0 = new MyThread(); 15 MyThread target1 = new MyThread(); 16 17 Thread thA = new Thread(new Runnable() { 18 public void run() { 19 target0.function(); 20 } 21 }, "Thread A"); 22 23 Thread thB = new Thread(new Runnable() { 24 public void run() { 25 target1.function(); 26 } 27 }, "Thread B"); 28 thA.start(); 29 thB.start(); 30 } 31 }
这里再来解释一下3.2节中留下的问题,在运行速度方面StringBuffer<StringBuilder,这是因为StringBuffer由于线程安全的特性,常常应用于多线程的程序中,为了保证多线程同步一些线程就会遇到阻塞的情况,这就使得StringBuffer的运行时间增加,从而使得运行速度减慢;而StringBuilder通常不会出现多线程的情况,所以运行时就不会被阻塞,运行速度也自然就比StringBuffer快了。
5.final关键字修饰
从第1节中展示的源码,我们可以很快得到结论:String, StringBuffer, StringBuilder都能够用final关键字修饰,此处不再过多解释。
但需要注意的是:
- final修饰的类不能被继承;
- final修饰的方法不能被继承类重写;
- final修饰的变量为常量,不能被改变。