当用synchronized来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。即使在执行过程中,CPU切换到别的线程了,因为有锁的缘故,其他线程也不会进来执行代码,而CPU又切换到原来的线程,接着执行后面的代码。
用例1:synchronized同步代码块
class RunnableImpl implements Runnable {
public void run() {
synchronized (this) {
for (int i = 0; i < 2; i++) {
System.out.print("i=" + i + "," + Thread.currentThread().getName() + "线程执行业务代码;");
}
}
}
}
public class Test1 {
public static void main(String[] args) {
RunnableImpl target = new RunnableImpl();
Thread ta = new Thread(target, "A");
Thread tb = new Thread(target, "B");
ta.start();
tb.start();
}
}
上面创建了两个线程,因为创建线程时传的参数是同一个Runnable对象,故这两个线程操作的是同一个Runnable对象的run方法。run方法体内是一个同步代码块,假如线程A先获得锁,则打印结果是:
i=0,A线程执行业务代码;i=1,A线程执行业务代码;i=0,B线程执行业务代码;i=1,B线程执行业务代码;
如果线程B先获得锁,则打印结果是:
i=0,B线程执行业务代码;i=1,B线程执行业务代码;i=0,A线程执行业务代码;i=1,A线程执行业务代码
因为不管是A线程先获得锁,还是B线程先获得锁,在获得锁后就直接进入了循环,没有循环完期间,即使CPU切换到了另一个线程,但是由于获得锁的线程还未释放锁,所以另一个线程也进不去,不能执行任何操作。待循环完后,释放了锁,另一个线程才能获得锁,并执行循环操作。
总结来说,当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
用例2:synchronized同步代码块
对上例稍微变化一下:
class RunnableImpl implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
synchronized (this) {
System.out.print("i=" + i + "," + Thread.currentThread().getName() + "线程执行业务代码");
}
}
}
}
public class Test1 {
public static void main(String[] args) {
RunnableImpl target = new RunnableImpl();
Thread ta = new Thread(target, "A");
Thread tb = new Thread(target, "B");
ta.start();
tb.start();
}
}
上面代码打印结果是变化的,其中一个有代表性的是:
i=0,A线程执行业务代码;i=0,B线程执行业务代码;i=1,B线程执行业务代码;i=2,B线程执行业务代码;i=3,B线程执行业务代码;i=4,B线程执行业务代码;i=1,A线程执行业务代码;i=2,A线程执行业务代码;i=3,A线程执行业务代码;i=4,A线程执行业务代码;
解析:线程A先被CPU调用,获得锁,执行完打印后释放锁,这时候线程B被CPU调用,获得锁,执行完打印后释放锁。之后重复此步骤,直到线程B执行完后,线程A被CPU调用,获得锁,执行完打印后释放锁。之后重复此步骤。
用例3:synchronized同步代码块
对用例1再稍微变化一下:
class RunnableImpl implements Runnable {
public void run() {
for (int i = 0; i < 2; i++) {
System.out.print("i=" + i + "," + Thread.currentThread().getName() + "线程;");
}
synchronized (this) {
for (int i = 0; i < 2; i++) {
System.out.print("i=" + i + "," + Thread.currentThread().getName() + "线程执行业务代码;");
}
}
}
}
public class Test1 {
public static void main(String[] args) {
RunnableImpl target = new RunnableImpl();
Thread ta = new Thread(target, "A");
Thread tb = new Thread(target, "B");
ta.start();
tb.start();
}
}
上例打印结果也有很多种,挑出一条有代表性的:
i=0,A线程;i=1,A线程;i=0,A线程执行业务代码;i=0,B线程;i=1,A线程执行业务代码;i=1,B线程;i=0,B线程执行业务代码;i=1,B线程执行业务代码;
看第二条i=0,打印出i=0,A线程执行业务代码;说明线程A现在拿到了锁,且刚执行完第一次循环。按道理讲,这个时候即使CPU切换到线程B,线程B也会因为线程A没有释放锁而不执行,线程A接着执行打印i=0,A线程执行业务代码;,但是为什么现在接着打印出了i=0,B线程;呢?这是因为run方法中不只有synchronized同步代码块,还有其他的非synchronized的代码,线程B进不去synchronized同步代码块,但是可以执行非synchronized的代码块。
总结来说就是:当一个线程访问某Runnable对象的一个synchronized(this)同步代码块时,其他线程仍然可以访问该对象中的非synchronized(this)的代码块。
还有,如果run方法中有多个synchronized同步代码块,当一个线程访问其中一个synchronized(this)同步代码块时,其他线程对所有其它synchronized(this)同步代码块的访问将被阻塞,因为如果这些同步代码块都是用synchronized(this)或者说都是用同一个同步代码块标识包起来的话,则这些synchronized同步代码块是同一个锁,只要一个线程拿到锁了没释放,其他线程就拿不到锁了,就得阻塞。