一、线程同步与锁
同步和锁的概念,主要是为了解决多线程共享资源的问题。当多个线程对共享资源进行竞争访问,导致运行结果与期望不符时,通常就需要利用线程同步和锁的机制来消除这种多线程的竞争访问导致的差异性。示例:
public class ThreadCompetitionTest { static int count=0; public static void main(String[] args) throws InterruptedException { long start=System.currentTimeMillis(); Thread t = new Thread(){ @Override public void run() { for (int i = 0; i <5000000 ; i++) { count++; } System.out.println("自定义线程:计算完成...,耗时"+(System.currentTimeMillis()-start)); } }; t.start(); for (int i = 0; i <5000000 ; i++) { count++; } System.out.println("主线程:计算完成....,耗时"+(System.currentTimeMillis()-start)); t.join(); System.out.println("count:"+count); } }
次示例代码的其中一次运行结果如下(每次运行的结果几乎都不相同)
主线程:计算完成....,耗时11 自定义线程:计算完成...,耗时13 count:9973996
次示例代码的运行结果并不是固定的一千万,而是每次都不一样。出现该现象的原因主要是这两个线程同时对共享资源count变量进行了修改,并且count++
并不是一个原子操作
。每次自增实际上是分为3个步骤:
-
获取count变量的当前值
-
将当前值加1
-
将加1后的值存储到count变量中
因为线程是并行执行的,因此这就可能出现问题。例如假设count变量当前值是0,主线程和自定义线程同时获取到这个值,主线程先完成自增的操作,将count变量的值设置为1。自定义线程随后完成自增的操作,因为自定义线程也是在0的基础上加1,然后将值赋值给count变量,最终导致实际上进行了两次自增操作,但实际上确只加了1。
我们可以通过同步代码块(synchronized block
)和锁
来解决上述代码中出现的问题,解决的办法是将主线程和自定义线程中的count++修改成:
synchronized (ThreadCompetitionTest.class) { count++; }
当然这只是多种同步锁解决方案的其中一种,这里我们先以 synchronized来解决。这样修改之后不论运行多少次,其结果都是像期望的那样是一千万。
二、竞态条件与临界区
多线程并不是造成上述问题的关键,关键在于多个线程访问了相同的资源(即共享资源),并且只有当这些线程中的一个或多个对这些共享资源做了写操作才可能导致出现问题,只要资源没有发生变化,多个线程读取相同的资源还是安全的。
当多个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件
。导致竞态条件发生的代码区称作临界区
。例如上面示例代码中的count++操作,就是一个临界区,它会产生竞态条件。
当多个线程同时访问同一个资源,并且其中的一个或者多个线程对这个资源进行了写操作,才会产生竞态条件
。多个线程同时读同一个资源不会产生竞态条件。因此,有时候我们可以通过创建不可变的共享对象来保证对象在线程间共享时不会被进行写操作导致修改,从而消除竞态条件。
三、线程安全
允许被多个线程同时执行的代码称作线程安全
的代码。也就是说,线程安全的代码对多个线程的访问顺序不敏感,不论多个线程以何种顺序进行访问其结果都不会发生改变,故线程安全的代码不包含竞态条件
。
一般情况下,局部基础类型变量和不会逃逸(通俗地讲,指不会被其他线程引用到)出当前线程的局部引用类型对象都是线程安全的。对于成员变量,如果两个线程可以同时更新同一个对象的同一个成员,那这个代码就非线程安全的。对于非局部引用变量,就算其内部被操作的是一个线程安全的对象实例引用,但是该非局部引用变量本身还是可能是非线程安全的,因为,其内部被操作的那个线程安全的对象引用可能会被改变而重新指向另一块内存地址。
线程控制逃逸规则
可以帮助你判断代码中对某些资源的访问是否是线程安全的。
- 如果一个资源的创建,使用,销毁都在同一个线程内完成,
- 且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。
资源可以是对象,数组,文件,数据库连接,套接字等等。Java中你无需主动销毁对象,所以“销毁”指不再有引用指向对象。
即使对象本身线程安全,但如果该对象中包含其他资源(文件,数据库连接),整个应用也许就不再是线程安全的了。比如2个线程都创建了只在各自线程上下文中使用的数据库连接,相对数据库连接来说,每个连接自身确实是线程安全的,但它们所连接到的同一个数据库实例也许就不是线程安全,例如:如果这两个线程都先查询某条记录X是否存在,如果不存在就插入X记录,如果恰好两个线程同时都查询到不存在X记录,那么将导致这两个线程分别插入了一条相同的记录。
类似的问题可能还会发生在文件操作或其他共享资源的操作上,因此,判断线程控制的对象是资源本身,还是仅仅到某个资源的引用很重要。
四、Synchronized锁使用
synchronized关键字最主要有以下四种使用方式:
- 实例方法同步
- 实例方法同步块
- 静态方法同步
- 静态方法同步块
在Java中,每一个对象实例有且仅有一个锁与之相关联,所以任何一个对象实例都可以作为一个锁。当线程要执行同步方法或同步代码块的时候,必须首先获得相应的锁才能进入执行,否则将转为阻塞等待状态,这种阻塞等待除了获得了相应的锁,不能通过中断等方式唤醒从而终止继续阻塞等待。在多个线程同时准备获得同一把锁的时候,最多只能有一个线程能够成功获得这把锁(也称对象监视器),执行完毕退出同步方法或同步代码块之后,释放相应的锁。
所以,同步锁是掌握在当前运行线程手里的。另外,静态方法同步/块对应的锁,实际上是Class对象的一个特定实例,而实例同步方法/块的锁,是相应的特定的对象实例。例如,实例同步方法和使用this作为锁的同步代码块,其锁就是当前调用执行该方法的对象实例。
后记
在查阅synchronized相关资料的时候,发现很多资料有提到“synchronized是不能被继承的” ,当我第一次看到这个结论的时候,编写了测试代码进行验证,但是却总是无法证实该观点,于是继续查证,最后在CSND找到如下解释:
楼主是第一种意思,其他人是第二种意思。所以,会出现该贴的尴尬讨论。
第一种理解方式:父类中有个synchronized方法,子类继承了父类,但子类没覆写该方法。通过子类实例来使用该方法时,按“synchronized不能被继承”,意思就为:该子类的该方法就变成了非synchronized方法。
第二种理解方式:synchronized并不属于方法定义的一部分,不能被继承。子类覆写了该方法,如果在覆写时不明确写上synchronized,那这个方法就不是synchronized。换句话说,虽然继承了,但是没把synchronized继承下来,也就意味着“synchronized不能被继承”。
我觉得“synchronized不能被继承”这句话,没把意思表述清楚。产生这种情况的原因,我推测是这样的:某前辈详细解释了以上2种意思,最后总结的时候,使用了“synchronized不能被继承”这句不太合适的话。某无知后辈转述前辈意思的时候,就直接用了“synchronized不能被继承”。结果一传十,十传百,这句话就传开了,也让初学者产生了迷惑。
在英文世界里,没有“synchronized不能被继承”的讨论。这也说明了点问题。
其实楼主的问题,可以记住这么句话:synchronized方法,一定要显示标明,它是不能隐式标明的。
中文里“一句话多种意思”的问题,真的是给学术界增加了不少麻烦!
原来我的理解是第一种方式,而这句话真正要说的是,当子类覆写了父类的同步方法时,synchronized必须显示的指定才会是同步方法,而不能通过从父类继承从而也成为同步方法,如果子类没有覆写父类的同步方法,通过子类实例调用该方法,其实使用的还是父类的方法,因此该方法仍然是synchronized。
相关推荐
Java 锁机制 Synchronized 是 Java 语言中的一种同步机制,用于解决多线程并发访问共享资源时可能出现的一些问题。 Java 锁机制 Synchronized 的概念 在 Java 中,每个对象都可以被看作是一个大房子,其中有多个...
在Java 5之后,引入了`java.util.concurrent`包,其中的`ReentrantLock`类提供了可重入锁,它具有与`synchronized`相似的功能,但更加灵活,支持公平锁、非公平锁以及可中断的锁等待。 **5. synchronized的应用示例...
【Java同步之synchronized解析】 Java中的`synchronized`关键字是实现多线程同步的重要机制,它确保了在并发环境中对共享资源的访问是线程安全的。以下是对`synchronized`的详细解读: ### 1. synchronized的特性 ...
Java中的同步锁,即`synchronized`关键字,是Java多线程编程中用于解决并发问题的重要机制。它确保了对共享资源的互斥访问,防止数据的不一致性。当我们有多线程环境并涉及到共享数据时,可能会出现竞态条件,就像...
### Java同步锁原理 在Java中,`synchronized`关键字用于实现线程同步,即确保同一时刻只有一个线程可以访问特定的代码块或方法。这种机制通过内部维护一个锁来实现,每个对象都有一个内置锁,这个锁可以被任何拥有...
文件名`java_demo_synchronized`可能包含的是关于Java同步锁的示例代码,可以从中学习如何在实际项目中应用同步锁策略。通过深入理解同步锁的工作原理和优化技巧,我们可以构建出高效、高并发的Spring Boot应用程序...
总结来说,Java中的`synchronized`关键字是实现线程同步的关键,它通过互斥锁确保对共享资源的访问是线程安全的。在多线程编程中,合理使用`synchronized`可以有效避免竞态条件,保证程序的正确性和稳定性。对于...
Synchronized 是 Java 语言中用于解决多线程共享数据同步问题的关键字。它可以作为函数的修饰符,也可以作为函数内的语句,用于实现同步方法和同步语句块。在 Java 中,synchronized 关键字可以作用于 instance 变量...
- **同步锁的选择**:使用了一个名为`threadCounterLock`的静态变量作为锁对象,确保每次只有一个线程能进入`synchronized`代码块。 - **循环和休眠处理**:在`run`方法中,线程会持续运行直到达到预设的时间点(30...
"基于Java synchronized同步锁实现线程交互" Java多线程能够提高CPU利用效率,但也容易造成线程不安全、线程死锁等问题。Java synchronized同步锁可以保证同一时刻只有一个线程操作同一资源,使用wait()、notify()...
在Java编程语言中,`synchronized`关键字是一个非常重要的同步机制,用于控制多线程对共享资源的访问,以防止数据不一致性和竞态条件。本文将深入探讨`synchronized`关键字的用法,包括类锁、对象锁、方法锁以及它们...
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
Java 线程同步机制中 synchronized 关键字的理解 Java 的线程同步机制是为了解决多个线程共享同一片存储空间所带来的访问冲突问题。其中,synchronized 关键字是 Java 语言中解决这种冲突的重要机制。 ...
Java 同步锁是Java多线程编程中的关键概念,用于确保多个线程在访问共享资源时能够有序、安全地进行。在这个主题中,我们将详细探讨Java中的同步机制,包括同步方法、同步代码块、synchronized关键字以及其背后的...
在Java编程语言中,`synchronized`关键字是一个至关重要的概念,它主要用于实现线程同步,以确保多线程环境下的数据一致性与安全性。本篇文章将深入探讨`synchronized`的使用,包括其基本原理、使用方式以及实际应用...
为了解决这一问题,Java提供了更细粒度的锁,如`java.util.concurrent.locks.ReentrantLock`,它具有与`synchronized`相似的功能,但提供了更多的灵活性,如可中断的等待、定时等待和尝试获取锁。 此外,`java.util...
2. 每个对象只有一个锁与之相关联。 3. 实现同步可能会产生系统开销,甚至可能造成死锁,因此应尽量避免无谓的同步控制。 synchronized 关键字是 Java 中解决多线程并发问题的重要工具之一。正确地使用 ...
在Java编程中,synchronized关键字是用于同步的关键字,它可以用于锁住一个对象,以实现线程同步。但是,在使用synchronized关键字时,需要了解锁住的到底是哪个对象,否则可能出现问题。 在Java中,synchronized...
Java中的`synchronized`关键字是多线程编程中的一个重要概念,用于控制并发访问共享资源,以保证数据的一致性和完整性。这个关键词提供了互斥锁机制,防止多个线程同时执行同一段代码,确保了线程安全。 一、`...