利用synchronized实现线程同步的案例讲解

 更新时间:2021年02月20日 10:05:36   作者:Chin_style  
这篇文章主要介绍了利用synchronized实现线程同步的案例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

一、前期基础知识储备

(1)线程同步的定义:多线程之间的同步。

(2)多线程同步原因:一个多线程的程序如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,由此引出资源的同步问题,即当多个线程要操作同一资源时,有可能出现错误。

(3)实现多线程同步的方式——引入同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问。——这样做的结果,所有线程间会有资源竞争,但是所有竞争的资源是同步的,刷新的,动态的,不会因为线程间的竞争,导致资源“过度消耗”或者“虚拟消耗”。

上代码,具体展示“过度消耗/虚拟消耗”问题:

public class TestTicketRunnable{
  public static void main(String[] a){
    TicketThread tThread = new TicketThread();
    new Thread(tThread).start();
    new Thread(tThread).start();
    new Thread(tThread).start();
  }
};
class TicketThread implements Runnable {
  private int ticket = 5;
  public void run(){
    for (int i = 0; i < 5; i++){
      if (ticket > 0){
        try {
          Thread.sleep(300);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
      }
    }
  }
};

运行结果:

Thread-0卖票:ticket = 5
Thread-2卖票:ticket = 5 //虚拟消耗
Thread-1卖票:ticket = 4
Thread-1卖票:ticket = 2
Thread-2卖票:ticket = 3
Thread-0卖票:ticket = 3 //虚拟消耗
Thread-0卖票:ticket = -1 //过度消耗
Thread-1卖票:ticket = 1
Thread-2卖票:ticket = 0

如上所见,票一共5张,三个线程调用买票,线程1网上卖了售票第1张,紧接着线程2线下也卖了“第一张”出现了“虚拟消耗”的问题;线程3黄牛党卖了最后1张票,线程1网上又卖了最后1张,出现了“过度消耗”的问题,这两种问题都是实际生活中不可能发生的,但是在这个3个线程执行中却出现了,那一定是有问题的,问题的根源就在于,三个渠道的“售票员”不在一个频道上办事,或者说没有相互之间同步所共享的资源,导致这一问题的根本原因,就是相互之间实现方式不同步。

二、使用synchronized实现同步机制

synchronized关键字:Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。

当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

它包括两种用法:synchronized 方法和 synchronized 块。

即实现线程间同步的方式有两种:

①使用synchronized同步代码块;

②使用synchronized关键字创建synchronized()方法

下面分别进行解析,对上面售票的代码进行改造:

①代码——使用synchronized同步代码块

class TicketThread implements Runnable {
  private int ticket = 5;
  public void run(){
    for (int i = 0; i < 5; i++){
      synchronized(this){
        if (ticket > 0){
        try {
          Thread.sleep(300);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
      }
      }
    }
  }
}

②代码——使用synchronized关键字创建synchronized()方法

class TicketThreadMethod implements Runnable {
  private int ticket = 5;
  public void run(){
    for (int i = 0; i < 5; i++){
      this.sale();
    }
  }
  public synchronized void sale(){
      if (ticket > 0){
        try {
          Thread.sleep(300);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
      }
  }
}

三、synchronized方法和synchronized同步代码块的区别:

synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的。

synchronized方法是粗粒度的并发控制,某一个时刻只能有一个线程执行该synchronized方法。

synchronized同步代码块是细粒度的并发控制,只会将块中的代码同步,代码块之外的代码可以被其他线程同时访问。

补充:多线程同步锁synchronized(对象锁与全局锁)总结

1.synchronized同步锁的引入

/*
 * 非线程安全
 * */
//多个线程共同访问一个对象中的实例变量,则会出现"非线程安全"问题
class MyRunnable1 implements Runnable{
 private int num = 10;
 public void run() {
 try {
  if(num > 0) {
  System.out.println(""+Thread.currentThread().getName()+"开始"+",num= "+num--);
  Thread.sleep(1000);
  System.out.println(""+Thread.currentThread().getName()+"结束");
  }
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 }
}public class Test5_5{
 public static void main(String[] args) {
 MyRunnable1 myRunnable1 = new MyRunnable1();
 Thread thread1 = new Thread(myRunnable1,"线程1");
 Thread thread2 = new Thread(myRunnable1,"线程2");
 thread1.start();
 thread2.start();
 }
}

上例说明两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则会出现“线程不安全”问题。

由此我们引入synchronized关键字来实现同步问题:

在Java中使用synchronized关键字控制线程同步,控制synchronized代码段不被多个线程同时执行,synchronized即可以使用在方法上也可以使用在代码块中。

2. 对象锁

(1)synchronized方法(对当前对象进行加锁)

若我们对如上代码进行修改,在run()方法上加入synchronized关键字使其变为同步方法。

/*
 * 同步方法
 * */
class MyRunnable1 implements Runnable{
 private int num = 10;
 public void run() {
 this.print();
 }
 
