一. synchronized 同步代码关键字
此关键字为java中非常重要的关键字类型,当用它来修饰一个方法或者代码块的时候,能够保证所修饰的方法或者代码块在同一时刻最多只有一个线程在执行该段代码。
1) 当两个或者多个并发线程同时访问一个object中的synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行,其他线程必须要等到当前线程执行完这个代码块之后才能继续执行该代码块。
2) 然而,当一个线程访问object中的synchronized(this)同步代码块时,其他线程仍然可以调用object中的其它非synchronized(this)同步代码块。
3) 尤其关键的是,当一个线程访问object中的一个synchronized(this)同步代码块时,其他线程对于object中的其它synchronized(this)同步代码块的访问,都将被阻塞。
4) 第3条中的说明同样适用于其它同步代码块,也就是说,当一个线程访问object中的一个synchronized(this)同步代码块时,它就获得了整个object的对象锁,因此,其他所有线程对于object的所有同步代码块的访问都将被暂时阻塞,直至当前线程执行完这个代码块。
5) 上述的规则对于其他对象锁也同样适用。
对于synchronized关键字的个人理解
synchronized关键字,包括两种用法,一种是synchronized方法,另一种是synchronized块。
1. synchronized方法###
如果我们想要声明一个synchronized方法,只需要在声明方法的时候,加入synchronized关键字即可,如:
public synchronized void method2()
每一个类实例都对应一把锁,每个synchronized方法的执行都必须要获得调用该方法的类的实例的锁,才能执行,否则,线程就会阻塞。而方法一旦开始执行,就会独占该锁,直到方法执行完成返回后释放该锁,然后阻塞的线程就获取锁,然后调用方法执行。
这种机制确保了同一时刻对于每一个类的实例来讲,只有一个synchronized方法处于正在执行状态,因为同一时间最多只能有一个线程获取到类的实例的锁。这能确保类成员变量的访问冲突问题,前提是我们将所有可能冲突的方法声明为synchronized方法。
在java中,不仅仅是类的实例,类也有锁,我们可以将类的静态变量声明上添加synchronized关键字,这样就能控制线程对于类静态成员变量的访问。
缺点:将方法声明为synchronized会影响效率。例如,如果我们想线程类的run()方法定义为synchronized的,则在线程的整个生命周期内,它都会一直运行,且它无法调用本类任何synchronized方法,因为一直占用锁。所以我们可以将访问成员变量部分的代码定义为单独的方法,然后将这个方法声明为synchronized即可。
synchronized块###
我们同样可以通过使用synchronized关键字声明一个synchronized块,代码如下
synchronized (inner) {
//需要进行访问控制的代码
}
上述代码中的inner可以为类,也可以为类的实例,具体实现可以如下所示,此种方式同synchronized进行比较的话,更为灵活,我们可以根据实际的需要对于想要添加锁的代码或类加锁。
1.示例演示第1条规则,同一时间只能有一个线程在执行同步代码块
public class ThreadTest implements Runnable {
public void run() {
//同步代码块,该代码块在同一时间只能被一个线程调用,其他想要执行同步代码块的线程必须要等待当前线程执行完代码块之后才能继续执行同步代码块的内容
synchronized (this) {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized index " + i);
}
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread ta = new Thread(threadTest,"Thread ta");
Thread tb = new Thread(threadTest,"Thread tb");
ta.start();
tb.start();
}
}
执行结果为
Thread ta synchronized index 0
Thread ta synchronized index 1
Thread ta synchronized index 2
Thread tb synchronized index 0
Thread tb synchronized index 1
Thread tb synchronized index 2
2.示例演示第2条规则,同一时间其他线程可以调用对象的其他非同步代码块
public class ThreadTest {
public void synchron() {
synchronized (this) {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
public void noSynchron() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synchron();
}
}, "ThreadTA");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
threadTest.noSynchron();
}
}, "ThreadTB");
ta.start();
tb.start();
}
}
执行结果为
ThreadTA:3
ThreadTB:3
ThreadTA:2
ThreadTB:2
ThreadTA:1
ThreadTB:1
ThreadTB:0
ThreadTA:0
3.示例演示第3条规则,我们修改上面代码的noSynchron方法为同步代码块
代码如下
public void noSynchron() {
synchronized (this) {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
执行结果为
ThreadTA:3
ThreadTA:2
ThreadTA:1
ThreadTA:0
ThreadTB:3
ThreadTB:2
ThreadTB:1
ThreadTB:0
4.示例演示第4条规则,我们修改上面代码的noSynchron方法为如下代码
public synchronized void noSynchron() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
执行结果与上面相同。
5.示例演示第5条规则,以上规则对于其他对象锁同样适用
public class ThreadTest {
class innerClass {
private void method1() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " innerClass.method1 " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
private void method2() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " innerClass.method2 " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
public void getInnerClass1(innerClass inner) {
synchronized (inner) {
inner.method1();
}
}
public void getInnerClass2(innerClass inner) {
inner.method2();
}
public static void main(String[] args) {
final ThreadTest threadTest = new ThreadTest();
final innerClass inner = threadTest.new innerClass();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
threadTest.getInnerClass1(inner);
}
}, "ThreadTA");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
threadTest.getInnerClass2(inner);
}
}, "ThreadTB");
ta.start();
tb.start();
}
}
执行结果
ThreadTA innerClass.method1 3
ThreadTB innerClass.method2 3
ThreadTA innerClass.method1 2
ThreadTB innerClass.method2 2
ThreadTA innerClass.method1 1
ThreadTB innerClass.method2 1
ThreadTA innerClass.method1 0
ThreadTB innerClass.method2 0
那么,如果我们在method2方法上增加同步方法锁,则方法变为
private synchronized void method2() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " innerClass.method2 " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
执行结果变为
ThreadTA innerClass.method1 3
ThreadTA innerClass.method1 2
ThreadTA innerClass.method1 1
ThreadTA innerClass.method1 0
ThreadTB innerClass.method2 3
ThreadTB innerClass.method2 2
ThreadTB innerClass.method2 1
ThreadTB innerClass.method2 0
至此可以看出,尽管两个线程访问了内部类中不同的两个方法,但是由于都是使用了同步代码块synchronized关键字进行了标识,因此在调用中,由于ThreadTA先获得了innerClass类的对象锁,导致在ThreadTA执行完代码块之前,ThreadTB被阻塞,直到ThreadTA执行完代码块后,ThreadTB才继续执行。