详解Java多线程编程中的线程同步方法

 更新时间:2016年05月10日 08:59:48   作者:arthinking  
这篇文章主要介绍了Java多线程编程中的线程同步方法,使用synchronized关键字创建线程同步方法是实现线程同步的关键,需要的朋友可以参考下

1、多线程的同步:
1.1、同步机制:
在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生。所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问。

1.2、共享成员变量的例子:
成员变量与局部变量:
成员变量:

如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的。

局部变量:

如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝。他们之间的局部变量互不影响。

下面举例说明:
实现了Runnable的线程类:

class MyThread3 implements Runnable{

 //两个线程操作同一个对象,共享成员变量
 //int i;
 @Override
 public void run() {
  //两个线程操作同一个对象,各自保存局部变量的拷贝
  int i = 0;
  while(i<100){
   System.out.println(i);
   i++;
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
}

在main方法中用两个线程操作同一个对象:

public static void main(String[] args) {

 MyThread3 myThread = new MyThread3();
 //下面两个线程对同一个对象(Runnable的实现类对象)进行操作
 Thread thread = new Thread(myThread);
 Thread thread2 = new Thread(myThread);
 //各自保存局部变量的拷贝,互不影响,输出200个数字
 thread.start();
 thread2.start();
}

这里如果把i变成成员变量,则输出100个数字。

1.3、共享资源导致的读取错误
下面举个例子,两个线程共用一个Number对象,通过Number类的getNumber方法获取数据,读取数据并改写时,发现了重复读操作:

首先创建一个Number类:

class Number{
 private int number = 10;
 public String getNumber(int i){
  if(number > 0){
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   number -= i;
   return "取出"+i+"成功,剩余数量:"+number;
  }
  return "取出"+i+"失败,剩余数量:"+number;
 }
}

线程类,在线程类中的私有属性包含了Number类的引用:

class MyThread4 extends Thread{

 //两个线程操作同一个对象,共享成员变量
 Number number;
 public MyThread4(Number number){
  this.number = number;
 }
 @Override
 public void run() {
  System.out.println(number.getNumber(8));
 }
}

在main函数中创建两个线程类,包含了同一个Number类实例的引用:

public static void main(String[] args) {

 Number number = new Number();
 //两个线程操作同一个对象,共享对象number的成员变量number
 MyThread4 myThread = new MyThread4(number);
 MyThread4 myThread2 = new MyThread4(number);
 myThread.start();
 myThread2.start();
}

这样,当第一个线程读取Number中的number变量时先保存下来再休眠0.1秒,然后第二个线程再读取number变量并保存,此时两个线程保存了同样的数字,在修改时,也就导致修改了同一个数字两次。

2、同步机制的实现:

在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了”
Java中的每一个对象都可以作为锁。

对于同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前对象的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
2.1、使用synchronized关键字创建synchronized方法:
使用synchronized关键字,该关键字修饰的方法叫做同步方法。

Java中每个对象都有一个锁或者称为监视器,当访问某个对象的synchronized方法时,表示将该对象上锁,而不仅仅是为该方法上锁。

这样如果一个对象的synchronized方法被某个线程执行时,其他线程无法访问该对象的任何synchronized方法(但是可以调用其他非synchronized的方法)。直至该synchronized方法执行完。

静态的synchronized方法调用情况:
当调用一个对象的静态synchronized方法时,它锁定的并不是synchronized方法所在的对象,而是synchronized方法所在对象对应的Class对象。这样,其他线程就不能调用该类的其他静态synchronized方法了,但是可以调用非静态的synchronized方法。

结论:执行静态synchronized方法锁方法所在对象,执行非静态synchronized方法锁方法所在对象对应的Class对象。

下面是多线程调用静态的方法的例子,由于锁定了方法所在对象对应的Class对象,其他线程无法调用该方法所在对象其他的静态synchronized方法:

/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
 //这时如果某个线程调用该方法,
 //将锁定synchronized方法所在对象对应的class对象,
 //而不是锁定synchronized方法所在对象
 public synchronized static void execute(){
  for(int i = 0; i<100; i++){
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   System.out.println("compute1:execute1 " + i++);
  }
 }
 public synchronized static void execute2(){
  for(int i = 0; i<100; i++){
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   System.out.println("compute1:execute2 " + i++);
  }
 }
}

main方法中两个线程分别调用同一个对象的两个static synchronized方法:

public static void main(String[] args) {
 Compute1 com = new Compute1();
 Thread thread1 = new Thread1(com);
 Thread thread2 = new Thread2(com);
 thread1.start();
 thread2.start();
}

一次只能调用一个静态方法,直到执行完成。

2.2、使用synchronized创建同步代码块:
通过使用synchronized同步代码块,锁定一个对象,该对象作为可执行的标志从而达到同步的效果:

/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
 //通过同步代码块锁定object1对象进行锁定了其他同样的synchronized代码块
 private Object object1 = new Object();
 public void execute(){
  synchronized(object1){
   for(int i = 0; i<100; i++){
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println("compute1:execute1 " + i++);
   }
  }

 }
 public synchronized void execute2(){
  synchronized(object1){
   for(int i = 0; i<100; i++){
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println("compute1:execute2 " + i++);
   }
  }
 }
}

