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修飾的變量為常量,不能被改變。