该文档不讨论同一同步块在不同线程的使用,而讨论其底层的锁机制,不同的同步块之间会有何种影响,这种影响不可避免的降低性能
1. 共享实例都会发生争用
2. 自有实例都不会发生争用,除以下情况:
2.1. 加锁对象为直接使用的字符串,如“abc”。因为字符串这样使用,实在常量池中创建,不管如何使用,都只创建一次
2.2. 加锁对象被static修饰。static修饰都归类所有,不属于实例。
2.2.1 static修饰方法。synchronized修饰方法,加锁对象为该实例,static修饰的方法不属于实例,属于类,synchronized修饰该方法时加锁对象则变成该类的monitor
2.2.2 加锁对象是对象,该对象被static修饰
1. synchronized关键字修饰方法时,加锁对象为实例或类
1.1自有实例(LockTest.java)无争用
public class LockTest {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest().printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest().printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
public synchronized void printString(){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void printDate(){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
结论:
打印结果可看出两线程同时在执行
JProfiler没有监测到Monitor&Lock信息,也没观察到线程阻塞
1.2 共享实例,LockTest2.java 总有一个被锁住,两个线程只能顺序执行
代码:
public class LockTest2 {
public static void main(String[] args) {
final LockTest2 instance = new LockTest2();
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
instance.printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
instance.printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
public synchronized void printString(){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void printDate(){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
结论:
打印结果可看出执行是有顺序的,两个线程互斥
JProfiler工具可以看到,有线程阻塞(红色部分)
两个线程(String thread, Date thread)在不同时间段都有阻塞
“Current Locking Graph”视图,一旦有线程锁住,主视图将有以下画面,两个线程正在争用同一个monitor,红色箭头表示线程阻塞,等待锁释放,黑色箭头表示获得锁,正在执行
1.3 静态方法,自有实例(LockTest3.java)发生争用现象
代码:
public class LockTest3 {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest3().printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest3().printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
public static synchronized void printString(){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static synchronized void printDate(){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class LockTest4 {
public static void main(String[] args) {
final LockTest4 instance = new LockTest4();
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
instance.printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
instance.printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
public static synchronized void printString(){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static synchronized void printDate(){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class LockTest5 {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
LockTest5.printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
LockTest5.printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
public static synchronized void printString(){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static synchronized void printDate(){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
结论:
结果是顺序打印,线程互斥,静态方法的共享实例则更确定会发生争用(LockTest4.java)
其实被static修饰的字段和方法都属类所有,通过类可以直接调用(LockTest5.java)。使用synchronized同步,其加锁对象是同一个,与实例无关,也不需通过实例调用。LockTest3,LockTest4通过实例调用反而多声称一份无用实例,同时误导读者
2. synchronized修饰对象
2.1加锁普通字段,自有实例发生争用
代码:
public class LockTest8 {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest8().printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest8().printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
private String lockStr=new String("StrLock");
public void printString(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void printDate(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class LockTest9 {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest9().printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest9().printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
private static String lockStr=new String("StrLock");
public void printString(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void printDate(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class LockTest10 {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest10().printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest10().printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
private Date lockDate=new Date();
public void printString(){
synchronized(lockDate){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void printDate(){
synchronized(lockDate){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class LockTest11 {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest11().printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest11().printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
private static Date lockDate=new Date();
public void printString(){
synchronized(lockDate){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void printDate(){
synchronized(lockDate){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
结论:
如“LockTest8.java”,一般普通字段都属于实例所有,每个实例都会创建一份属于自已的字段,对其加锁时,如果是自有实例则加锁对象不一样,不会争用;如果属于共享实例,则会发生争用。注意,这两个文件中都是用new String().不是直接使用字符串。一旦这类字段被static修饰,它就归类所有,不属于任何实例,无论创建多少实例,该字段都只创建一份,如(LockTest9.java),对这样的字段加锁,都会发生争用。LockTest10.java, LockTest11.java同样说明这一问题
2.2 加锁字符串
代码:
public class LockTest6 {
public static void main(String[] args) {
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest6().printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
new LockTest6().printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
private String lockStr="StrLock";
public void printString(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void printDate(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class LockTest7 {
public static void main(String[] args) {
final LockTest7 instance = new LockTest7();
new Thread("String thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
instance.printString();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("Date thread"){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
instance.printDate();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
private String lockStr="StrLock";
public void printString(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printString "+i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void printDate(){
synchronized(lockStr){
for (int i = 0; i < 3; i++) {
System.out.println("printDate "+new Date().toLocaleString());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
结论:
其创建方式也可以多样,最普遍的是直接使用。如我们使用“abc”不会去创建String str=new String("abc"),而是直接使用Stringstr="abc"。
上面提到了newString的同步,new出来的对象都在堆中创建,堆中对象不共享。
字符串直接使用会在常量池中创建,常量池的对象都是共享的,不管怎么使用,都只创建一次。这类对象的加锁,无论其本身是否被static修饰,都会产生争用(LockTest6.java,LockTest7.java)
newString("abc")其实创建了两个对象"abc"本身在常量池创建,然后作为参数给构造器又在堆中创建一次。