如果想要使用synchronized同步代码块达到和使用synchronized方法同样的效果,可以锁定this引用:

synchronized(this){
 …
}

2.3、synchronized方法和synchronized同步代码块的区别:
synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的。

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

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

相关文章

  • SpringBoot整合MyBatis的代码详解

    SpringBoot整合MyBatis的代码详解

    这篇文章主要介绍了SpringBoot整合MyBatis笔记记录,大家需要注意在整合mybatis之前我们需要相对应的导入相关依赖,首先需要在java的目录和resources下创建mapper文件夹,对SpringBoot整合MyBatis的详细过程感兴趣的朋友一起看看吧
    2022-05-05
  • Kafka Java Producer代码实例详解

    Kafka Java Producer代码实例详解

    这篇文章主要介绍了Kafka Java Producer代码实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Spark学习笔记之Spark中的RDD的具体使用

    Spark学习笔记之Spark中的RDD的具体使用

    这篇文章主要介绍了Spark学习笔记之Spark中的RDD的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-06-06
  • spring XML配置文件标签详解

    spring XML配置文件标签详解

    这篇文章主要介绍了spring XML配置文件标签详解,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2024-12-12
  • Java容器ArrayList知识点总结

    Java容器ArrayList知识点总结

    本篇文章给大家分享了Java容器ArrayList的相关知识点,对此有需要的朋友可以跟着学习参考下。
    2018-05-05
  • Spring Boot 项目启动失败的解决方案

    Spring Boot 项目启动失败的解决方案

    这篇文章主要介绍了Spring Boot 项目启动失败的解决方案,帮助大家更好的理解和学习使用Spring Boot,感兴趣的朋友可以了解下
    2021-03-03
  • Spring中BeanFactory解析bean详解

    Spring中BeanFactory解析bean详解

    本篇文章主要介绍了Spring中BeanFactory解析bean详解 ,详细的介绍了使用BeanFactory对bean进行解析的实例,有兴趣的可以了解一下。
    2017-04-04
  • MyBatis 结果映射的几种实现方式

    MyBatis 结果映射的几种实现方式

    本文主要介绍了MyBatis中的结果映射机制,包括映射方式及其在处理关联关系中的高级应用,具有一定的参考价值,感兴趣的可以了解一下
    2025-09-09
  • Java中实现订单超时自动取消功能(最新推荐)

    Java中实现订单超时自动取消功能(最新推荐)

    本文介绍了Java中实现订单超时自动取消功能的几种方法,包括定时任务、JDK延迟队列、Redis过期监听、Redisson分布式延迟队列、RocketMQ延迟消息和RabbitMQ死信队列,每种方法都有其优缺点,可以根据具体需求选择合适的方法,感兴趣的朋友一起看看吧
    2025-02-02
  • springboot实现小程序支付的项目实践

    springboot实现小程序支付的项目实践

    本文主要介绍了springboot实现小程序支付的项目实践, 可以通过调用微信支付 API 实现支付功能,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09

最新评论