本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Synchronized 锁的使用
synchronized的三种应用方式
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
- 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
- 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
- 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
1.普通方法
1.1 两个线程访问同一个加锁方法
package com.jzj.study.learn0513.jzj0516;
public class NormalMethod {
private int count;
/**
* Synchronized 加锁普通方法
*/
private synchronized void add() {
count++;
}
private Integer getCount() {
return count;
}
public static void main(String[] args) {
NormalMethod nm = new NormalMethod();
//开启一个线程 count++
new Thread(() -> {
for (int i = 0; i < 50000; i++) {
nm.add();
}
}).start();
//开启第二个线程 count++;
new Thread(() -> {
for (int i = 0; i < 50000; i++) {
nm.add();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("result: " + nm.getCount());
}
}
执行结果 result:10000,没问题,多个线程针对Synchronized 普通方法需要加锁
1.2 两个线程访问同一对象方法,一个加锁,一个不加锁
package com.jzj.study.learn0513.jzj0516;
public class NormalMethodDiff {
/**
* Synchronized 加锁Write方法
*/
private synchronized void write() {
System.out.println(Thread.currentThread().getName() + " write begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " write end");
}
/**
* 不加锁方法
*/
private void read() {
System.out.println(Thread.currentThread().getName() + " read begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " read end");
}
public static void main(String[] args) {
NormalMethodDiff normalMethodDiff = new NormalMethodDiff();
//开启线程1 访问 锁write方法
new Thread(() -> normalMethodDiff.write()).start();
//开启线程2 访问 锁write方法
new Thread(() -> normalMethodDiff.read()).start();
//开启线程3 访问 锁write方法
new Thread(() -> normalMethodDiff.write()).start();
}
}
执行结果,同一个类中存在加锁和不加锁的方法,线程获取加锁方法的同时,另一个线程可以获取不加锁的方法,不影响
1.3 两个个线程访问同一对象多个Sync加锁方法
表面上Synchronized是加在方法上,现在尝试多个线程访问同一对象的多个Synchronized方法,表面看来,t1获取A方法,t2获取B方法,应该是可以的,但是想一下底层,我们知道普通方法上的Synchronized 关键字是加在对象上的,对象的锁只有一个,所以是不能获取同一个对象的多个Sync方法的,必须等前对象释放锁
package com.jzj.study.learn0513.jzj0516;
public class NormalMethodManyOne {
/**
* Synchronized 加锁Write方法
*/
private synchronized void write() {
System.out.println(Thread.currentThread().getName() + " write begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " write end");
}
/**
* Synchronized 加锁Read方法
*/
private synchronized void read() {
System.out.println(Thread.currentThread().getName() + " read begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " read end");
}
public static void main(String[] args) {
NormalMethodManyOne normalMethodManyOne = new NormalMethodManyOne();
//开启线程1 访问 锁write 方法
new Thread(() -> normalMethodManyOne.write()).start();
//开启线程2 访问 另一个锁Read 方法
new Thread(() -> normalMethodManyOne.read()).start();
}
}
执行结果:两个线程访问同一个对象的不同Sync加锁方法,虽然是不同方法,但是锁是锁在对象的,依旧要等前一个方法释放锁,才能获取锁
1.4 多个线程访问不同对象多个Sync加锁方法
我们知道普通方法上的Synchronized 关键字是加在对象上的,现在尝试 多个对象访问多个对象的Synchronized方法
package com.jzj.study.learn0513.jzj0516;
public class NormalMethodManyMany {
/**
* Synchronized 加锁Write方法
*/
private synchronized void write() {
System.out.println(Thread.currentThread().getName() + " write begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " write end");
}
/**
* 不加锁方法
*/
private void read() {
System.out.println(Thread.currentThread().getName() + " read begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " read end");
}
public static void main(String[] args) {
NormalMethodManyMany normalMethodManyManyOne = new NormalMethodManyMany();
NormalMethodManyMany normalMethodManyManyTwo = new NormalMethodManyMany();
//开启线程1 访问 锁write方法
new Thread(() -> normalMethodManyManyOne.write()).start();
//开启线程2 访问 锁write方法
new Thread(() -> normalMethodManyManyTwo.write()).start();
}
}
执行结果:可以看到线程1和线程2分别获取One对象和Two对象的两个加锁方法,同时获取,互不干扰
2.静态同步方法
2.1 多个线程调用多个对象的static加锁Sync方法
Synchronized修饰静态Static方法,其实锁是加载Class类上的,和你新建了多少个对象,对象调用方法没任何关系,即使有再多的对象,用的还都是一把锁
package com.jzj.study.learn0513.jzj0516;
public class StaticMethod {
/**
* Synchronized 修饰静态方法
*/
public static synchronized void write() {
System.out.println(Thread.currentThread().getName() + " write begin");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " write end!");
}
public static void main(String[] args) {
//开启线程1 执行锁 write 方法
new Thread(StaticMethod::write).start();
//开启线程2 执行锁 write 方法
new Thread(StaticMethod::write).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("这个要是不明显的话,我们看下下面的 创建两个对象");
//新建对象One,执行方法write
StaticMethod smOne = new StaticMethod();
new Thread(() -> smOne.write()).start();
//新建对象Two,执行方法write
StaticMethod smTwo = new StaticMethod();
new Thread(() -> smTwo.write()).start();
}
}
执行结果:可以看到,前面的线程不释放锁,后面时没办法拿到锁的,即使有多个对象One、Two用的依旧时一把锁,需要互斥等待锁的释放
3.修饰代码块
有时候我们方法冗余了很多业务操作,对方法加锁的话太重了,我们需要细化粒度 此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。
3.1 修饰代码块用法
- 修饰具体的对象
private static CodePartSync instance = new CodePartSync();
synchronized (instance) {
for (int i = 0; i < 50000; i++) {
count++;
}
}
}
- 修饰this关键字
synchronized (this){
for (int i = 0; i < 50000; i++) {
count++;
}
}
- 修饰Class类对象
synchronized (CodePartSync.class){
for (int i = 0; i < 50000; i++) {
count++;
}
}
3.2 修饰代码块,多个线程访问加锁代码块
package com.jzj.study.learn0513.jzj0516;
public class CodePartSync {
private static CodePartSync instance = new CodePartSync();
private static int count = 0;
private void add() {
//处理业务逻辑
System.out.println(Thread.currentThread().getName() + " 处理业务逻辑");
//加锁 instance对象 正确
synchronized (instance) {
//加锁 this,指当前对象 正确
// synchronized (this){
//加锁 class,类对象 正确
// synchronized (CodePartSync.class){
//加锁新建的对象 new Obj() 错误!!!!!!!!!
// synchronized (new CodePartSync()) {
for (int i = 0; i < 50000; i++) {
count++;
}
}
}
public static void main(String[] args) {
CodePartSync codePartSync = new CodePartSync();
//开启线程1 执行累加
new Thread(() -> codePartSync.add()).start();
//开启线程2 执行累加
new Thread(() -> codePartSync.add()).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
}
执行结果:
- synchronized (instance) 加锁 instance对象 正确
- synchronized (this) 加锁 this,指当前对象 正确
- synchronized (CodePartSync.class) 加锁 class,类对象 正确
!!!!! 错误的方法
只有你同步的新建的对象 比如synchronized (new CodePartSync()) 这样的时候,才是错误的,因为每个线程的锁都是新的对象,根本无法锁住代码块
!!!!! 错误的结果
*** 关于Synchronized 关键字的用法就到此为止了