synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
修饰一个方法
被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
修饰一个静态的方法
其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
修饰一个代码块
被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
修饰一个类
其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
修饰一个方法
被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
如果多个线程访问同一个对象的实例变量,可能出现非线程安全问题。
例子:a线程set后sleep2000ms,看b线程是否可以趁机set,造成非线程安全
HasSelfPrivateNum.java:
-
package service;
-
-
public
class
HasSelfPrivateNum {
-
-
private
int
num
=
0;
-
-
public
void
addI
(String username) {
-
try {
-
if (username.equals(
"a")) {
-
num =
100;
-
System.out.println(
"a set over!");
-
Thread.sleep(
2000);
-
}
else {
-
num =
200;
-
System.out.println(
"b set over!");
-
}
-
System.out.println(username +
" num=" + num);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
-
}
A:
-
package extthread;
-
-
import service.HasSelfPrivateNum;
-
-
public
class
ThreadA
extends
Thread {
-
-
private HasSelfPrivateNum numRef;
-
-
public
ThreadA
(HasSelfPrivateNum numRef) {
-
super();
-
this.numRef = numRef;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
numRef.addI(
"a");
-
}
-
-
}
B:
-
package extthread;
-
-
import service.HasSelfPrivateNum;
-
-
public
class
ThreadB
extends
Thread {
-
-
private HasSelfPrivateNum numRef;
-
-
public
ThreadB
(HasSelfPrivateNum numRef) {
-
super();
-
this.numRef = numRef;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
numRef.addI(
"b");
-
}
-
-
}
run:
-
package test;
-
-
import service.HasSelfPrivateNum;
-
import extthread.ThreadA;
-
import extthread.ThreadB;
-
-
public
class
Run {
-
public
static
void
main
(String[] args) {
-
-
HasSelfPrivateNum
numRef
=
new
HasSelfPrivateNum();
-
-
ThreadA
athread
=
new
ThreadA(numRef);
-
athread.start();
-
-
ThreadB
bthread
=
new
ThreadB(numRef);
-
bthread.start();
-
-
}
-
}
结果:a线程set后sleep2000ms,b线程可以趁机set,造成非线程安全
这时我们使用synchronized修饰addI方法:
-
package service;
-
-
public
class
HasSelfPrivateNum {
-
-
private
int
num
=
0;
-
-
synchronized
public
void
addI
(String username) {
-
try {
-
if (username.equals(
"a")) {
-
num =
100;
-
System.out.println(
"a set over!");
-
Thread.sleep(
2000);
-
}
else {
-
num =
200;
-
System.out.println(
"b set over!");
-
}
-
System.out.println(username +
" num=" + num);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
-
}
结果:B不能set了,说明线程安全。
注意:我们取得的是对象锁,也就是说,一个对象一个锁,而不是锁住整个类或者代码或者方法。
例子:两个对象两个锁
myobject.java
打印名字后sleep,最后打印end
-
package extobject;
-
-
public
class
MyObject {
-
-
synchronized
public
void
methodA
() {
-
try {
-
System.out.println(
"begin methodA threadName="
-
+ Thread.currentThread().getName());
-
Thread.sleep(
5000);
-
System.out.println(
"end");
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
-
}
A:
-
package extthread;
-
-
import extobject.MyObject;
-
-
public
class
ThreadA
extends
Thread {
-
-
private MyObject object;
-
-
public
ThreadA
(MyObject object) {
-
super();
-
this.object = object;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
object.methodA();
-
}
-
-
}
B:
-
package extthread;
-
-
import extobject.MyObject;
-
-
public
class
ThreadB
extends
Thread {
-
-
private MyObject object;
-
-
public
ThreadB
(MyObject object) {
-
super();
-
this.object = object;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
object.methodA();
-
}
-
}
RUN:
-
package test.run;
-
-
import extobject.MyObject;
-
import extthread.ThreadA;
-
import extthread.ThreadB;
-
-
public
class
Run {
-
-
public
static
void
main
(String[] args) {
-
MyObject
object
=
new
MyObject();
-
ThreadA
a
=
new
ThreadA(object);
-
a.setName(
"A");
-
ThreadB
b
=
new
ThreadB(object);
-
b.setName(
"B");
-
-
a.start();
-
b.start();
-
}
-
-
}
结果:两个对象互不影响,各自运行各自上锁
其它方法被调用是什么效果呢?
之前做实验是因为怕大家不理解知识,现在大家已经有了一定的基础,这个结论不再做实验。
结论:
如果A线程持有x对象的锁,B线程不可调用synchronized修饰的方法,但是可以异步调用没有被synchronized修饰的方法
synchronized具有锁重入功能,也就是说一个线程获得锁,再次请求是可以再次得到对象的锁的
下面做实验验证这个结论:
service.java
-
package myservice;
-
-
public
class
Service {
-
-
synchronized
public
void
service1
() {
-
System.out.println(
"service1");
-
service2();
-
}
-
-
synchronized
public
void
service2
() {
-
System.out.println(
"service2");
-
service3();
-
}
-
-
synchronized
public
void
service3
() {
-
System.out.println(
"service3");
-
}
-
-
}
thread:
-
package extthread;
-
-
import myservice.Service;
-
-
public
class
MyThread
extends
Thread {
-
@Override
-
public
void
run
() {
-
Service
service
=
new
Service();
-
service.service1();
-
}
-
-
}
run:
-
package test;
-
-
import extthread.MyThread;
-
-
public
class
Run {
-
public
static
void
main
(String[] args) {
-
MyThread
t
=
new
MyThread();
-
t.start();
-
}
-
}
结果验证了上面的结论:
注:在父子类之间同样适用,不再做实验
但是如果一个线程出现了异常,难道就永远锁住了资源吗?其实不是的,出现异常自动释放锁。
实验:让a锁住对象后出现异常,看b是否可以拿到锁,代码不再展示。
结果:b可以正常执行。
修饰一个静态的方法
其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
也就是说整个类就一个锁,这也和静态的概念相符(静态方法和属性是属于类的而不是具体一个对象的)
让我们来验证这个结论:
service:
-
package service;
-
-
public
class
Service {
-
-
synchronized
public
static
void
printA
() {
-
try {
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName()
-
+
"在" + System.currentTimeMillis() +
"进入printA");
-
Thread.sleep(
3000);
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName()
-
+
"在" + System.currentTimeMillis() +
"离开printA");
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
-
synchronized
public
static
void
printB
() {
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在"
-
+ System.currentTimeMillis() +
"进入printB");
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在"
-
+ System.currentTimeMillis() +
"离开printB");
-
}
-
-
}
a:
-
package extthread;
-
-
import service.Service;
-
-
public
class
ThreadA
extends
Thread {
-
private Service service;
-
-
public
ThreadA
(Service service) {
-
super();
-
this.service = service;
-
}
-
-
@Override
-
public
void
run
() {
-
service.printA();
-
}
-
}
b:
-
package extthread;
-
-
import service.Service;
-
-
public
class
ThreadB
extends
Thread {
-
private Service service;
-
-
public
ThreadB
(Service service) {
-
super();
-
this.service = service;
-
}
-
-
@Override
-
public
void
run
() {
-
service.printB();
-
}
-
}
run:
-
package test;
-
-
import service.Service;
-
import extthread.ThreadA;
-
import extthread.ThreadB;
-
-
public
class
Run {
-
-
public
static
void
main
(String[] args) {
-
-
Service
service1
=
new
Service();
-
Service
service2
=
new
Service();
-
-
ThreadA
a
=
new
ThreadA(service1);
-
a.setName(
"A");
-
a.start();
-
-
ThreadB
b
=
new
ThreadB(service2);
-
b.setName(
"B");
-
b.start();
-
-
}
-
-
}
结果:
但是,请注意一个显而易见的结论:a线程访问synchronized修饰的静态方法时,b线程可以访问synchronized修饰的非静态方法,原因也很容易想到:静态方法是属于类的,普通方法是属于对象本身的,所以一个是对象锁,一个是class锁,不会影响。
为了验证这个结论,我们做实验看看结果。
service:
AB为静态的,C普通的。
-
package service;
-
-
public
class
Service {
-
-
synchronized
public
static
void
printA
() {
-
try {
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName()
-
+
"在" + System.currentTimeMillis() +
"进入printA");
-
Thread.sleep(
3000);
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName()
-
+
"在" + System.currentTimeMillis() +
"离开printA");
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
-
synchronized
public
static
void
printB
() {
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在"
-
+ System.currentTimeMillis() +
"进入printB");
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在"
-
+ System.currentTimeMillis() +
"离开printB");
-
}
-
-
synchronized
public
void
printC
() {
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在"
-
+ System.currentTimeMillis() +
"进入printC");
-
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在"
-
+ System.currentTimeMillis() +
"离开printC");
-
}
-
-
}
a:
-
package extthread;
-
-
import service.Service;
-
-
public
class
ThreadA
extends
Thread {
-
private Service service;
-
-
public
ThreadA
(Service service) {
-
super();
-
this.service = service;
-
}
-
-
@Override
-
public
void
run
() {
-
service.printA();
-
}
-
-
}
b:
-
package extthread;
-
-
import service.Service;
-
-
public
class
ThreadB
extends
Thread {
-
private Service service;
-
-
public
ThreadB
(Service service) {
-
super();
-
this.service = service;
-
}
-
-
@Override
-
public
void
run
() {
-
service.printB();
-
}
-
}
c:
-
package extthread;
-
-
import service.Service;
-
-
public
class
ThreadC
extends
Thread {
-
-
private Service service;
-
-
public
ThreadC
(Service service) {
-
super();
-
this.service = service;
-
}
-
-
@Override
-
public
void
run
() {
-
service.printC();
-
}
-
}
run:
-
package test;
-
-
import service.Service;
-
import extthread.ThreadA;
-
import extthread.ThreadB;
-
import extthread.ThreadC;
-
-
public
class
Run {
-
-
public
static
void
main
(String[] args) {
-
-
Service
service
=
new
Service();
-
-
ThreadA
a
=
new
ThreadA(service);
-
a.setName(
"A");
-
a.start();
-
-
ThreadB
b
=
new
ThreadB(service);
-
b.setName(
"B");
-
b.start();
-
-
ThreadC
c
=
new
ThreadC(service);
-
c.setName(
"C");
-
c.start();
-
-
}
-
-
}
结果:
说明:验证了结论,因为AB同步执行,C异步执行。
修饰一个代码块
被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;(而不一定是本对象了)
(synchronized方法是对当前对象加锁,synchronized代码块是对某个对象加锁)
用synchronized声明方法是有弊端的,比如A调用同步方法执行一个很长时间的任务,这时B就必须等待,有时候这种长时间等待是低效率且没有必要的,这时我们就要认识一下synchronized同步代码块了。
格式是这样的:synchronized(类名){......}
我们先来认识第一种用法来体会修饰代码块的好处:
synchronized(this)同步代码块:a调用相关代码后,b对其它synchronized方法和synchronized(this)同步代码块调用会阻塞。但是没有被synchronized修饰的代码就得以执行,不像之前修饰方法那么死板了。
我们来看一个例子:
第一个循环异步,第二个循环同步:
-
package mytask;
-
-
public
class
Task {
-
-
public
void
doLongTimeTask
() {
-
for (
int
i
=
0; i <
100; i++) {
-
System.out.println(
"nosynchronized threadName="
-
+ Thread.currentThread().getName() +
" i=" + (i +
1));
-
}
-
System.out.println(
"");
-
synchronized (
this) {
-
for (
int
i
=
0; i <
100; i++) {
-
System.out.println(
"synchronized threadName="
-
+ Thread.currentThread().getName() +
" i=" + (i +
1));
-
}
-
}
-
-
}
-
}
thread1:
-
package mythread;
-
-
import mytask.Task;
-
-
public
class
MyThread1
extends
Thread {
-
-
private Task task;
-
-
public
MyThread1
(Task task) {
-
super();
-
this.task = task;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
task.doLongTimeTask();
-
}
-
-
}
thread2:
-
package mythread;
-
-
import mytask.Task;
-
-
public
class
MyThread2
extends
Thread {
-
-
private Task task;
-
-
public
MyThread2
(Task task) {
-
super();
-
this.task = task;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
task.doLongTimeTask();
-
}
-
-
}
run:
-
package test;
-
-
import mytask.Task;
-
import mythread.MyThread1;
-
import mythread.MyThread2;
-
-
public
class
Run {
-
-
public
static
void
main
(String[] args) {
-
Task
task
=
new
Task();
-
-
MyThread1
thread1
=
new
MyThread1(task);
-
thread1.start();
-
-
MyThread2
thread2
=
new
MyThread2(task);
-
thread2.start();
-
}
-
}
结果:
在非同步代码块时,是交叉打印的
同步代码块时排队执行:
另一个用法:
synchronized(非this)同步代码块(也就是说将任意对象作为对象监视器):
格式:synchronized(非this对象x){......}
1、当多个线程持有的对象监听器为同一个对象时,依旧是同步的,同一时间只有一个可以访问,
2、但是对象不同,执行就是异步的。
这样有什么好处呢?
(因为如果一个类有很多synchronized方法或synchronized(this)代码块,还是会影响效率,这时用synchronized(非this)同步代码块就不会和其它锁this同步方法争抢this锁)
实验
第一点我们就不验证了,因为体现不出它的好处,我们验证第二点:
service:
一个同步非this代码块,一个同步方法:
-
package service;
-
-
public
class
Service {
-
-
private
String
anyString
=
new
String();
-
-
public
void
a
() {
-
try {
-
synchronized (anyString) {
-
System.out.println(
"a begin");
-
Thread.sleep(
3000);
-
System.out.println(
"a end");
-
}
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
-
synchronized
public
void
b
() {
-
System.out.println(
"b begin");
-
System.out.println(
"b end");
-
}
-
-
}
a:
-
package extthread;
-
-
import service.Service;
-
-
public
class
ThreadA
extends
Thread {
-
private Service service;
-
-
public
ThreadA
(Service service) {
-
super();
-
this.service = service;
-
}
-
-
@Override
-
public
void
run
() {
-
service.a();
-
-
}
-
-
}
b:
-
package extthread;
-
-
import service.Service;
-
-
public
class
ThreadB
extends
Thread {
-
-
private Service service;
-
-
public
ThreadB
(Service service) {
-
super();
-
this.service = service;
-
}
-
-
@Override
-
public
void
run
() {
-
service.b();
-
-
}
-
-
}
run:
-
package test;
-
-
import service.Service;
-
import extthread.ThreadA;
-
import extthread.ThreadB;
-
-
public
class
Run {
-
-
public
static
void
main
(String[] args) {
-
Service
service
=
new
Service();
-
-
ThreadA
a
=
new
ThreadA(service);
-
a.setName(
"A");
-
a.start();
-
-
ThreadB
b
=
new
ThreadB(service);
-
b.setName(
"B");
-
b.start();
-
-
}
-
-
}
结果:
确实是异步的。
最后一个要注意的点:我们知道synchronized(非this对象x){......}是将对象x监视,这也就意味着当线程a调用这段代码时,线程b调用类x中的同步方法和代码块也会是同步的效果(阻塞)。
为了让大家更明白,做最后一个例子:
首先创建一个有静态方法的类:
-
package test2.extobject;
-
-
public
class
MyObject {
-
synchronized
public
void
speedPrintString
() {
-
System.out.println(
"speedPrintString ____getLock time="
-
+ System.currentTimeMillis() +
" run ThreadName="
-
+ Thread.currentThread().getName());
-
System.out.println(
"-----------------");
-
System.out.println(
"speedPrintString releaseLock time="
-
+ System.currentTimeMillis() +
" run ThreadName="
-
+ Thread.currentThread().getName());
-
}
-
}
然后用 synchronized(非this对象x){......}的形式引用它,并进入这个代码块,然后看看这时这个静态方法是否可以被调用。
service:作用是synchronized(object)
-
package test2.service;
-
-
import test2.extobject.MyObject;
-
-
public
class
Service {
-
-
public
void
testMethod1
(MyObject object) {
-
synchronized (object) {
-
try {
-
System.out.println(
"testMethod1 ____getLock time="
-
+ System.currentTimeMillis() +
" run ThreadName="
-
+ Thread.currentThread().getName());
-
Thread.sleep(
5000);
-
System.out.println(
"testMethod1 releaseLock time="
-
+ System.currentTimeMillis() +
" run ThreadName="
-
+ Thread.currentThread().getName());
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
-
}
threadA:
-
package test2.extthread;
-
-
import test2.extobject.MyObject;
-
import test2.service.Service;
-
-
public
class
ThreadA
extends
Thread {
-
-
private Service service;
-
private MyObject object;
-
-
public
ThreadA
(Service service, MyObject object) {
-
super();
-
this.service = service;
-
this.object = object;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
service.testMethod1(object);
-
}
-
-
}
threadB:
-
package test2.extthread;
-
-
import test2.extobject.MyObject;
-
-
public
class
ThreadB
extends
Thread {
-
private MyObject object;
-
-
public
ThreadB
(MyObject object) {
-
super();
-
this.object = object;
-
}
-
-
@Override
-
public
void
run
() {
-
super.run();
-
object.speedPrintString();
-
}
-
}
run:
-
package test2.run;
-
-
import test2.extobject.MyObject;
-
import test2.extthread.ThreadA;
-
import test2.extthread.ThreadB;
-
import test2.service.Service;
-
-
public
class
Run {
-
-
public
static
void
main
(String[] args)
throws InterruptedException {
-
Service
service
=
new
Service();
-
MyObject
object
=
new
MyObject();
-
-
ThreadA
a
=
new
ThreadA(service, object);
-
a.setName(
"a");
-
a.start();
-
-
Thread.sleep(
100);
-
-
ThreadB
b
=
new
ThreadB(object);
-
b.setName(
"b");
-
b.start();
-
}
-
-
}
结果:
是同步的。
当然,把修饰方法改为修饰代码块也是一样不能被执行的。
Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。
synchronized底层原理
在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统来实现,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。
在 Java 6 之后从 JVM 层面对synchronized 较大优化,锁的实现引入了如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令。
其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。但是原理其实都是类似的。具体的实现是操作系统的知识可以去翻我操作系统的文章。
锁详解
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
自旋:当有个线程A去请求某个锁的时候,这个锁正在被其它线程占用,但是线程A并不会马上进入阻塞状态,而是循环请求锁(自旋)。这样做的目的是因为很多时候持有锁的线程会很快释放锁的,线程A可以尝试一直请求锁,没必要被挂起放弃CPU时间片,因为线程被挂起然后到唤醒这个过程开销很大,当然如果线程A自旋指定的时间还没有获得锁,仍然会被挂起。
自适应性自旋:自适应性自旋是自旋的升级、优化,自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定。例如线程如果自旋成功了,那么下次自旋的次数会增多,因为JVM认为既然上次成功了,那么这次自旋也很有可能成功,那么它会允许自旋的次数更多。
锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
偏向锁的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那么偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不用做了。偏向锁默认是开启的,也可以关闭。
转载自:
https://blog.csdn.net/hebtu666/article/details/103057476?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167656144816800182766614%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=167656144816800182766614&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-29-103057476-null-null.142v73insert_down2,201v4add_ask,239v2insert_chatgpt&utm_term=synchronized&spm=1018.2226.3001.4187