Synchronized同步关键字的作用:
- 能够保证在
同一时刻
最多只有一个线程
执行该段代码,以达到保证并发安全
的效果;其是最基本的互斥同步手段
。
-
对象锁
:包括方法锁
(默认锁对象为this当前实例对象)和同步代码块锁
(自己指定锁对象)。
-
类锁
:指synchronized修饰静态
的方法或指定锁为Class对象
。Java类可能有很多个对象,但只有1个Class对象
。其用法有如下2种形式:
- synchronized 修饰 static 方法上
- synchronized 修饰(*.class)代码块
- 本质:类锁即
Class对象的锁
。效果:类锁只能在同一时刻被一个对象
拥有。
多线程访问同步方法的7种情况(面试常考)
- 两个线程同时访问
一个对象
的同步方法,则一个线程在执行时将某个对象锁定,且其他线程处于等待
状态
package com.zzw;
public class SynchronizedObjectMethod implements Runnable {
static SynchronizedObjectMethod synchronizedObjectMethod = new SynchronizedObjectMethod();
@Override
public void run() {
method();
}
//普通方法加锁(加了同步修饰符,那么线程是串行执行的)锁对象默认是this,注意不能是静态方法,是普通方法!
public synchronized void method() {
System.out.println("我是对象锁的方法修饰符形式,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public static void main(String[] args) {
//分配同一个Thread对象synchronizedObjectMethod,表示2个线程运行时只能占用一个资源对象synchronizedObjectMethod
Thread t1 = new Thread(synchronizedObjectMethod);
Thread t2 = new Thread(synchronizedObjectMethod);
t1.start();
t2.start();
//直到2个线程死亡
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}

2个线程同时访问一个对象的同步结果
- 两个线程访问的是
两个对象
的同步方法,则2个线程并发执行,结果互不干扰。
package com.zzw;
public class SynchronizedObjectCodeBlocks1 implements Runnable {
static SynchronizedObjectCodeBlocks1 instance1 = new SynchronizedObjectCodeBlocks1();
static SynchronizedObjectCodeBlocks1 instance2 = new SynchronizedObjectCodeBlocks1();
@Override
public void run() {
//用当前对象this作为锁,此代码块使得各个线程串行执行
synchronized (this) {
//不同的线程有不同的名字
System.out.println("我是对象锁的代码块形式。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
}
public static void main(String[] args) {
//使用不同的Runnable实例
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
//死循环,直到这2个线程执行完毕
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}

2个线程同时访问两个对象的同步结果
- 两个线程访问的是synchronized的
静态
方法,这种属于类锁
,同一时刻一个Class对象只能被一个线程占用。
package com.zzw;
public class SynchronizedClassStatic implements Runnable{
static SynchronizedClassStatic instance1 = new SynchronizedClassStatic();
static SynchronizedClassStatic instance2 = new SynchronizedClassStatic();
//同步锁,也就是串行执行线程,使用static关键字修饰方法名,当线程执行该方法时,将会锁定该方法所在的类Class对象,使得另外的线程处于等待状态
public synchronized static void method() {
System.out.println("我是类锁的第一种形式:static形式。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}

两个线程访问synchronized修饰静态方法的同步结果
- 同时访问
同步
方法与非同步
方法,非同步方法不受影响
,其不会因为其它方法加了synchronized修饰符而不能并发执行。
package com.zzw;
//同时访问同步方法和非同步方法
public class SynchronizedYesAndNo implements Runnable{
static SynchronizedYesAndNo instance = new SynchronizedYesAndNo();
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")) {
method1();
} else {
method2();
}
}
public synchronized void method1() {
System.out.println("我是加锁的方法,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public void method2() {
System.out.println("我是没加锁的方法,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}

同时访问同步方法与非同步方法的测试结果
- 访问同一个对象的
不同的
普通同步方法,虽然没有指定明确要锁定的对象,但本质是指定this作为同步锁,则线程将会串行执行。
package com.zzw;
public class SynchronizedDifferentMethod implements Runnable {
static SynchronizedDifferentMethod instance = new SynchronizedDifferentMethod();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")) {
method1();
} else {
method2();
}
}
//访问同一个对象的不同的普通同步方法,将会指定当前类实例this作为同步锁,那么线程也就串行执行
public synchronized void method1() {
System.out.println("我是加锁的方法method1,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public synchronized void method2() {
System.out.println("我是加锁的方法method2,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}

访问同一个对象的不同的普通同步方法结果
- 同时访问
静态
synchronized和非静态
synchronized方法,静态加锁方法锁定的是.class对象,非静态加锁方法锁定的是对象实例本身this,两者不冲突,可以并发执行。
package com.zzw;
public class SynchronizedStaticAndNormal implements Runnable {
static SynchronizedStaticAndNormal instance = new SynchronizedStaticAndNormal();
@Override
public void run() {
//2个线程拿到不一样的锁,显然是并发执行的
if (Thread.currentThread().getName().equals("Thread-0")) {
method1();
} else {
method2();
}
}
//类锁:synchronized修饰静态方法,锁定的是Class对象(.class)
public synchronized static void method1() {
System.out.println("我是静态加锁的方法method1,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
//对象锁:synchronized修饰没有static关键字方法,锁定的是对象实例本身this
public synchronized void method2() {
System.out.println("我是非静态加锁的方法method2,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}

同时访问静态加锁和非静态加锁方法结果
package com.zzw;
public class SynchronizedException implements Runnable{
static SynchronizedException instance = new SynchronizedException();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")) {
method1();
} else {
method2();
}
}
public synchronized void method1() {
System.out.println("我是加锁的方法method1,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 运行时出错抛出异常后jvm会帮我们释放当前锁资源,那么线程2就会立马获得锁资源
int i = 100 / 0;
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public synchronized void method2() {
System.out.println("我是加锁的方法method2,我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}

方法抛异常后,jvm会帮我们释放锁
面试常考并发7种情况总结:3点核心思想
- 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);
- 每个实例都对应有自己的一把锁,
不同实例之间互不影响
;例外:锁对象是*.class以及synchronized修饰的是static方法时,所有对象共用同一把锁(对应第2、3、4、6种情况);
- 无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)。
synchronized的性质
-
可重入
(递归锁):指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。好处:避免死锁
,提升封装性
。粒度
:(默认加锁的范围为线程)线程而非调用(用3种情况来说明和pthread得区别)
-
不可中断
:一旦这个锁已经被别的线程获得了,若当前线程还想获得,只能选择等待或者阻塞,直到别的线程释放
这个锁。若别的线程永远不释放锁,那么当前线程只能永远地等待下去。
网友评论