synchronized关键字
1、概述
synchronized关键字是为了解决共享资源竞争的问题,共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入/输出端口,或者是打印机。要控制对共享资源的访问,得先把它包装进一个对象。然后把所有要访问的这个资源的方法标记为synchronized。如果某个任务处于一个对标记为synchronized的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任意synchronized方法的时候,对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
在Java中,每个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在的。当我们调用某个对象的synchronized方法时,就获得了该对象的同步锁,不同线程对同步锁的访问是互斥的。因为锁语句产生了一种互相排斥的效果,所以这种机制常常称为互斥量(mutex)。
2、synchronized基本原则和实例
2.1、基本原则
1.当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的该synchronized方法或者synchronized代码块的访问将被阻塞。
2. 当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程仍然可以访问该对象的非同步代码块。
3. 当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的其他的synchronized方法或者synchronized代码块的访问将被阻塞。
2.2、实例
第一条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的该synchronized方法或者synchronized代码块的访问将被阻塞。
//实例1
public class RunnableTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
class MyRunnable implements Runnable{
private int j=5;
@Override
public void run() {
synchronized(this){
for(int i=0;i<5;i++){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" loop "+i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Runnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable,"t1");
Thread t2 = new Thread(runnable,"t2");
t1.start();
t2.start();
}
}
运行结果:
t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t1 loop 5
t1 loop 6
t1 loop 7
t1 loop 8
t1 loop 9
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
t2 loop 5
t2 loop 6
t2 loop 7
t2 loop 8
t2 loop 9
结果说明:run()方法中存在synchronized(this)代码块,而且t1和t2都是基于MyRunnable这个Runnable对象创建的线程。这就意味着,我们可以将synchronized(this)中的this看做是MyRunnable这个Runnable对象;因此,线程t1和t2共享“MyRunable对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待正在运行的线程释放MyRunnable的同步锁之后才能运行。
下面一个极其相似的例子:
//实例2
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
synchronized(this){
for(int i=0;i<10;i++){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" loop "+i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Thread t1 = new MyThread("t1");
Thread t2 = new MyThread("t2");
t1.start();
t2.start();
}
}
运行结果
t2 loop 0
t1 loop 0
t2 loop 1
t1 loop 1
t1 loop 2
t2 loop 2
t2 loop 3
t1 loop 3
t1 loop 4
t2 loop 4
t2 loop 5
t1 loop 5
t1 loop 6
t2 loop 6
t1 loop 7
t2 loop 7
t2 loop 8
t1 loop 8
t2 loop 9
t1 loop 9
对比结果,你是否可以正确且快速的明白呢?上面的实例1是实现了Runnable接口,实例2继承了Thread类。在run()方法中都有synchronized(this),实例1的结果是先执行线程t1然后才是线程t2,实例2的结果是线程t1和t2交替执行。
分析:synchronized(this)中的this是指当前对象,即synchronized(this)所在类对应的当前对象。它的作用是获取获取当前对象的同步锁。对于实例2中的synchronized(this)中的this代表的是MyThread对象,t1和t2是两个不同的MyThread对象,因此t1和t2在执行synchronized(this)时获取的是不同对象的同步锁。对于实例1来说,synchronized(this)中的this代表的时候MyRunnable对象,t1和t2是共同一个MyRunnable对象,因此,一个线程获取了对象的同步锁,会造成另一个线程的等待。
第二条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程仍然可以访问该对象的非同步代码块。
public class SyncAndNoSync {
/**
* @param args
*/
public static void main(String[] args) {
class Count {
// 含有synchronized同步块的方法
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 非同步的方法
public void nonSynMethod() {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
final Count count = new Count();
// 新建t1, t1会调用“count对象”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
count.synMethod();
}
}, "t1");
// 新建t2, t2会调用“count对象”的nonSynMethod()方法
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
count.nonSynMethod();
}
}, "t2");
t1.start(); // 启动t1
t2.start(); // 启动t2
}
}
运行结果:
t2 nonSynMethod loop 0
t1 synMethod loop 0
t2 nonSynMethod loop 1
t1 synMethod loop 1
t2 nonSynMethod loop 2
t1 synMethod loop 2
t1 synMethod loop 3
t2 nonSynMethod loop 3
t2 nonSynMethod loop 4
t1 synMethod loop 4
结果说明:
线程t1和t2交替执行。t1会调用count对象的synMethod()方法,该方法中含有同步块;而t2则会调用count对象的nonSynMethod()方法,该方法不是同步方法。t1运行时,虽然调用synchronized(this)获取count对象的同步锁;但是并没有造成t2的阻塞,因为t2没有用到count对象的同步锁。
第三条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的其他的synchronized方法或者synchronized代码块的访问将被阻塞。
public class SyncAndSync {
/**
* @param args
*/
public static void main(String[] args) {
class Count {
// 含有synchronized同步块的方法
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 也包含synchronized同步块的方法
public void synMethod2() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " synMethod2 loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
final Count count = new Count();
// 新建t1, t1会调用“count对象”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
count.synMethod();
}
}, "t1");
// 新建t2, t2会调用“count对象”的synMethod2()方法
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
count.synMethod2();
}
}, "t2");
t1.start(); // 启动t1
t2.start(); // 启动t2
}
}
运行结果:
t1 synMethod loop 0
t1 synMethod loop 1
t1 synMethod loop 2
t1 synMethod loop 3
t1 synMethod loop 4
t2 synMethod2 loop 0
t2 synMethod2 loop 1
t2 synMethod2 loop 2
t2 synMethod2 loop 3
t2 synMethod2 loop 4
结果说明:
t1和t2运行时都调用synchronized(this),这个this是Count对象(count),而t1和t2共用count。因此,在t1运行时,t2会被阻塞,等待t1运行释放“count对象的同步锁”,t2才能运行。
3、synchronized方法和synchronized代码块
3.1、概述
synchronized方法是用synchronized修饰方法,这是一种粗粒度锁;这个同步方法(非static方法)无需显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
synchronized代码块是用synchronized修饰代码块,这是一种细粒度锁。线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,任何时候只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步监视器的锁定。虽然Java允许使用任何对象作为同步监视器,但同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。
3.2、实例
public class SnchronizedTest {
public static void main(String[] args) {
class Demo {
public synchronized void synMethod() {
for(int i=0; i<1000000; i++)
;
}
public void synBlock() {
synchronized( this ) {
for(int i=0; i<1000000; i++)
;
}
}
}
Demo demo = new Demo();
long start,diff;
start = System.currentTimeMillis();
demo.synMethod(); // 调用“synchronized方法块”
diff = System.currentTimeMillis() - start; // 获取“时间差值”
System.out.println("synMethod() : "+ diff);
start = System.currentTimeMillis();
demo.synBlock(); // 调用“synchronized方法块”
diff = System.currentTimeMillis() - start; // 获取“时间差值”
System.out.println("synBlock() : "+ diff);
}
}
运行结果:
synMethod() : 24
synBlock() : 9
结果说明:synchronized代码块可以更精确的控制冲突限制访问区域,有时候表现更高效率。
4、实例锁和全局锁
4.1、概念
- 实例锁:锁在某个实例对象上。如果该类是单例,那么该锁也是具有全局锁的概念。实例锁对应的就是synchronized关键字。
- 全局锁:该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
4.2、实例
关于实例锁和全局锁有个很好的例子。
pulbic class Something {
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
假设,类Something有两个实例(对象)分别为x和y。分析下面4组表达式获取锁的情况。
- x.isSyncA()与x.isSyncB()
- x.isSyncA()与y.isSyncA()
- x.cSyncA()与y.cSyncB()
- x.isSyncA()与Something.cSyncA()
4.2.1、x.isSyncA()与x.isSyncB()不能同时访问
这是因为isSyncA()和isSyncB()都是访问的同一个对象(对象x)的同步锁
public class LockTest {
static class Something{
public synchronized void isSyncA(){
for(int i=0;i<5;i++){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" : isSyncA");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void isSyncB(){
for(int i=0;i<5;i++){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" : isSyncB");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static synchronized void cSyncA(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName()+" : cSyncA");
}
}catch (InterruptedException ie) {
}
}
public static synchronized void cSyncB(){
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName()+" : cSyncB");
}
}catch (InterruptedException ie) {
}
}
}
Something x = new Something();
Something y = new Something();
private void test1(){
Thread t11 = new Thread(new Runnable(){
@Override
public void run() {
x.isSyncA();
//x.cSyncA();
}
},"t11");
Thread t12 = new Thread(new Runnable(){
@Override
public void run() {
x.isSyncB();
//y.isSyncA();
//y.cSyncB();
//Something.cSyncA();
}
},"t12");
t11.start();
t12.start();
}
public static void main(String[] args) {
LockTest lockTest1 = new LockTest();
lockTest1.test1();
}
}
运行结果:
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
4.2.2、x.isSyncA()与y.isSyncB()能同时访问
因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。
代码只需要在4…2.1上的代码将x.isSyncA() 和y.isSyncA()的注释去掉,其他的语句加上注释。
运行结果:
t11 : isSyncA
t12 : isSyncA
t11 : isSyncA
t12 : isSyncA
t12 : isSyncA
t11 : isSyncA
t12 : isSyncA
t11 : isSyncA
t12 : isSyncA
t11 : isSyncA
4.2.3、x.cSyncA()与y.cSyncB() 不能同时被访问
因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。
代码只需要在4.2.1上的代码将 x.cSyncA()与y.cSyncB() 的注释去掉,其他的语句加上注释。
运行结果:
t12 : cSyncB
t12 : cSyncB
t12 : cSyncB
t12 : cSyncB
t12 : cSyncB
t11 : cSyncA
t11 : cSyncA
t11 : cSyncA
t11 : cSyncA
t11 : cSyncA
4.2.4、x.isSyncA()与Something.cSyncA() 可以被同时访问
因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。
代码只需要在4.2.1上的代码将 x.isSyncA()与Something.cSyncA() 的注释去掉,其他的语句加上注释。
运行结果:
t12 : cSyncA
t11 : isSyncA
t11 : isSyncA
t12 : cSyncA
t11 : isSyncA
t12 : cSyncA
t11 : isSyncA
t12 : cSyncA
t11 : isSyncA
t12 : cSyncA