Java关键字synchronized

Java关键字synchronized

一、用途

synchronized可以实现线程同步,保证线程安全。被synchronized修饰的代码块或方法在同一时刻只允许一个线程进入临界区。

二、用法

  1. 修饰普通方法,即同步方法
  2. 修饰静态方法,即静态同步方法
  3. 修饰代码块,即同步代码块

三、例子

1、同步方法

代码中用synchronized修饰了method,启动线程t0和线程t1。我们的预期结果是某一线程进入临界区,另一个线程在等待。进入临界区的线程睡眠2s后退出临界区,另一个线程再进入临界区。

public class Sync {
	
	public synchronized void method() {
		System.out.println(Thread.currentThread().getName() + ": in");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out");
	}
	
	public static void main(String[] args) {
		final Sync s = new Sync();
		Thread t0 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s.method();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s.method();
			}
		});
		t0.start();
		t1.start();
	}
	
}

运行结果:

可以看到是预期的结果。


现在只有一个方法,下面我们测试一下多个方法。
public class Sync {
	
	public synchronized void method1() {
		System.out.println(Thread.currentThread().getName() + ": in method1");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method1");
	}
	
	public void method2() {
		System.out.println(Thread.currentThread().getName() + ": in method2");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method2");
	}
	
	public static void main(String[] args) {
		final Sync s = new Sync();
		Thread t0 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s.method1();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s.method2();
			}
		});
		t0.start();
		t1.start();
	}
	
}

现在有两个方法分别是method1method2,method1是由synchronized修饰的,method2则是一个普通方法。

来看一下运行结果:

似乎并不影响其他线程调用对象的非synchronized修饰的方法。


那么如果两个方法都是synchronized修饰的方法呢?
public class Sync {
	
	public synchronized void method1() {
		System.out.println(Thread.currentThread().getName() + ": in method1");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method1");
	}
	
	public synchronized void method2() {
		System.out.println(Thread.currentThread().getName() + ": in method2");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method2");
	}
	
	public static void main(String[] args) {
		final Sync s = new Sync();
		Thread t0 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s.method1();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s.method2();
			}
		});
		t0.start();
		t1.start();
	}
	
}

看到运行结果我们会发现同一个对象的两个被synchronized修饰的不同方法不能同时被调用。


再试试不同对象
public class Sync {
	
	public synchronized void method1() {
		System.out.println(Thread.currentThread().getName() + ": in method1");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method1");
	}
	
	public synchronized void method2() {
		System.out.println(Thread.currentThread().getName() + ": in method2");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method2");
	}
	
	public static void main(String[] args) {
		final Sync s0 = new Sync();
		final Sync s1 = new Sync();
		Thread t0 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s0.method1();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s1.method1();
			}
		});
		t0.start();
		t1.start();
	}
	
}

从运行结果可以看出调用不同对象的method1()方法是可以同时调用的。

那么不同对象的不同名字的方法依然是互不影响的。


那么是不是可以总结得到:synchronized修饰普通方法的时候是对对象进行加锁,即对象锁。

2、静态同步方法

把method1和methd2都改成静态方法,两个线程都去调用method1。

public class Sync {
	
	public static synchronized void method1() {
		System.out.println(Thread.currentThread().getName() + ": in method1");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method1");
	}
	
	public static synchronized void method2() {
		System.out.println(Thread.currentThread().getName() + ": in method2");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method2");
	}
	
	public static void main(String[] args) {
		Thread t0 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				Sync.method1();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				Sync.method1();
			}
		});
		t0.start();
		t1.start();
	}
	
}

两个线程调用method1不能同时进入临界区,只有等到一个退出临界区后另一个才能进入。


再试试调用不同静态方法。

public class Sync {
	
	public static synchronized void method1() {
		System.out.println(Thread.currentThread().getName() + ": in method1");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method1");
	}
	
	public static synchronized void method2() {
		System.out.println(Thread.currentThread().getName() + ": in method2");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": out method2");
	}
	
	public static void main(String[] args) {
		Thread t0 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				Sync.method1();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				Sync.method2();
			}
		});
		t0.start();
		t1.start();
	}
	
}

依然是同样的结果,两个进程不能同时进入临界区。


synchronized修饰静态方法是是对类进行加锁,即类锁。

3、同步代码块

同步代码块的都是这样的:

synchronized (...) {
	...
}

①对象锁

synchronized (this) {
	...
}

像这样的写法是不能写在静态方法里的,效果与同步方法类似。

②类锁

synchronized (XXX.class) {
	...
}

XXX指的是类名。

像这样的写法可以写在静态方法和普通方法中。①②两种写法是不能写成构造代码块的,但是能写在构造方法中。

③私有锁

Object lock
synchronized (lock) {
	...
}

私有锁的粒度更小,不会与对象锁和类锁竞争。


代码演示一下:

public class Sync {
 	
	public Integer a = new Integer(1);
	public void method() {
		System.out.println(Thread.currentThread().getName() + ": in method");
		synchronized (a) {
			System.out.println(Thread.currentThread().getName() + ": in synchronized block");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ": out synchronized block");
		}
		System.out.println(Thread.currentThread().getName() + ": out moetod");
	}
	
	public static void main(String[] args) {
		final Sync s = new Sync();
		
		Thread t0 = new Thread(new Runnable() {
			@Override
			public void run() {
				s.method();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				s.method();
			}
		});
		t0.start();
		t1.start();
	}
	
}

运行结果可以看到两个线程可以同时进入方法,但是不能同时进入synchronized (a){...}所包含的代码块。


synchronized (lock){...}中的lock是不同的对象时结果是不一样的。

举个例子:小明和小红两人可以同时走进房间,但是小明(或小红)进入房间后必须走出房间才能再次进入房间。

public class Sync {
	class Person {
		String name;
		Person(String name) {
			this.name = name;
		}
		void enterRoom() {
			System.out.println(Thread.currentThread().getName() + "-->" + name + ": 我准备好了!");
			synchronized (name) {
				System.out.println(Thread.currentThread().getName() + "-->" + name + ": 我进入房间了,并且我要在房间里待2秒!");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				exitRoom();
			}
			
		}
		void exitRoom() {
			System.out.println(Thread.currentThread().getName() + "-->" + name + ": 我退出了房间!");
		}
	}
	public static void main(String[] args) {
		final Person p1 = new Sync().new Person("小明");
		final Person p2 = new Sync().new Person("小明");
		Thread t0 = new Thread(new Runnable() {
			@Override
			public void run() {
				p1.enterRoom();
			}
		});
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				p2.enterRoom();
			}
		});
		t0.start();
		t1.start();
	}
	
}

姓名相同,即lock是同一个对象时,两个线程是不能同时进入的。


把p2改名叫小红

final Person p1 = new Sync().new Person("小明");
final Person p2 = new Sync().new Person("小红");

可以看到小明小红几乎同时进入房间,也就是这两个线程同步进行的。

posted @ 2019-08-26 16:42  lovexy-fun  阅读(165)  评论(0)    收藏  举报