前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发-3.synchronized关键字

Java并发-3.synchronized关键字

作者头像
悠扬前奏
发布2019-05-28 12:37:05
3480
发布2019-05-28 12:37:05
举报

Java中synchronized关键字作用是实现线程间的同步。它对同步的代码加锁,使得每次只能有一个线程进入同步快,以此保证线程间的安全性。 它的用法主要包括:

  • 指定加锁对象:给只指定对象加锁,进入同步代码前要获得指定对象的锁。
  • 修饰一个代码块,被修饰的代码被称为同步语句块,其作用范围是大括号之间的代码,作用的对象是调用这个代码块的对象。
  • 修饰一个实例方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象(对实例加锁)。
  • 修饰一个静态方法,其作用范围是整个静态方法,作用对象是这个类的所有对象(对类加锁)。
  • 修饰一个类,其作用范围是括号中间的部分,作用对象是这个类的所有对象(对类加锁)。

以下例子,展现synchronized的作用:

0.多个线程对同一个变量进行访问,往往会出错

两个线程同时对一个类变量累加十万次,最后结果往往小于二十万次。因为两个线程对同一个类变量进行写入的时候,一个线程的结果会覆盖另外一个。

代码语言:javascript
复制
package temp;

public class AccountingVol implements Runnable{
    static AccountingVol instance = new AccountingVol();
    static volatile int i = 0;
    public static void increase() {
        i++;
    }
    
    @Override
    public void run() {
        for(int j = 0; j < 100000; j++) {
            increase();
        }
    }
    
    public static void main (String[] args) throws InterruptedException{
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}

以上代码执行结果(可能):

代码语言:javascript
复制
151018

1. 修饰代码块

1.1. synchronized修饰一个代码块时,其他访问该代码块的线程将会阻塞。

代码语言:javascript
复制
public class AccountingVol implements Runnable{
    static AccountingVol instance = new AccountingVol();
    static volatile int i = 0;
    public static void increase() {
        i++;
    }
    
    @Override
    public void run() {
        synchronized(this) {
            for(int j = 0; j < 100000; j++) {
                increase();
            }
            System.out.println(Thread.currentThread().getName()+ " complete! i = " + i );
        }
    }
    
    public static void main (String[] args) throws InterruptedException{
        Thread t1 = new Thread(instance ,"A");
        Thread t2 = new Thread(instance, "B");
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}

以上代码会产生阻塞,等一个线程执行完后才会执行另一个,执行结果为:

代码语言:javascript
复制
/*
 * A complete! i = 100000
 * B complete! i = 200000
 * 200000
 */

1.2 synchronized只对代码块加锁,并未对代码块所在的对象加锁

当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

代码语言:javascript
复制
class Count implements Runnable {
    private int count;

    public Count() {
        count = 0;
    }

    public void countAdd() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
    public void printCount() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + " count:" + count);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.equals("A")) {
            countAdd();
        } else if (threadName.equals("B")) {
            printCount();
        }
    }

    // main函数略
}

以上代码执行结果为:

代码语言:javascript
复制
A:0
B count:1
B count:1
A:1
B count:2
A:2
B count:3
A:3
A:4
B count:5

2. 修饰实例方法/实例

修饰实例或者实例方法,会获取对实例的锁,每次只有一个线程对实例进行访问。 一下两种方法都能正确得到200000.

  • 修饰实例方法
代码语言:javascript
复制
public class AccountingVol implements Runnable {
    static AccountingVol instance = new AccountingVol();
    static volatile int i = 0;

    public synchronized void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            increase();
        }

    }   
    // main方法略
}
  • 修饰实例
代码语言:javascript
复制
public class AccountingVol implements Runnable {
    static AccountingVol instance = new AccountingVol();
    static volatile int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            synchronized(instance) {
                i++;
            }
        }
    }
    // main方法略
}

3. 修饰静态方法

静态方法是属于类的,因此synchronized 修饰静态方法会使多个调用该方法(调用该类资源)的线程使用同一把锁。

代码语言:javascript
复制
class Count implements Runnable {
      private static int count;

       public Count() {
          count = 0;
       }

       public synchronized static void method() {
          for (int i = 0; i < 5; i ++) {
             try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
             } catch (InterruptedException e) {
                e.printStackTrace();
             }
          }
       }

       public synchronized void run() {
          method();
       }

    // main函数略
}

以上代码执行结果为:

代码语言:javascript
复制
SyncThread2:0
SyncThread2:1
SyncThread2:2
SyncThread2:3
SyncThread2:4
SyncThread1:5
SyncThread1:6
SyncThread1:7
SyncThread1:8
SyncThread1:9

4. 修饰类

Synchronized还可修饰一个类T,类T的所有对象使用同一把锁:

代码语言:javascript
复制
class Count implements Runnable {
    private static int count;

    public Count() {
          count = 0;
       }

    public static void method() {
        synchronized (Count.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public synchronized void run() {
        method();
    }

    // main函数略
}

执行结果同3中例子一样。

5. Synchronized关键字原理

  • JVM基于进入和退出Monitor对象来实现方法同步和代码同步:
    • 代码块同步是使用monitorenter和monitorexit指令实现
    • monitorenter在编译后插入到同步块开始位置,monitorexit插入到方法结束和异常处,他们一定是相互配对的。
    • 任何对象都有一个monitor与之关联,当monitor被持有,就处于锁定状态。线程执行到monitorenter指令,就尝试获取对象对应的monitor所有权。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.01.19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0.多个线程对同一个变量进行访问,往往会出错
  • 1. 修饰代码块
    • 1.1. synchronized修饰一个代码块时,其他访问该代码块的线程将会阻塞。
      • 1.2 synchronized只对代码块加锁,并未对代码块所在的对象加锁
      • 2. 修饰实例方法/实例
      • 3. 修饰静态方法
      • 4. 修饰类
      • 5. Synchronized关键字原理
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档