一)synchronized关键字简介
synchronized:Java语言中的关键字,可用来给对象、方法或代码块加锁,当它锁定一个对象、一个方法或一个代码块的时,同一时刻最多只能有一个线程执行这段代码。
1)对象加锁:当对象加锁时,其它别的线程在该类所有对象上的任何操作都不能进行,需要等待该对象释放才能执行。
// 方式一: 当前对象加锁
public void currentObject() {
synchronized(this) {
// 代码
}
}
// 方式二: 虚拟对象加锁
Object xlock = new Object(), ylock = new Object();
public void xyObject() {
synchronized(xlock) {
// 代码
}
synchronized(ylock) {
// 代码
}
}
2)方法加锁:当方法加锁时,线程获得的是成员锁,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入执行。
public synchronized void synMethod() {
// 方法体
}
3)代码块加锁:当对某一代码块加锁时,synchronized后跟括号,括号里是变量,这样一次只有一个线程进入该代码块,此时线程获得的是成员锁。
public Object synMethod(Object obj) {
synchronized(obj) {
// 一次只能有一个线程进入执行
}
}
二)synchronized关键字基本规则
规则一:当有多个线程同时对“某对象”的“synchronized方法”或“synchronized代码块”访问时,一次只会有一个线程得到“某对象”的“synchronized方法”或“synchronized代码块”访问权限,其它线程会阻塞,必须等待该线程执行完之后才能进行访问。
同一对象访问:
public class MyRunnable implements Runnable {
public void run(){
synchronized (this) { // 代码块
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ", i=" + i);
}
}
}
public static void main(String[] args) {
MyRunnable my = new MyRunnable(); // 创建一个Runnable对象,声明多个线程对它进行访问
Thread t1 = new Thread(my); // t1线程对Runnable对象访问
Thread t2 = new Thread(my); // t2线程对Runnable对象访问
t1.start(); // 启动t1线程,会对Runnable对象加锁
t2.start(); // 启动t2线程,由于t1线程已加锁,所以需要等待t1线程释放锁之后才能访问
}
}
运行结果:
不同对象访问:
public class MyThread extends Thread {
private String name;
public MyThread (String name) {
this.name = name;
}
public void run(){
synchronized (this) { // 当前对象,只要对象不相同,都可以访问
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
System.out.println(name + ", i=" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread my1 = new MyThread("A线程"); // 创建一个A线程
MyThread my2 = new MyThread("B线程"); // 创建一个B线程
my1.start(); // 启动A线程时, this表示A线程对象
my2.start(); // 启动B线程时, this表示B线程对象
}
}
运行结果:
规则二:当有多个线程同时对“某对象”的“synchronized方法”或“synchronized代码块”访问时,其它线程都可以访问“某对象”的非同步代码块。
public class Calc {
// 同步方法
public void synchroMethod() {
synchronized (this) { // 当前对象
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
System.out.println(Thread.currentThread().getName() + ", i=" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 非同步方法
public void noSynchroMethod() {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
System.out.println(Thread.currentThread().getName() + ", i=" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final Calc calc = new Calc();
Thread t1 = new Thread(new Runnable() {
public void run() {
calc.synchroMethod(); // 调用Calc对象中的同步方法
}
}, "t1线程");
Thread t2 = new Thread(new Runnable() {
public void run() {
calc.noSynchroMethod(); // 调用Calc对象中的非同步方法
}
}, "t2线程");
t1.start(); // 启动t1线程
t2.start(); // 启动t2线程
}
}
运行结果:
规则三:当有多个线程同时对“某对象”的“synchronized方法”或“synchronized代码块”访问时,其它线程对于“某对象”的“synchronized方法”或“synchronized代码块”访问都将被暂时阻塞。
public class Calc {
// 同步方法
public void synchroMethod() {
synchronized (this) { // 当前对象
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
System.out.println(Thread.currentThread().getName() + ", i=" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 非同步方法
public void noSynchroMethod() {
synchronized (this) { // 当前对象
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 睡眠100毫秒,是为了更好看出线程的切换打印
System.out.println(Thread.currentThread().getName() + ", i=" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final Calc calc = new Calc();
Thread t1 = new Thread(new Runnable() {
public void run() {
calc.synchroMethod(); // 调用Calc对象中的同步方法
}
}, "t1线程");
Thread t2 = new Thread(new Runnable() {
public void run() {
calc.noSynchroMethod(); // 调用Calc对象中的非同步方法
}
}, "t2线程");
t1.start(); // 启动t1线程
t2.start(); // 启动t2线程
}
}
运行结果:
三)全局锁和实例锁
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
实例锁对应的就是synchronized关键字。
关于“全局锁”和“实例锁”的一个案例:
pulbic class Something {
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
(01) x.isSyncA()与x.isSyncB()
(02) x.isSyncA()与y.isSyncA()
(03) x.cSyncA()与y.cSyncB()
(04) x.isSyncA()与Something.cSyncA()
(01) 不能被同时访问。因为isSyncA()和isSyncB()都是访问同一个对象(对象x)的同步锁!
(02) 可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。
(03) 不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。
(04) 可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!