Java锁机制(一)synchronized

进行多线程编程的时候,需要考虑的是线程间的同步问题。对于共享的资源,需要进行互斥的访问。在Java中可以使用一些手段来达到线程同步的目的:

1. synchronized 

2. ThreadLocal,线程本地变量

3. Java.util.concurrent.Lock

Java中,线程会共享堆上的实例变量以及方法区的类变量,而栈上的数据是私有的,不必进行保护。synchronized方法或synchronized块将标记一块监视区域,线程在进入该区域时,需要获得对象锁或类锁,JVM将自动上锁。synchronized提供了两种主要特性:

1. 互斥。互斥是指一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的并发访问,保证一次只有一个线程能够使用该共享数据。

2.可见性。确保释放锁之前对共享数据做出的更改对随后获得该锁的另一个线程是可见的。如果不能保证可见性,也就无法保证数据正确性,这将引发严重问题。volitail关键字同样保证了这种可见性。

在这里,我们将探讨synchronized使用时的三种情况:

1. 在对象上使用synchronized

2. 在普通成员方法上使用synchronized

3. 在静态成员方法上使用synchronized

这三种线程同步的表现有何不同?

下面通过三段示例代码来演示这三种情况。这里模拟线程报数的场景。

情况一:在普通成员函数上使用synchronized

public class MyThread extends Thread {

    public static void main(String[] args) throws Exception {
        for (int i = 1; i < 100; i++) {
            MyThread t  = new MyThread();
            t.setName("Thread="+i);
            t.start();
            Thread.sleep(100);
        }
    }

    @Override
    public synchronized void run() {
        for (int i = 1; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

对一个成员函数使用synchronized进行加锁,所获取的锁,是方法所在对象本身的对象锁。在这里,每个线程都以自身的对象作为对象锁,要对线程进行同步,要求锁对象必须唯一,所以这里多个线程间同步失败。

情况二:在对象上使用synchronized

这里在类中增加一个成员变量lock,在该变量上使用synchronized:

public class MyThread1 extends Thread {

    private String lock;

    public MyThread1(String lock) {
        this.lock = lock;
    }

    public static void main(String[] args) throws Exception {
        String lock = new String("lock");
        for (int i = 1; i < 100; i++) {
            Thread t = new MyThread1(lock);
            t.setName("Thread=" + i);
            t.start();
            Thread.sleep(100);
        }
    }

    @Override
    public void run() {
        synchronized (lock) {
            for (int i = 1; i < 10000; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

100个线程在创建的时候,都传递了同一个lock对象(在main中创建的)去初始化线程类成员lock,因此,这100个线程都在同一个lock对象上进行synchronized同步。因此线程同步成功。

情况三:在静态成员函数上使用synchronized

public class MyThread2 extends Thread {


    public static void main(String[] args) throws Exception {

        for (int i = 1; i < 10; i++) {
            Thread t = new MyThread2();
            t.setName("Thread=" + i);
            t.start();
            Thread.sleep(10);
        }
    }

    public static synchronized void func() {
        for (int i = 1; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }

    @Override
    public void run() {
        func();
    }
}

这种情况下,线程获得的锁是对象锁,而对象锁是唯一的,因此多个进程间也能同步成功。

补充:

1. 慎用字符串常量做同步对象,因为JVM内部会把常量字符串转换成同一个对象,同理的,基本数据除了Float和Double外,也有缓存对象[-128,127].

2. synchronized方法继承问题:1. 子类会继承父类的synchronized方法。2. 如果子类重写了父类的synchronized方法,必须也加上synchronized关键字,否则子类中的方法将变成非同步的。 3. 同一个子类对象中,子类的synchronized方法父类的synchronized方法使用的是同一个临界区。 

(完)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

问题解决: java.util.ConcurrentModificationException问题描述原因分析解决办法小结

在H5性能测试平台系统的开发过程中,客户端调用服务端API,写入性能数据的时候,报了如下错误:

552
来自专栏happyJared

设计模式入门:单例模式

  单例模式属于创建型模式,是一种较为简单的设计模式,但也是最容易让人犯错的。在不同的单例模式实现中,首先要确保构造函数是私有的,然后提供一个静态入口(方法)用...

652
来自专栏Java面试笔试题

Spring中Bean的作用域有哪些?

在Spring的早期版本中,仅有两个作用域:singleton和prototype,前者表示Bean以单例的方式存在;后者表示每次从容器中调用Bean时,都会返...

762
来自专栏Python、Flask、Django

Python中我刚知道的秘密

851
来自专栏青枫的专栏

在不是Thread类的子类中,如何获取线程对象的名称呢?

我想要获取main方法所在的线程对象的名称,该怎么办呢?   遇到这种情况,Thread类就提供了一个很好玩的方法:     public static Thr...

371
来自专栏xingoo, 一个梦想做发明家的程序员

【Spring实战】—— 3 使用facotry-method创建单例Bean总结

如果有这样的需求:   1 不想再bean.xml加载的时候实例化bean,而是想把加载bean.xml与实例化对象分离。   2 实现单例的bean ...

1815
来自专栏编程坑太多

Lambda表达式概述

903
来自专栏JAVA高级架构

Java设计模式--单例模式

2054
来自专栏一个会写诗的程序员的博客

13.13 java.util.ConcurrentModificationException13.13 java.util.ConcurrentModificationException问题描述原因

在H5性能测试平台系统的开发过程中,客户端调用服务端API,写入性能数据的时候,报了如下错误:

703
来自专栏青枫的专栏

同步解决线程安全问题的三种实现

541

扫码关注云+社区