1.修饰实例方法,为当前实例加锁,进入同步方法前要获得当前实例的锁。 2.修饰静态方法,为当前类对象加锁,进入同步方法前要获得当前类对象的锁。 3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。
这三种使用方式大家应该都很熟悉,有一个要注意的地方是对静态方法的修饰可以和实例方法的修饰同时使用,不会阻塞,因为一个是修饰的Class类,一个是修饰的实例对象。下面的例子可以说明这一点:
public class SynchronizedTest {
public static synchronized void StaticSyncTest() {
for (int i = 0; i < 3; i++) {
System.out.println(&#34;StaticSyncTest&#34;);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void NonStaticSyncTest() {
for (int i = 0; i < 3; i++) {
System.out.println(&#34;NonStaticSyncTest&#34;);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {SynchronizedTest synchronizedTest = new SynchronizedTest();new Thread(new Runnable() {
@Override
public void run() {
SynchronizedTest.StaticSyncTest();
}
}).start();new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest.NonStaticSyncTest();
}
}).start();
}
//waitthreadwait-before
//notifythreadnotify-before
//notifythreadafter-notify
//waitthreadafter-wait代码中notify线程通知之后wait线程并没有马上启动,还需要notity线程执行完同步代码块释放锁之后wait线程才开始执行。
CAS
在synchronized的优化过程中我们看到大量使用了CAS操作,CAS全称Compare And Set(或Compare And Swap),CAS包含三个操作数:内存位置(V)、原值(A)、新值(B)。简单来说CAS操作就是一个虚拟机实现的原子操作,这个原子操作的功能就是将旧值(A)替换为新值(B),如果旧值(A)未被改变,则替换成功,如果旧值(A)已经被改变则替换失败。
可以通过AtomicInteger类的自增代码来说明这个问题,当不使用同步时下面这段代码很多时候不能得到预期值10000,因为noncasi0++不是原子操作。
private static void IntegerTest() throws InterruptedException {final Integer[] noncasi = new Integer[]{ 0 };for (int i = 0; i < 10; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 1000; j++) {noncasi[0]++;}}});thread.start();}while (Thread.activeCount() > 2) {Thread.sleep(10);}System.out.println(noncasi[0]);
}
//7889当使用AtomicInteger的getAndIncrement方法来实现自增之后相当于将casi.getAndIncrement()操作变成了原子操作:
private static void AtomicIntegerTest() throws InterruptedException {AtomicInteger casi = new AtomicInteger();casi.set(0);for (int i = 0; i < 10; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 1000; j++) {casi.getAndIncrement();}}});thread.start();}while (Thread.activeCount() > 2) {Thread.sleep(10);}System.out.println(casi.get());
}
//10000当然也可以通过synchronized关键字来达到目的,但CAS操作不需要加锁解锁以及切换线程状态,效率更高。
再来看看casi.getAndIncrement()具体做了什么,在JDK1.8之前getAndIncrement是这样实现的(类似incrementAndGet):
private volatile int value;
public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}
}通过compareAndSet将变量自增,如果自增成功则完成操作,如果自增不成功,则自旋进行下一次自增,由于value变量是volatile修饰的,通过volatile的可见性,每次get()都能获取到最新值,这样就保证了自增操作每次自旋一定次数之后一定会成功。
JDK1.8中则直接将getAndAddInt方法直接封装成了原子性的操作,更加方便使用。
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}CAS操作是实现Java并发包的基石,他理解起来比较简单但同时也非常重要。Java并发包就是在CAS操作和volatile基础上建立的,下图中列举了J.U.C包中的部分类支撑图: