锁的类型
- 类锁:只有synchronized修饰静态方法或者修饰一个类的class对象时,才是类锁。
- 对象锁:除了类锁,所有其他的上锁方式都认为是对象锁。比如synchronized修饰普通方法或者synchronized(this)给代码块上锁等
我们先来看下面这段代码:
private static void count(String name, int i) {
x = i;
y = i;
if (x != y) {
System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " x=" + x + ",y="+ y);
}
}
阅读上面的代码,我们可能会觉得不会有什么输出值。是的如果上面的代码是在一个线程中运行,那么确实不会有输出值;那么我们在两个线程,或者更多线程中运行,会是什么结果呢?我们来验证一下:
//生成一个Runnable对象
private static Runnable getRunnable1(final String name, final int i) {
return new Runnable() {
@Override
public void run() {
count("Count", i);
}
};
}
//建立线程池,并生成两个线程
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < Integer.MAX_VALUE/10; i++) {
executorService.execute(getRunnable1(name, i));
executorService.execute(getRunnable1(name, i));
}
executorService.shutdown();
运行程序,并查看输出结果:
Count-->CurrThread:pool-4-thread-69 x=1350
Count-->CurrThread:pool-4-thread-69 y=1322
Count-->CurrThread:pool-4-thread-5 x=1427
Count-->CurrThread:pool-4-thread-5 y=1432
为什么会有结果输出呢?两个线程中都调用的是count
方法,里面就是实现了简单的赋值操作;一个线程正在进行赋值操作,这时另一个线程也调用方法进行赋值操作,这样就会造成这个问题,因为多线程中,共同调用一个方法进行赋值,并不是一个线程赋值完成后,另一个线程在进行调用。那么怎么解决这个问题呢?这就是我们这篇要讲的内容。
互斥锁,也就是:当多个线程共同访问共享数据时,需要保证同一时刻有且只有一个线程进行数据操作,其他线程等待该线程执行完后在执行。关键字Synchronized
就可满足。下面来看它的具体使用。
- 修饰非静态实例方法
建立一个线程实现类,里面调用Count
类中的方法
public class TempRunnable implements Runnable {
private Count count;
public TempRunnable(Count count) {
this.count = count;
}
@Override
public void run() {
count.count("TempRunnable_Count");
}
}
Count
类中的方法
public void count(String name) {
x++;
System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " x=" + x);
}
建立两个线程
private static void outPutString() {
ExecutorService executorService = Executors.newCachedThreadPool();
Count count = new Count();
for (int i = 0; i < 1000; i++) {
executorService.execute(new TempRunnable(count));
executorService.execute(new TempRunnable(count));
}
executorService.shutdown();
}
我们期望的结果是2000,来看下代码输出结果:
TempRunnable_Count-->CurrThread:pool-4-thread-276 x=1366
TempRunnable_Count-->CurrThread:pool-4-thread-59 x=1364
这个结果明显不是我们想要的,我们在来看synchronized
修饰的方法:
public synchronized void improve(String name) {
x++;
System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " x=" + x);
}
运行程序,结果为:
TempRunnable_improve(Improve)-->CurrThread:pool-4-thread-4 x=1999
TempRunnable_improve(Improve)-->CurrThread:pool-4-thread-60 x=2000
这个明显是我们需要的结果。在看下面例子:
首先,一个类中建立三个方法,两个synchronized
方法和一个普通方法,如下:
public class Count {
public synchronized void countImprove3(String name) {
System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
public synchronized void countImprove4(String name) {
System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
public void countImprove(String name) {
System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
}
其次:建立一个Runnable
实现类,如下:
public class MyRunnable implements Runnable {
private Count count;
public MyRunnable(Count count) {
this.count = count;
}
@Override
public void run() {
count.countImprove3("countImprove3:synchronized");
count.countImprove("normal");
count.countImprove4("countImprove4:synchronized");
}
}
最后,在代码中的使用,如下:
ExecutorService executorService = Executors.newCachedThreadPool();
Count count = new Count();
executorService.execute(new MyRunnable(count));
executorService.execute(new MyRunnable(count));
executorService.shutdown();
看输出结果:
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
normal(Improve)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
normal(Improve)-->CurrThread:pool-4-thread-1 end...
normal(Improve)-->CurrThread:pool-4-thread-2 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
normal(Improve)-->CurrThread:pool-4-thread-2 end...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
从输出结果我们可以看到:
- 在同一个实例对象中,调用
synchronized
方法的同时,不影响非synchronized
方法的调用; - 在同一个实例对象中,一个线程调用了其中的
synchronized
方法后,其他线程就不能再继续调用其中的其他synchronized
方法;只有当先调用的线程释放锁后其他线程才能调用。 - 给方法添加
synchronized
修饰,本质上就是给这个类的对象加锁,也就是对象锁。
那我们传入不同的实例对象,有是什么情况呢,看代码:
ExecutorService executorService = Executors.newCachedThreadPool();
Count count = new Count();
executorService.execute(new MyRunnable(new Count(new Count("Count Instance 1:"))));
executorService.execute(new MyRunnable(new Count(new Count("Count Instance 2:"))));
executorService.shutdown();
其输出结果为:
Count Instance 1:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove(Improve)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove(Improve)-->CurrThread:pool-4-thread-2 start...
Count Instance 2:countImprove(Improve)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove(Improve)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
从上面的输出结果可以看出,他们之间的调用互不影响。因为传入了各自的Count
对象,运行后他们进入各自的对象锁。这样就会造成最开始例子中值不一样的问题了。
如果我就想传入不同的对象,但是要实现的效果和传入同一对象一样,这该怎么实现呢?这就需要将synchronized
作用于静态方法中,看例子:
新建Count
类中有一个static
修饰的synchronized
方法,如下:
public class Count {
public static synchronized void add(String name) {
m++;
System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " m=" + m);
}
public synchronized void count(String name) {
y++;
System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " y=" + y);
}
}
先调用count
方法,运行,结果为:
Count Instance 1:-->CurrThread:pool-4-thread-53 y=98
Count Instance 1:-->CurrThread:pool-4-thread-46 y=97
调用add
方法,运行,结果为:
Count Instance 2:(Improve)-->CurrThread:pool-4-thread-35 m=199
Count Instance 1:(Improve)-->CurrThread:pool-4-thread-7 m=200
通过上面两个例子可以看出,传入不同的对象,调用synchronized
方法,并没有起到互斥锁的作用,因为他们进入了各自的对象锁,因此输出的值并不是依次叠加后的值;调用静态synchronized
方法,结果输出正确,所以静态的synchronized
方法相当于给整个类加锁,也就是类锁。线程调用其中的静态的synchronized
方法,只有一个线程执行完释放锁后其余线程才能进行调用。但是不影响调用其他非静态的方法。
新建类Count
中有下面几个方法:
public class Count {
public void countImprove2(String name) {
synchronized (this) {
for (int i = 0; i < 3; i++) {
System.out.println(name + "(countImprove2)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(countImprove2)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
}
}
public synchronized void countImprove4(String name) {
for (int i = 0; i < 3; i++) {
System.out.println(name + "(countImprove4)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(countImprove4)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
}
public void countImprove(String name) {
for (int i = 0; i < 3; i++) {
System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
}
}
该类中提供了一个synchronized
方法,一个方法中含synchronized
代码块和一个普通方法。接下来在建立两个线程类ThreadA
和ThreadB
,代码如下:
public class ThreadA implements Runnable {
private Count count;
public TempRunnable(Count count) {
this.count = count;
}
@Override
public void run() {
count.countImprove2(count.getName());
}
}
public class ThreadB implements Runnable {
private Count count;
public TempRunnable(Count count) {
this.count = count;
}
@Override
public void run() {
count.countImprove4(count.getName());
}
}
在代码中使用:
ExecutorService executorService = Executors.newCachedThreadPool();
Count count1 = new Count("Count Instance 1:");
executorService.execute(new TempRunnable(count1));
executorService.execute(new MyRunnable(count1));
executorService.shutdown();
传入了相同的Count
实例对象,运行结果为:
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
从结果看它是一个执行完成后,另一个才开始执行的。在代码中我们的countImprove2
方法是上的对象锁,而countImprove4
中synchronized
代码段传入的是this
,因此也就相当于上的对象锁;而调用时传入的也是同一对象,所以:当一个线程调用countImprove2
方法,在没有结束前其他的线程并不能调用countImprove4
方法,但是可以调用非synchronized
修饰过的方法。
更改上面的使用代码,如下:
ExecutorService executorService = Executors.newCachedThreadPool();
Count count1 = new Count("Count Instance 1:");
Count count2 = new Count("Count Instance 2:");
executorService.execute(new TempRunnable(count1));
executorService.execute(new MyRunnable(count2));
executorService.shutdown();
传入了两个不同的实例对象,运行结果为:
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
从结果看,他们是互不影响的。因为传入的是两个不同的,所以他们加的也是不同的对象锁,因此互不影响。
我们来在Count
类中加入下面两个方法:
......
public void countImprove5(String name) {
synchronized (Count.class) {
for (int i = 0; i < 2; i++) {
System.out.println(name + "(countImprove5)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(countImprove5)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
}
}
public static synchronized void countImprove3(String name) {
for (int i = 0; i < 2; i++) {
System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
}
}
......
在ThreadA
和ThreadB
中分别调用countImprove5
和countImprove3
方法,在传入相同的对象,运行结果为:
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
从结果看它是一个执行完成后,另一个才开始执行的。在代码中我们的countImprove5
方法中是synchronized
代码段,传入的是Count.class
因此上的是类锁;而countImprove3
为静态synchronized
方法,也就相当于上的类锁;而调用时传入的也是同一对象,所以:当一个线程调用countImprove5
方法,在没有结束前其他的线程并不能调用countImprove3
方法,但是可以调用非synchronized
修饰过的方法。
更改代码在ThreadB
中调用countImprove4
方法,传入相同的对象,运行结果为:
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
从结果看互不干扰,因为countImprove4
方法加的是对象锁,而countImprove5
加的是类锁,他们不是同一类锁,因此互不干扰。
更改代码,在ThreadA
和ThreadB
中分别调用countImprove2
和countImprove3
方法,在传入相同的对象,运行结果为:
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
从结果看也是互不干扰,因为countImprove2
方法加的是对象锁,而countImprove3
加的是类锁,他们不是同一类型的锁,因此互不干扰。
总的来说:
- 加了相同的锁,不管是对象锁(同一个类的实例对象)还是类锁(同一个类),只有先获取到锁的线程执行完成后其它线程才能继续执行。也就是说:为对象锁时,获取到锁的线程执行完方法后,下次执行的加锁的方法可能是另一个线程了;为类锁时,先获取到锁的线程将改线程内的加了同步锁的方法执行完成后,其它线程才能继续执行。
- 加了不同的锁,不同的线程之间调用是互不影响的。
- 不管加了什么锁,对于普通方法的调用不影响。
网友评论