前言:
当前你们对“java中的同步方法”大体比较着重,大家都想要学习一些“java中的同步方法”的相关资讯。那么小编同时在网摘上汇集了一些关于“java中的同步方法””的相关文章,希望朋友们能喜欢,我们一起来学习一下吧!写在前面:
视频是什么东西,有看文档精彩吗?
视频是什么东西,有看文档速度快吗?
视频是什么东西,有看文档效率高吗?
1. 数据安全问题
诸小亮:多线程的使用虽然可以帮助我们提高效率,但是也可能造成问题
张小飞:是吗?什么样的问题?
诸小亮:数据安全问题
1. 前提
诸小亮:你还记得之前我们说了CPU切换线程的情况吗?
张小飞:当然了,CPU是不断的切换线程执行的,当CPU从 A 线程切换到 B 线程时,A 线程就会暂停执行
诸小亮:没错,那你看看下面这段代码,CPU可能让线程暂停到哪一行?
public void run() { System.out.println(name + "。。。。。"); System.out.println(name + "。。。。。"); System.out.println(name + "。。。。。");}
张小飞:上面 3 句代码,这里的每一句代码都有可能被CPU的调度打断,导致当前线程暂停执行
诸小亮:你说的不错,都有可能,具体看 CPU 心情
2. 多线程操作数据
诸小亮:接下来,我们就来看一下——多线程中可能出现的数据安全问题
张小飞:到底什么时候会产生问题呢?
诸小亮:当多个线程同时操作同一个数据时候,可能产生数据安全问题
class Hero implements Runnable{ //1. 定义一个静态变量,多个线程同时操作它 public static int num = 10; @Override public void run() { while (true){// while中用true,这是死循环,谨慎使用,这里是为了演示效果 //2. run方法中,对num--,当num<=0时,跳出循环 if(num > 0){ //sleep(5),让当前线程休眠5毫秒,此时CPU会执行其他线程 try { Thread.sleep(5); } catch (InterruptedException e) {} num--; System.out.println(Thread.currentThread().getName() + "***********" + num); }else{ break; } } }}public class ThreadDemo { public static void main(String[] args) { Hero hero = new Hero(); Thread yase = new Thread(hero, "yase"); Thread daji = new Thread(hero, "daji"); //3. 开启两个线程操作num yase.start(); daji.start(); }}
结果:
诸小亮:仔细看上面的代码,当 num>0 时,才会执行输出语句,但是却输出了负数
张小飞:是啊,为什么会出现负数?
诸小亮:我们来看一下代码的执行过程:
2个线程刚开始正常执行,都会执行num--。。。。。当 num=1 时,假如 'yase'先进入 先进入 if ,休眠5毫秒,这时 CPU 切换到线程‘daji’进入 if 语句 ‘yase’休眠结束,执行num--,接着‘daji’睡醒后也执行num--,最终num=-1
张小飞:原来是这样,如果不让这两个线程休眠,是不是可以避免这种情况?
诸小亮:即使没有 sleep 语句,也可能输出负数,只不过概率太低而已
张小飞:额。。。,我明白了,线程可能暂停到任意一句代码
诸小亮:是的,所以记住,一个线程在操作数据时,其他线程也参与运算,最终可能造成数据错误
张小飞:那么如何解决这种问题呢?
诸小亮:保证操作数据的代码在某一时间段内,只被一个线程执行,执行期间,其他线程不能参与
2. 线程同步
张小飞:怎么才能保证‘在某一时间段内,只被一个线程执行’呢?
诸小亮:那就要用到——线程同步
线程同步:就是让线程一个接一个的排队执行同步:是一个接一个的意思
张小飞:排队执行,有意思,怎么才能让线程排队?
诸小亮:Java中提供 synchronized 关键字用来实现同步,可以解决多线程时的数据安全问题
张小飞:原来这样的关键字,赶快告诉我怎么用啊
1. synchronized 代码块
诸小亮:synchronized 的使用非常简单,其格式:
synchronized(锁对象){ 需要同步代码块}
张小飞:‘锁对象’是什么东西?
诸小亮:可以理解为钥匙、通行证,只有线程拿到通行证后,才能执行 { } 中的代码
1. 演示
诸小亮:我们使用 synchronized 修改一下 run 方法:
public static Object obj = new Object();@Overridepublic void run() { while (true){ //使用同步代码块,把 obj 作为锁对象 synchronized (obj){ if(num > 0){ //sleep(5),让当前线程休眠5毫秒,此时CPU会执行其他线程 try { Thread.sleep(5); } catch (InterruptedException e) {} num--; System.out.println(Thread.currentThread().getName() + "***********" + num); }else{ break; } } }}
结果:
张小飞:可以具体解释一下吗?
诸小亮:当然可以
当线程执行到 synchronized (obj) 时,会获取尝试 obj 对象synchronized 保证多线程下,只有一个线程能获取到obj对象,而其他线程会阻塞(暂停)当多个线程同时执行到 synchronized (obj) 这句代码时,只有一个线程能够拿到obj,其他线程暂停当持有 obj 的线程从 synchronized 退出后,会释放 obj 对象,然后其他线程再次争夺 obj
诸小亮:这样就保证了 synchronized 中的代码在某一时刻只能被一个线程执行
张小飞:原来如此
2. 锁对象
张小飞:不过,synchronized 的锁对象都可以是什么?
诸小亮:好问题!在 synchronized 中,锁对象可以是任意对象,只要是引用类型的对象都可以作为锁对象
张小飞:那么如何选择合适的锁对象呢?
诸小亮:这就要根据实际情况去选择了,在之后我们会介绍两种常用的锁对象
张小飞:好的
3. 好处和弊端
诸小亮:刚才演示了 synchronized 的最用,你能总结一下它的好处和弊端吗?
张小飞:当然可以了
好处:解决多线程数据安全问题弊端:同步代码块同时只能被一个线程执行,降低效率
诸小亮:嗯嗯,还不错
4. synchronized的注意事项
诸小亮:使用 synchronized 需要注意,必须在多线程场景下使用
线程数>1,就是多线程
张小飞:明白
诸小亮:另外,多线程争抢的锁对象只能有一个,下图中的做法要坚决抵制
诸小亮:上图中的做法是会被开除的
张小飞:这是自然,确保锁对象的唯一性是确保 synchronized 正确性的前提
诸小亮:哟,没想到你总结的还挺到位
张小飞:那是自然,也不看是跟谁学习的
2. 同步方法
诸小亮:下面,我们来说说——同步方法
张小飞:是把 synchronized 放到方法上吗?
诸小亮:是的,把 synchronized 放到方法上,那么这个方法就是同步方法
1. 演示
诸小亮:同步方法,限制同一时刻只有一个线程可以执行这个方法,比如:
class Hero implements Runnable{ public static int num = 10; public static Object obj = new Object(); @Override public void run() { //run方法中调用同步方法 this.show(); } //同步方法 public synchronized void show(){ while (true){ if(num > 0){ try { Thread.sleep(5); } catch (InterruptedException e) {} num--; System.out.println(Thread.currentThread().getName() + "***********" + num); }else{ break; } } }}public class ThreadDemo { public static void main(String[] args) { Hero hero = new Hero(); Thread yase = new Thread(hero, "yase"); Thread daji = new Thread(hero, "daji"); yase.start(); daji.start(); }}
结果:
张小飞:上面的输出表示,只有 yase 线程进入了 show 方法吧
诸小亮:不错,yase线程进入show方法后,操作 num,然后退出方法,接着 daji 线程进入,发现num已经为0
所以直接退出
张小飞:不是说使用 synchronized,都需要锁对象吗,同步方法的锁对象是???
2. 同步方法的锁对象
诸小亮:同步代码块需要一个锁对象,但是同步方法并不需要,因为:同步方法的锁是this(当前对象)
张小飞:当前对象?
诸小亮:是的,下面的代码可以证明
class Hero implements Runnable{ public static Object obj = new Object(); @Override public void run(){ //1. 使用 this 作为锁对象 synchronized (this){ try { Thread.sleep(2000); } catch (InterruptedException e) {} System.out.println(Thread.currentThread().getName() + "。。。。。。。"+this); } } //2. 同步方法的锁是 this public synchronized void show(){ System.out.println(Thread.currentThread().getName() + "。。。。。。。"+this); }}public class ThreadDemo { public static void main(String[] args) throws Exception { Hero hero = new Hero(); //3. 启动 ‘亚瑟’ 线程,自动执行run方法 new Thread(hero,"亚瑟").start(); //4. 主线程休眠100毫秒后,执行show这个同步方法 Thread.sleep(100); hero.show(); }}
诸小亮:上面代码中,run方法中的 this 就是 hero对象,这个知道吧
张小飞:这时自然
诸小亮:好的,那我们运行一下,它的结果是
张小飞:看结果,应该是 ‘亚瑟’线程执行结束后,在执行的 main 线程中的 show 方法
诸小亮:不错,其具体过程是
亚瑟线程先执行,走到 Thread.sleep(2000) 时,休眠2秒,但这时它已经持有 this (也就是hero对象)然后main线程休眠100毫秒后,执行show方法尝试获取this,无法获取到,于是等待1.9秒所以最终结果。。。。。
张小飞:明白了
3. 静态同步方法
张小飞:synchronized 可以放到静态方法上吗
诸小亮:可以的,这时就是静态同步方法
张小飞:那么,静态同步方法的锁对象是???
诸小亮:锁对象是:Hero.class,也就是方法所在类的 class 对象
张小飞:什么是 class 对象?
诸小亮:Hero.class 也是对象,称为 Class 或字节码对象,是 JVM 把 class 文件加载到内存后创建一个对象
张小飞:这么说,每一个类都有自己的 class 对象了?
诸小亮:是的,我们修改上面的代码
class Hero implements Runnable{ public static Object obj = new Object(); @Override public void run(){ //1. 锁对象换成了Hero.class,也就是Hero字节码对象 synchronized (Hero.class){ try { Thread.sleep(2000); } catch (InterruptedException e) {} System.out.println(Thread.currentThread().getName() + "。。。。。。。"+Hero.class); } } //2. show方法改成static public static synchronized void show(){ System.out.println(Thread.currentThread().getName() + "。。。。。。。"+Hero.class); }}public class ThreadDemo { public static void main(String[] args) throws Exception { Hero hero = new Hero(); new Thread(hero,"亚瑟").start(); Thread.sleep(100); Hero.show(); }}
结果:
4. 死锁
诸小亮:在使用 synchronized 时,要特别注意一种情况——死锁
张小飞:死锁是什么意思?
诸小亮:其实就是两个线程互相影响,A 线程等 B 线程释放锁,同时 B 线程也在等 A 线程释放锁,比如:
张小飞:能写个代码具体看看吗?
诸小亮:代码我都准备好了,死锁——通常发生在同步嵌套
同步嵌套:synchronized 中 还有 synchronized
示例:
class Hero implements Runnable{ //1. 搞两个锁对象 A、B public static Object A = new Object(); public static Object B = new Object(); public boolean bool; Hero(boolean bool){ this.bool = bool; } @Override public void run(){ //2. 当 bool 是 true if(bool){ synchronized (A){//先获取 A 锁,然后睡一会儿,再获取 B 锁 System.out.println(Thread.currentThread().getName() + "------拿到A锁,准备获取B锁。。。。。。。"); try { Thread.sleep(200); } catch (InterruptedException e) {} synchronized (B){ System.out.println(Thread.currentThread().getName() + "。。。。。。。"); } } }else{//3. 当 bool 是 false synchronized (B){ //先获取B锁,睡一会儿,再获取A锁 System.out.println(Thread.currentThread().getName() + "------拿到B锁,准备获取A锁。。。。。。。"); try { Thread.sleep(200); } catch (InterruptedException e) {} synchronized (A){ System.out.println(Thread.currentThread().getName() + "。。。。。。。"); } } } }}public class ThreadDemo { public static void main(String[] args) throws Exception { new Thread(new Hero(true), "yase").start(); new Thread(new Hero(false), "daji").start(); }}
诸小亮:你自己研究一下这个代码吧
张小飞:好的
五分钟后。
张小飞:结果是这样子的
诸小亮:那你能详细解释一下原因吗?
张小飞:我试试吧,其本质是——死锁导致程序无法继续运行,也一直不结束,其过程:
假设 yase 线程先执行,在获取的 A 锁后,睡觉daji 线程接着执行,在获取 B 锁后,睡觉yase 线程睡醒后,尝试获取 B 锁,但是已经被 daji 线程拿到了,于是阻塞daji 线程睡醒后,尝试获取 A 锁,但是已经被 yase 线程拿到了,也阻塞yase 线程需要获取 B 锁后,执行完代码,再能释放 A 锁daji 线程需要获取 A 锁后,执行完代码,再能释放 B 锁最终两个线程都阻塞,程序卡死
诸小亮:嗯嗯,分析的还不错,一定要记住:
死锁是开发中的禁忌,绝对禁止出现,所以开发中尽量避免同步代码块嵌套使用
标签: #java中的同步方法