java中的synchronized 关键字,想必大部分人还是了解的,就是同步。主要目的是什么呢?:避免多线程共享资源的不安全。什么意思呢?因为如果存在多线程共享资源的时候,很容易出现不安全性。比如下面一段代码就是不安全的:
public class ThreadTest {
private int ticket = 3;
private void buyTicket() {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + ":买到,余票为" + ticket);
} else {
System.out.println(Thread.currentThread().getName() + ":没买到,余票为" + ticket);
}
}
public static void main(String[] args) {
final ThreadTest test = new ThreadTest();
Thread t1 = new Thread(new Runnable() {
public void run() {
for(int i=0;i<3;i++){
test.buyTicket();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
for(int i=0;i<3;i++){
test.buyTicket();
}
}
}, "t2");
t1.start();
t2.start();
}
}
执行的结果为:
t1:买到,余票为2
t2:买到,余票为1
t2:买到,余票为0
t2:没买到,余票为0
t1:买到,余票为-1
t1:没买到,余票为-1
例子很实用,就是我们现实生活中的买票,因为卖票点有非常多在同时操作,而票的余量是固定的。这里每个卖票的窗口就像是一个线程,票的余量就是共享的资源。很明显我们分析打印就知道很可笑:其中出现了4次买到,但是只有3张票,所以这是线程不安全的。那如何解決呢?我们可以在买票的方法中加上同步锁,代码如下:
private void buyTicket() {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + ":买到,余票为" + ticket);
} else {
System.out.println(Thread.currentThread().getName() + ":没买到,余票为" + ticket);
}
}
}
其他不变,我们看输出:
t1:买到,余票为2
t2:买到,余票为1
t1:买到,余票为0
t1:没买到,余票为0
t2:没买到,余票为0
t2:没买到,余票为0
这样程序就输出正常了。解决了不安全的问题。这就是synchronized 的作用以及简单用法。
今天我们要讲的当然不只是这些,因为我们常常在使用synchronized 有很多种用法,下面是一些常用的:
synchronized (this) { ... }
synchronized (object) { ... }
public synchronized void fuction(){
}
synchronized (object.class) { ... }
public static synchronized void fuction(){
}
其中注意的有几种用法:
1. 同一个对象调用了具有同步关键字的代码块
2. 不同的对象调用了具有同步关键子的代码块
3. 同一个对象同时调用了含有不同同步代买块的方法。
4. 不同对象调用了含有不同不同步方法的代码块
如下表:
类型 | 同一个对象中多次调用 | 不同的对象中多次调用 | 同一个对象与其他类型同步类型混用 | 不同的对象与其他类型同步类型混用 |
---|---|---|---|---|
synchronized (object) | 会阻塞 | 不会阻塞 | synchronized (this)不会,synchronized (object)如果object是同一个对象会阻塞,public synchronized void fuction()不会阻塞 | synchronized (this)不会,synchronized (object)如果object是同一个对象会阻塞,public synchronized void fuction()不会阻塞 |
synchronized (this) | 会阻塞 | 不会阻塞 | synchronized (this)会,synchronized (object)不会,public synchronized void fuction()会阻塞 | synchronized (this)不会,synchronized (object)不会,public synchronized void fuction()不会阻塞 |
public synchronized void fuction() | 会阻塞 | 不会阻塞 | synchronized (this)会,synchronized (object)不会,public synchronized void fuction()会阻塞 | synchronized (this)会,synchronized (object)不会,public synchronized void fuction()会阻塞 |
synchronized (object.class) | 会阻塞 | 会阻塞 | synchronized (object.class)不会,public static synchronized void fuction()会阻塞 | synchronized (object.class)会,public synchronized void fuction()会阻塞,public static synchronized void fuction()会阻塞 |
public static synchronized void fuction() | 会阻塞 | 会阻塞 | synchronized (object.class)会,public static synchronized void fuction()会阻塞 | synchronized (object.class)会,public static synchronized void fuction()会阻塞 |
前三种属于一类,后面两种属于一类,这两类混用不起任何效果。
这里就补一个一个测试了。表述一下总结把:
1.synchronized (object) { … } 这种作用最小,只有同步的对象是同一个的时候才会起作用,并且当他所在的类存在 synchronized (this) { … } 、public synchronized void fuction(){} 的时候是互不相关的
2.synchronized (object.class) { … } 这种作用和 public static synchronized void fuction(){} 作用类似,同步的是所有这个类的对象,针对不同实例调用次方法是都会被阻塞。static 方法就是一个,所以不管是哪个实例调用,最终都是同一个,所以肯定会阻塞的。
3.synchronized (this) { … } 同步的是当前的这个类的对象。值得注意的是,当他和同步方法一起使用的时候其中后执行的一个会被阻塞
好了到这里我们同步锁讲的差不多了,但是我们还要知道,加了同步锁的代码会造成阻塞,所以我们在使用的时候一定要注意场景,防止过多的实现消耗。