synchronized关键字
说在前面:文章内容为自己学在习过程中对知识的理解,如有不正确的之处,欢迎大家指正~~共同进步!!!
在日常开发工作,很多情况会用到多线程,那么在多线程运行的环境下,就很难避免会涉及到一些共享数据。若出现多个线程同时访问操作同一共享数据的情况,可能会造成数据混乱的现象,破坏数据一致性。这个时候就要考虑使用“锁”来解决这一现象。“锁”见名知意,将某物锁起来,不让其他人用。那么在我们Java程序开发中,锁的物就是我们的类、对象、代码块。本文主要说的是通过Java中synchronized关键字实现锁的效果。
首先整理synchronized的几个知识点:
1.synchronized修饰普通方法,锁的是该类的当前实例对象
2.synchronized修饰静态方法,锁的是该类(即该类的所有实例对象)
3.synchronized修饰代码块,锁的是synchronized(X)代码块中定义的X对象实例
4.synchronized代码块里代码单线程执行
5.synchronized具有可重入性
6.处于阻塞状态的线程即使调用了中断方法也不会生效
7.使用synchronized会降低效率
下面跟着代码进一步理解吧
情景描述:模拟银行柜台叫号的工作流程。多个柜台办理业务,多个顾客拿着各自的排号等待去柜台办理业务。
剖析:情景描述中的多个柜台就是多个需要执行任务的线程,每个顾客手中的排号就是“共享数据”
情景功能实现测试一:不加锁
package com.thread.demo;
/**
* @Author: Peacock__
* @Date: 2019/4/25 14:36
*/
public class BankThread implements Runnable {
//当前排号:当前办理的排号(在情景中充当共享数据的身份)
private Integer CURRENT_NUMBER = 1;
//最大排号:最多办理3个顾客的业务就要去开集体临时会议了
private final static Integer MAX_NUMBER = 3;
@Override
public void run() {
while(true){
//当前排号大于最大排号时,任务执行完毕
if(CURRENT_NUMBER > MAX_NUMBER){
break;
}
//休眠5毫秒,模拟正在办理业务
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//控制台输出,并将当前排号+1,模拟叫下一位顾客
System.out.println(Thread.currentThread().getName() + "办理完毕"+(CURRENT_NUMBER++)+"号顾客业务");
}
}
}
package com.thread.demo;
/**
* @Author: Peacock__
* @Date: 2019/4/25 14:39
*/
public class BankTest {
public static void main(String[] args) {
final BankThread bankThread = new BankThread();
Thread threadA = new Thread(bankThread,"一号柜台");
Thread threadB = new Thread(bankThread,"二号柜台");
Thread threadC = new Thread(bankThread,"三号柜台");
threadA.start();
threadB.start();
threadC.start();
}
}
控制台数据结果
看到运行结果我们可以看到“一号柜台”、“二号柜台”、“三号柜台”谁抢占到了资源谁就办理了业务,没有先后顺序之分。但是肯定会很诧异,为什么我们明明规定了最多办理3个客户,为什么一号柜台还给4号顾客办理了业务????解释如下图
注意:不要被控制台输出语句的顺序影响,要按照CURRENT_NUMBER的值来分析。
按照输出结果来分析,是一号柜台先抢占到了资源,然后二号柜台抢占到资源,最后三号柜台抢占到资源。当一号柜台抢占到资源进入run方法后,直接进入while循环,当前的CURRENT_NUMBER是1,不符合break条件, 因为没有加锁,所以在第一柜台执行run方法的时候,第二柜台已经抢占到了资源(注意这个时候如果第一柜台第一次循环执行完毕了,那么CURRENT_NUMBER = 2,如果第一柜台第一次循环没有执行完毕了,那么CURRENT_NUMBER = 1) 按照输出结果可知此时第一柜台没有执行完毕,所以此时CURRENT_NUMBER = 1不符合break条件,所以第二柜台也会进入sleep状态, 正当第二柜台在sleep的时候第三柜台进来了,按照输出结果可知此时第一柜台、第二柜台都没执行完毕,所以此时依旧CURRENT_NUMBER = 1,也不符合break条件,所以第三柜台也会进入sleep状态, 按照输出结果依旧可知,第三柜台进来后,第一柜台第一次执行完毕,进行第二次执行,此时第二柜台和第三柜台都没有执行完毕,所以此时CURRENT_NUMBER = 2不符合break条件,所以第一柜台第二次执行进入sleep状态, 就在这时可能第二柜台执行完毕了,CURRENT_NUMBER变成了2,紧接着第三柜台执行完毕CURRENT_NUMBER变成了3,这时不管哪个柜台再进入循环都符合break程序结束条件。但是就在马上要执行break的时候, 第一柜台的第二次执行也执行完毕了,输出了“一号柜台办理完毕4号顾客业务”这句话,所以CURRENT_NUMBER = 4。刚好输出完这句话,break执行了,程序彻底结束。
情景功能实现测试二:加锁,锁定类的当前实例
package com.thread.demo;
/**
* @Author: Peacock__
* @Date: 2019/4/25 14:36
*/
public class BankThread implements Runnable {
//当前排号:当前办理的排号
private Integer CURRENT_NUMBER = 1;
//最大排号:为了更好的展现效果,调整最多办理30个顾客的业务再去开集体临时会议了
private final static Integer MAX_NUMBER = 30;
@Override
public void run() {
//this为BankThread类的当前实例
synchronized (this) {
while (true) {
//当前排号大于最大排号时,任务执行完毕
if (CURRENT_NUMBER > MAX_NUMBER) {
break;
}
//休眠5毫秒,模拟正在办理业务
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//控制台输出,并将当前排号+1,模拟叫下一位顾客
System.out.println(Thread.currentThread().getName() + "办理完毕" + (CURRENT_NUMBER++) + "号顾客业务");
}
}
}
}
测试类还是用上面测试的测试类,没有任何改动,这时我们再运行测试,运行结果如下:
由测试结果可见,一号柜台最先抢占到资源,按顺序完成了业务办理,但是还是存在问题,那就是第二柜台和第三柜台根本就没干活呀,这怎么能行呢?之所以出现这个问题的原因是我们在测试类中new Thread的时候,都是通过同一个BankThread的实例来创建的,所以第一柜台、第二柜台、第三柜台线程都是通过同一BankThread的实例来访问run方法。刚好我们加run方法里的锁是锁定BankThread的当前实例的,所以当第一柜台进来后就持有了BankThread的当前实例的锁,第二柜台、第三柜台进来run方法后就需要一直等待获取BankThread的当前实例的锁。当第一柜台执行完毕后释放了BankThread的当前实例的锁,但是这个时候不管哪个柜台在持有BankThread的当前实例的锁,进入while后,都满足break条件退出循环,程序结束。
情景功能实现测试三:加锁,锁定代码块
package com.thread.demo;
/**
* @Author: Peacock__
* @Date: 2019/4/25 14:36
*/
public class BankThread implements Runnable {
//当前排号:当前办理的排号
private Integer CURRENT_NUMBER = 1;
//最大排号:为了更好的展现效果,调整最多办理500个顾客的业务再去开集体临时会议了
private final static Integer MAX_NUMBER = 500;
//定义私有唯一对象
private final static Object LOCK = new Object();
@Override
public void run() {
//此部分不加锁
while (true) {
//do_job()中加锁
if (do_job()) {
break;
}
}
}
private boolean do_job() {
//锁定LOCK对象,而非当前类的实例
synchronized (LOCK) {
//当前排号大于最大排号时,任务执行完毕
if (CURRENT_NUMBER > MAX_NUMBER) {
return true;
}
//休眠5毫秒,模拟正在办理业务
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//控制台输出,并将当前排号+1,模拟叫下一位顾客
System.out.println(Thread.currentThread().getName() + "办理完毕" + (CURRENT_NUMBER++) + "号顾客业务");
return false;
}
}
}
测试类还是用上面测试的测试类,没有任何改动,这时我们再运行测试,运行结果如下:
由运行结果可知,一号、二号、三号柜台交替办理完了500个顾客的业务,正是我们想要的效果。原因是我们锁定的是LOCK对象而并非BankThread类的当前实例,一号、二号、三号柜台都能够进入run方法,并进入while循环,在while循环中进入do_job方法,进入do_job后会持有LOCK的锁,执行完一次do_job方法后,就会释放LOCK的锁,由其他线程抢占接替持有LOCK的锁。直至do_job返回false,满足break条件退出循环,程序结束。
最后我们模拟一下synchronized修饰静态方法,验证一下锁定的是否为当前类的所有实例。
package com.thread.demo;
/**
* @Author: Peacock__
* @Date: 2019/4/25 17:45
*/
public class StaticDemo {
public synchronized static void s1() {
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---s1执行完毕");
}
public synchronized static void s2() {
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---s2执行完毕");
}
public static void s3() {
System.out.println(Thread.currentThread().getName()+"---s3执行完毕");
}
}
package com.thread.demo;
/**
* @Author: Peacock__
* @Date: 2019/4/25 14:39
*/
public class BankTest {
public static void main(String[] args) {
new Thread("t1"){
@Override
public void run() {
StaticDemo.s1();
}
}.start();
new Thread("t2"){
@Override
public void run() {
StaticDemo.s2();
}
}.start();
new Thread("t3"){
@Override
public void run() {
StaticDemo.s3();
}
}.start();
}
}
跑测试类输出如图:
根据输出结果的描述,验证了synchronized修饰静态方法锁定的是当前类的所有实例
synchronized具有可重入性
当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。synchronized是可重入锁。
假如objectA中有两个synchronized修饰的方法a、b,且在a方法中调用了b方法
这里的重入性是指当前线程获取到某对象(objectA)锁后,先执行a方法,在a中调用b方法的时候,由于b方法也被synchronized修饰。所以还需要获取objectA的对象锁,由于此时objectA的对象锁的持有者是自己,所以当前线程可以无需等待获取锁,而是可以直接正常执行b方法。需要特别注意另外一种情况,当子类继承父类时,子类也是可以通过可重入锁调用父类的同步方法。
package com.thread.demo;
/**
* @Author: Peakcock__
* @Date: 2019/4/28 14:24
*/
public class SynchronizedDemo implements Runnable{
private int x;
private int y;
public synchronized void a() {
for(int i = 0 ; i < 5 ; i ++){
x++;
b();
}
}
public synchronized void b() {
y++;
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
Thread t = new Thread(demo);
t.start();
}
@Override
public void run() {
a();
System.out.println(x +"----"+y);//输出结果:5----5
}
}
处于阻塞状态的线程即使调用了中断方法也不会生效(Thread.interrupt()
无法中断非阻塞状态下的线程)
对于synchronized来说,如果一个线程正在等待锁,结果只有两种,要么获得锁,要么继续等待,处于阻塞状态的线程即使调用了中断方法也不会被中断,但是会抛出InterrupterException异常,同时会立即复位(中断状态改为非中断状态)。
package com.thread.demo;
import java.util.concurrent.TimeUnit;
/**
* @Author: Peakcock__
* @Date: 2019/4/28 14:24
*/
public class SynchronizedDemo implements Runnable{
@Override
public void run() {
try{
while(true){
//休眠2秒
TimeUnit.SECONDS.sleep(2);
}
}catch(Exception e){
//捕获异常,查看中断状态
System.out.println("当前线程是否被打断:"+Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) {
Thread t = new Thread(new SynchronizedDemo());
t.start();
//中断线程
t.interrupt();
}
}
输出结果,验证了我们上面的描述
好,那既然我们已经验证了“处于阻塞状态的线程无法被中断”,那是不是意味着非阻塞的线程就可以被中断呢?我们来试一下:
package com.thread.demo;
import java.util.concurrent.TimeUnit;
/**
* @Author: Peakcock__
* @Date: 2019/4/28 14:24
*/
public class SynchronizedDemo implements Runnable{
@Override
public void run() {
try{
while(true){
System.out.println("线程未被打断");
}
}catch(Exception e){
//捕获异常,查看中断状态
System.out.println("当前线程是否被打断:"+Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) {
Thread t = new Thread(new SynchronizedDemo());
t.start();
//中断线程
t.interrupt();
}
}
但是发现输出结果是无限输出“线程未被打断”,额....................那到底怎样才能中断线程呢??????
原来是处于非阻塞状态的线程,我们需要手动进行中断检测后才能被中断,如下:
package com.thread.demo;
import java.util.concurrent.TimeUnit;
/**
* @Author: Peakcock__
* @Date: 2019/4/28 14:24
*/
public class SynchronizedDemo implements Runnable{
@Override
public void run() {
try{
while(true){
//中断检测
if(Thread.currentThread().isInterrupted()){
System.out.println("线程被打断");
break;
}
System.out.println("线程未被打断");
}
}catch(Exception e){
//捕获异常,查看中断状态
System.out.println("当前线程是否被打断:"+Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new SynchronizedDemo());
t.start();
//休眠1秒
TimeUnit.MILLISECONDS.sleep(1);
//中断线程
t.interrupt();
}
}
输出结果:
OK,输出结果已经验证“处于非阻塞状态的线程,我们需要手动进行中断检测后才能被中断”。
Ending~~~~~~~~~
哈哈^_^今天又是收获满满的一天(^-^)V
推荐阅读
-
synchronized关键字
-
Sqlserver 动态sql语句 和 use 关键字
-
对Pretected关键字的一个错误理解总结
-
@synchronized详解
-
sql DISTINCT 关键字去掉重复的列 博客分类: Database数据库相关 sql关键字DISTINCTsql distinct
-
SQL关联查询————LEFT JOIN关键字的使用
-
第八十六章 参数关键字 - Abstract
-
7-2 字符串关键字的散列映射 (25 分)C语言实现
-
Python函数:函数的定义语法、调用、参数类型(必选参数、缺省参数、可选参数、关键字可选参数)、return返回值、函数嵌套
-
java基础_面向对象_static关键字