 public synchronized void print() {
 if(this.num > 0) {
  System.out.println(""+Thread.currentThread().getName()+"开始"+",num= "+num--);
  try {
  Thread.sleep(1000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  System.out.println(""+Thread.currentThread().getName()+"结束");
 }
 }
}public class Test5_5{
 public static void main(String[] args) {
 MyRunnable1 myRunnable1 = new MyRunnable1();
 Thread thread1 = new Thread(myRunnable1,"线程1");
 Thread thread2 = new Thread(myRunnable1,"线程2");
 thread1.start();
 thread2.start();
 }
}  

结论:若两个线程同时访问同一个对象中的同步方法时一定是线程安全的。

(2)synchronized代码块(对某一个对象进行加锁)

如果要使用同步代码块必须设置一个要锁定的对象,所以一般可以锁定当前对象:this.

/*
 * 同步代码块
 * */
class MyRunnable1 implements Runnable{
 private int num = 10;
 public void run() {
 try {
  synchronized (this) {
  if(num > 0) {
   System.out.println(""+Thread.currentThread().getName()+"开始"+",num= "+num--);
   Thread.sleep(1000);
   System.out.println(""+Thread.currentThread().getName()+"结束");
  } 
  }
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 }
}
 
public class Test5_5{
 public static void main(String[] args) {
 MyRunnable1 myRunnable1 = new MyRunnable1();
 Thread thread1 = new Thread(myRunnable1,"线程1");
 Thread thread2 = new Thread(myRunnable1,"线程2");
 thread1.start();
 thread2.start();
 }
} 

(3)synchronized锁多对象

/*
 * synchronized锁多对象
 * */
class Sync{
 public synchronized void print() {
 System.out.println("print方法开始:"+Thread.currentThread().getName());
 try {
  Thread.sleep(1000);
 } catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
 System.out.println("print方法结束"+Thread.currentThread().getName());
 }
}
class MyThread extends Thread{
 public void run() {
 Sync sync = new Sync();
 sync.print();
 }
}
public class Test5_5{
 public static void main(String[] args) {
 for(int i = 0; i < 3;i++) {
  Thread thread = new MyThread();
  thread.start();
 }
 }
}

根据上例我们可以发现当synchronized锁多个对象时不能实现同步操作,由此可以得出关键字synchronized取得的锁都是对象锁,而不是将一段代码或者方法(函数)当作锁。哪个线程先执行带synchronized关键字的方法或synchronized代码块,哪个线程就有该方法或该代码块所持有的锁,其他线程只能呈现等待状态,前提是多个线程访问同一个对象。

只有共享资源的读写需要同步化,如果不是共享资源,那么就不需要同步化操作。

3.全局锁

实现全局锁有两种方式:

(1) 将synchronized关键字用在static方法上

synchronized加到static静态方法上是对Class类上锁,而synchronized加到非static方法上是给对对象上锁。Class锁可以对类的所有对象实例起作用。

/*
 * synchronized用在static方法上
 * */
class Sync{
 static public synchronized void print() {
 System.out.println("print方法开始:"+Thread.currentThread().getName());
 try {
  Thread.sleep(1000);
 } catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
 System.out.println("print方法结束"+Thread.currentThread().getName());
 }
}
class MyThread extends Thread{
 public void run() {
 Sync.print();
 }
}
public class Test5_5{
 public static void main(String[] args) {
 for(int i = 0; i < 3;i++) {
  Thread thread = new MyThread();
  thread.start();
 }
 }
}

(2) 用synchronized对类的Class对象进行上锁

synchronized(class)代码块的作用与synchronized static方法的作用一样。

/*
 * synchronized对类的Class对象上锁
 * */
class Sync{
 public void print() {
 synchronized (Sync.class) {
  System.out.println("print方法开始:"+Thread.currentThread().getName());
  try {
  Thread.sleep(1000);
  } catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
  }
  System.out.println("print方法结束"+Thread.currentThread().getName());
 }
 }
}
class MyThread extends Thread{
 public void run() {
 Sync sync = new Sync();
 sync.print();
 }
}
public class Test5_5{
 public static void main(String[] args) {
 for(int i = 0; i < 3;i++) {
  Thread thread = new MyThread();
  thread.start();
 }
 }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。如有错误或未考虑完全的地方,望不吝赐教。

相关文章

  • Springboot项目启动时如何用命令动态指定环境

    Springboot项目启动时如何用命令动态指定环境

    这篇文章主要介绍了Springboot项目启动时如何用命令动态指定环境的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java 中的static关键字和final关键字的不同之处

    java 中的static关键字和final关键字的不同之处

    java 中的static关键字和final关键字的不同之处,需要的朋友可以参考一下
    2013-03-03
  • java实现顺时针打印矩阵

    java实现顺时针打印矩阵

    这篇文章主要为大家详细介绍了java实现顺时针打印矩阵的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • Spring Security如何使用URL地址进行权限控制

    Spring Security如何使用URL地址进行权限控制

    这篇文章主要介绍了Spring Security如何使用URL地址进行权限控制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • MyBatis中#{}和${}的区别详解

    MyBatis中#{}和${}的区别详解

    mybatis和ibatis总体来讲都差不多的。下面小编给大家探讨下mybatis中#{}和${}的区别,感兴趣的朋友一起学习吧
    2016-08-08
  • jdk安装、Java环境配置方法详解

    jdk安装、Java环境配置方法详解

    这篇文章主要介绍了jdk安装、Java环境配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • 使用Java校验SQL语句的合法性五种解决方案

    使用Java校验SQL语句的合法性五种解决方案

    这篇文章主要介绍了如何用java校验SQL语句的合法性(提供五种解决方案),使用JDBC API和JSqlParser库、正则表达式、ANTLR解析器生成器或Apache Calcite库都可以实现校验SQL语句的合法性,需要的朋友可以参考下
    2023-04-04
  • SpringMVC的执行过程浅析

    SpringMVC的执行过程浅析

    这篇文章主要给大家介绍了关于SpringMVC的执行过程的相关资料,文中通过图文介绍的非常详细,对大家的学习或者使用SpringMVC具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • 详解如何判断Java线程池任务已执行完

    详解如何判断Java线程池任务已执行完

    线程池的使用并不复杂,麻烦的是如何判断线程池中的任务已经全部执行完了,所以接下来,我们就来看看如何判断线程中的任务是否已经全部执行完吧
    2023-08-08
  • Java集合List与Array的相互转换

    Java集合List与Array的相互转换

    本篇文章主要介绍了Java集合List与Array的相互转换,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02

最新评论