利用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();
 }
 }
}

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

(0)

相关推荐

  • Java线程之线程同步synchronized和volatile详解

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程共享的,一个线程改变了其成员变量num值,下一个线程正巧读到了修改后的num,所以会递增输出. 要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现.拿上篇博文中的例子来说明,在多个线程之间共享了Count类的

  • Java使用synchronized修饰方法来同步线程的实例演示

    Java中可以使用关键字synchronized进行线程同步控制,实现关键资源顺序访问,避免由于多线程并发执行导致的数据不一致性等问题.synchronized的原理是对象监视器(锁),只有获取到监视器的线程才能继续执行,否则线程会等待获取监视器.Java中每个对象或者类都有一把锁与之相关联,对于对象来说,监视的是这个对象的实例变量,对于类来说,监视的是类变量(一个类本身是类Class的对象,所以与类关联的锁也是对象锁).synchronized关键字使用方式有两种:synchronized方法

  • Java多线程synchronized同步方法详解

    1.synchronized 方法与锁对象 线程锁的是对象. 1)A线程先持有 object 对象的 Lock 锁, B线程可以以异步的方式调用 object 对象中的非 synchronized 类型的方法 2)A线程先持有 object 对象的 Lock 锁, B线程如果在这时调用 object 对象中的 synchronized 类型的方法,则需要等待,也就是同步. 2.脏读(DirtyRead) 示例: public class DirtyReadTest { public static

  • java多线程编程之Synchronized块同步方法

    文章分享了4个例子对synchronized的详细解释 1.是否加synchronized关键字的不同 public class ThreadTest { public static void main(String[] args) { Example example = new Example(); Thread t1 = new Thread1(example); Thread t2 = new Thread1(example); t1.start(); t2.start(); } } cl

  • Java多线程编程中synchronized线程同步的教程

    0.关于线程同步 (1)为什么需要同步多线程? 线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源.我们采用Java中的同步代码块和同步方法达到这样的目的.比如这样的解决多线程无固定序执行的问题: public class TwoThreadTest { public static void main(String[] args) { Thread th1= new MyThread1(); Thread th2= new MyThread2(); th1.st

  • 浅析Java多线程同步synchronized

    单线程是安全的,因为线程只有一个,不存在多个线程抢夺同一个资源 代码例子: public class SingleThread { int num=10; public void add(){ while(num<13){ num++; try{ Thread.sleep(1000); } catch(Exception e){ System.out.println("中断"); } System.out.println(num); } } public static void

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

    一.前期基础知识储备 (1)线程同步的定义:多线程之间的同步. (2)多线程同步原因:一个多线程的程序如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,由此引出资源的同步问题,即当多个线程要操作同一资源时,有可能出现错误. (3)实现多线程同步的方式--引入同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问.--这样做的结果,所有线程间会有资源竞争,但是所有竞争的资源是同步的,刷新的,动态的,不会因为线程间的竞争,导致资源"过

  • Java实现线程通信的案例讲解

    什么是线程通信.如何实现? 所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现. 线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做. 线程通信常见模型 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费数据. 要求:生产者线程生产完数据后,唤醒消费者,然后等待自己;消费者消费完该数据后,唤醒生产者,然后等待自己 public class 多线程_5线程通信 extends Thread{ public static void main(Str

  • Java之线程编程的4种方法实现案例讲解

    1.继承Thread public class T4 { public static void main(String[] args) { System.out.println(Thread.currentThread()); Thread t1 = new A1(); t1.start(); } } class A1 extends Thread{ @Override public void run() { for(int i=0;i<10;i++) { System.out.println(

  • C语言细致讲解线程同步的集中方式

    目录 互斥锁 条件变量 信号量 读写锁 互斥锁 使用互斥量完成对临界区的资源的加锁操作,使得同一时刻,对一个共享数据的使用只能又一个线程完成 例向屏幕上一次打印abcd四个字母 可以使用的是一个类似锁连的思想 a 加完解开后拿b锁依次类推 #define THRNUM 4 static pthread_mutex_t mut[4]; static int next(int n) { if(n + 1 == THRNUM) return 0; return n+1; } static void*

  • java volatile案例讲解

    本篇来自java并发编程实战关于volatile的总结. 要说volatile,先得明白内存可见性.那我们就从内存可见性说起. 一.内存可见性 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.在单线程环境中,如果向某个变量先写入值,然后在没有其他写入操作的情况下读取这个变量,那么总能得到相同的值.这看起来很自然.然而,当读操作和写操作在不同的线程中执行时,情况却并非如此,这听起来或许有些难以接受.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能

  • Java之ThreadLocal使用常见和方式案例讲解

    目录 1 两大使用场景-ThreadLocal的用途 2 典型场景1:每个线程需要一个独享的对象 3 典型场景2:当前用户信息需要被线程内所有方法共享 4 ThreadLocal方法使用总结 5 ThreadLocal原理 6 ThreadLocal使用问题内存泄露 7 实际应用场景-在spring中的实例分析 [面试高频]- ThreadLocal的使用场景以及使用方式是怎么样的 1 两大使用场景-ThreadLocal的用途 典型场景1:每个线程需要一个独享的对象(通常是工具类,典型需要使用

  • 深入解析Java的线程同步以及线程间通信

    Java线程同步 当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用.达到此目的的过程叫做同步(synchronization).像你所看到的,Java为此提供了独特的,语言水平上的支持. 同步的关键是管程(也叫信号量semaphore)的概念.管程是一个互斥独占锁定的对象,或称互斥体(mutex).在给定的时间,仅有一个线程可以获得管程.当一个线程需要锁定,它必须进入管程.所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程.这些其他的线程被

  • Java多线程 线程同步与死锁

     Java多线程 线程同步与死锁 1.线程同步 多线程引发的安全问题 一个非常经典的案例,银行取钱的问题.假如你有一张银行卡,里面有5000块钱,然后你去银行取款2000块钱.正在你取钱的时候,取款机正要从你的5000余额中减去2000的时候,你的老婆正巧也在用银行卡对应的存折取钱,由于取款机还没有把你的2000块钱扣除,银行查到存折里的余额还剩5000块钱,准备减去2000.这时,有趣的事情发生了,你和你的老婆从同一个账户共取走了4000元,但是账户最后还剩下3000元. 使用代码模拟下取款过

随机推荐