synchronzied关键字
简介
synchronized是Java中的关键字,是一种同步锁,当一个线程调用一个synchronized修饰的方法时,其他线程不能同时访问此方法,必须当前线程执行完,才能有新的线程竞争
Synchronzied的使用
修饰代码块
//修饰代码块
public void text1(){
synchronized (this){
//work
}
}
synchronzied锁的是某一个对象,但是实际作用在代码块里
修饰普通方法
public synchronized void text2(){
}
锁的是对象实例
多个线程来竞争时,哪个线程先获取到对象实例,哪个线程先执行,执行结束后,其他线程才能执行。
看一个例子:
class text1 extends Thread {
synchronizedDemo s1 = new synchronizedDemo();
public text1(synchronizedDemo s){
this.s1 = s;
}
@Override
public void run() {
s1.text1();
}
}
class text2 extends Thread {
synchronizedDemo s1 = new synchronizedDemo();
public text2(synchronizedDemo s){
this.s1 = s;
}
@Override
public void run() {
s1.text2();
}
}
public class synchronizedDemo {
public synchronized void text1(){
}
public synchronized void text2(){
}
public static void main(String[] args) {
synchronizedDemo s = new synchronizedDemo();
text1 text1 = new text1(s);
text1 text2 = new text1(s);
text1.start();
text2.start();
}
}
此例子是 分别用两个类 分别调用一个实例对象的不同方法,它会并发执行吗?
答案是不会
,因为Synchronzied修饰的是普通方法,锁的是对象实例,只有一个实例对象时,不会有多个线程同时访问。
还是刚才的例子 改一下
class text1 extends Thread {
synchronizedDemo s1 = new synchronizedDemo();
public text1(synchronizedDemo s){
this.s1 = s;
}
@Override
public void run() {
s1.text1();
}
}
class text2 extends Thread {
synchronizedDemo s1 = new synchronizedDemo();
public text2(synchronizedDemo s){
this.s1 = s;
}
@Override
public void run() {
s1.text1();
}
}
public class synchronizedDemo {
public synchronized void text1(){
}
public synchronized void text2(){
}
public static void main(String[] args) {
synchronizedDemo s1 = new synchronizedDemo();
synchronizedDemo s2 = new synchronizedDemo();//加了一个对象实例
text1 text1 = new text1(s1);//分别调用
text1 text2 = new text1(s2);
text1.start();
text2.start();
}
}
实现两个对象实例,两个类分别调用这两个对象实例的同一个方法,它会并发执行吗
答案是会的
,因为是不同的对象实例,两个不影响
修饰静态方法
public synchronized static void text3(){
}
静态方法上 锁的是当前的class实例
如果两个线程调用 类名.text3 能不能同时并发执行?
答案是不行的
因为这两个线程都是调用 class.text3,调用同一个class实例,所以只能串行执行
synchronized特点
- synchronized修饰的代码块或者方法,JVM只允许一个线程去访问,它是通过锁机制来完成同一时刻只能一个线程访问,就是临界区。
- synchronized满足 原子性,可见性,有序性
原理
修饰代码块
public class text{
//修饰代码块
public void text1(){
synchronized (this){
}
}
public void text2(){
}
}
以上面代码为例 进行cmd反编译 javac - text.java 会生成 text.class字节码文件 然后Javap -v text 进行反编译
我们会看到加了synchronized关键字 两个方法的区别 text1 会出现 monitorenter
和 monitorexit
的东西 这就是加锁和解锁的,那么为什么会出现两个解锁,下面那个monitorexit 是为了预防处理异常的解锁
注意 里面有个 flags标志位 flags: ACC_PUBLIC 下面会看到不同
修饰普通方法
public class text{
public synchronized void text1(){
}
public void text2(){
}
}
红框中 flags: ACC_PUBLIC, ACC_SYNCHRONIZED
修饰静态方法
public class text{
public synchronized static void text1(){
}
public void text2(){
}
}
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
我们可以看到无论是monitor` 还是 `ACC_SYNCHRONIZED
它本质上都是对一个对象的监视器(monitor)进行获取,而这个监视器的获取是排他的,同一时刻只能一个线程能获取到synchronized 所保护对象的监视器。
每个对象都有它的监视器,一个线程进来 先获取它的监视器,如果成功才能对对象进行操作,如果失败就会 进入到同步队列中等待(即获取锁失败,进入Blocking状态,等待获取锁资源) 获取到监视器的线程 monitorExit释放监视器,释放后才会重新竞争其对象的监视器。
monitor的排他性的实现是需要借助操作系统所提供的锁(借助硬件实现)来实现。
而synchronized也称为重量级锁,随着Java的发展,对于synchronized进行了优化,锁的优化:自旋锁,偏向锁,强重量锁,重量级锁。 实现思路:尽量通过代码层次进行加锁操作,减少系统交互。
使用场景
下列场景下线程是否安全?
场景一:两个线程同时访问同一个对象的同步方法。
线程安全
分析: 两个线程访问同一个对象锁,会互相等待b
***场景二:***两个线程同时访问两个对象的同步方法。
线程不安全
*分析:两个线程 对 不同的 对象进行操作,这是一种锁失效的情况,这种情况不存在互相竞争的关系,互相持有一把锁,互不干扰。
如何解决锁失效的问题:将方法用static
修饰,即形成类锁
,即实现多个线程竞争同一把类锁
*场景三:*两个线程同时访问一个或两个对象的静态同步方法。
线程安全
**分析:**相当一多个线程竞争同一把类锁。
*场景四:*两个线程分别同时访问(一个或者两个)对象的同步方法(synchronized修饰)和非同步方法。
线程不安全
例子:
class TestDemo {
public synchronized void fun1() {
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");
}
public void fun3() {
System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3");
}
public static void main(String[] args) {
TestDemo test1 = new TestDemo();
new Thread("A") {
@Override
public void run() {
test1.fun1(); //lock
test1.fun3();
}
}.start();
new Thread("B") {
@Override
public void run() {
test1.fun1(); //lock
test1.fun3();
}
}.start();
}
}
看上面的代码 实现了text1用synchronized修饰,text3是普通方法(两个方法都使线程睡眠一秒),分别用一个对象调用两个相同方法,观察是不是顺序执行。
结果:
可以看到是线程非安全的
可以看到 顺序没有按照顺序打印,因为非同步方法不是线程安全的。
可以得出结论
:非同步方法不受其他同步方法的影响
*场景五:*两个线程访问同一个对象中的同步方法,同步方法又调用另外一个非同步方法 。
线程不安全的
代码:
class TestDemo {
public synchronized void fun1() {
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
fun3(); //在同步方法中调用普通方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");
}
public void fun3() {
System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3结束");
}
public static void main(String[] args) {
TestDemo test1 = new TestDemo();
TestDemo test2 = new TestDemo();
new Thread("A") {
@Override
public void run() {
test1.fun1(); //unlock
// test1.fun3();
}
}.start();
new Thread("B") {
@Override
public void run() {
test1.fun1(); //lock
// test1.fun3();
}
}.start();
Thread threadc = new Thread("C"){
@Override
public void run() {
test1.fun3();
}
};
threadc.start();
}
}
此例子中为了测试结果 新建一个线程c调用普通方法,发现结果是非线程安全
的,当我们把线程c去掉后 ,只留下AB 两个线程调用同步方法,会发现它是线程安全的
*分析:*在同步方法中调用的非同步方法,是不会受synchronized的影响的 ,其他线程可以调用非同步方法。但是若没有其他线程调用非同步方法,它是线程安全的
*场景六:*两个线程同时访问同一个对象的不同的同步方法
线程安全
分析:两个线程分别获取的为对象锁,即为同一把锁,所以还是会串行执行。而对象锁的作用范围是对象中所有的同步方法。所以它是线程安全的
*场景七:*两个线程同时访问静态synchronized和非静态synchornized方法
线程不安全
我们来看一下代码
class TestDemo {
public synchronized void fun1() {
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");
}
public synchronized static void fun2() {
//类锁 class对象
System.out.println(Thread.currentThread().getName() + ":: 静态同步方法func2开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":: 静态同步方法func2结束");
}
public static void main(String[] args) {
TestDemo test1 = new TestDemo();
TestDemo test2 = new TestDemo();
new Thread("A") {
@Override
public void run() {
test1.fun1(); //lock
}
}.start();
new Thread("B") {
@Override
public void run() {
fun2();
}
}.start();
}
}
看一下结果
它没有按我们预想的结果 所以是非线程安全的
*分析:*静态synchronized锁的是类锁
(.class)而非静态synchronized锁的是对象锁,所以互不影响,它是非线程安全的
*场景八:*同步方法抛出异常,JVM会自动释放锁
只有当一个线程的同步方法出现异常时,会释放锁,不会造成死锁现象,其他线程继续运行。
class TestDemo {
public synchronized void fun1() {
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");
}
public static void main(String[] args) {
TestDemo test1 = new TestDemo();
Thread threadc = new Thread("C"){
@Override
public void run() {
test1.fun3();
}
};
threadc.start();
threadc.interrupt();
new Thread("D"){
@Override
public void run() {
test1.fun1();
}
}.start();
}
}
这里使用interrupt中断线程c的睡眠,达到抛出异常的目的
来看结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPEjn8vk-1613567563137)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210217150906582.png)]
这里使用interrupt中断线程c的睡眠,达到抛出异常的目的
来看结果:
我们看到D线程还是执行完成 说明线程是安全的