Java使用synchronized修饰方法来同步线程的实例演示
作者:柳哥 发布时间:2022-11-29 18:33:29
Java中可以使用关键字synchronized进行线程同步控制,实现关键资源顺序访问,避免由于多线程并发执行导致的数据不一致性等问题。synchronized的原理是对象监视器(锁),只有获取到监视器的线程才能继续执行,否则线程会等待获取监视器。Java中每个对象或者类都有一把锁与之相关联,对于对象来说,监视的是这个对象的实例变量,对于类来说,监视的是类变量(一个类本身是类Class的对象,所以与类关联的锁也是对象锁)。synchronized关键字使用方式有两种:synchronized方法和synchronized块。这两种监视区域都和一个引入对象相关联,当到达这个监视区域时,JVM就会锁住这个引用对象,当离开时会释放这个引用对象上的锁(有异常退出时,JVM会释放锁)。对象锁是JVM内部机制,只需要编写同步方法或者同步块即可,操作监视区域时JVM会自动获取锁或者释放锁。
示例1
先来看第一个示例,在java中,同一个对象的临界区,在同一时间只有一个允许被访问(都是非静态的synchronized方法):
package concurrency;
public class Main8 {
public static void main(String[] args) {
Account account = new Account();
account.setBalance(1000);
Company company = new Company(account);
Thread companyThread = new Thread(company);
Bank bank = new Bank(account);
Thread bankThread = new Thread(bank);
System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
companyThread.start();
bankThread.start();
try {
//join()方法等待这两个线程运行完成
companyThread.join();
bankThread.join();
System.out.printf("Account : Final Balance: %f\n", account.getBalance());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*帐户*/
class Account{
private double balance;
/*将传入的数据加到余额balance中*/
public synchronized void addAmount(double amount){
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp += amount;
balance = tmp;
}
/*将传入的数据从余额balance中扣除*/
public synchronized void subtractAmount(double amount){
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp -= amount;
balance = tmp;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
/*银行*/
class Bank implements Runnable{
private Account account;
public Bank(Account account){
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
account.subtractAmount(1000);
}
}
}
/*公司*/
class Company implements Runnable{
private Account account;
public Company(Account account){
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
account.addAmount(1000);
}
}
}
你已经开发了一个银行账户的模拟应用,它能够对余额进行充值和扣除。这个程序通过调用100次addAmount()方法对帐户进行充值,每次存入1000;然后通过调用100次subtractAmount()方法对帐户余额进行扣除,每次扣除1000;我们期望帐户的最终余额与起初余额相等,我们通过synchronized关键字实现了。
如果想查看共享数据的并发访问问题,只需要将addAmount()和subtractAmount()方法声明中的synchronized关键字删除即可。在没有synchronized关键字的情况下,打印出来的余额值并不一致。如果多次运行这个程序,你将获取不同的结果。因为JVM并不保证线程的执行顺序,所以每次运行的时候,线程将以不同的顺序读取并且修改帐户的余额,造成最终结果也是不一样的。
一个对象的方法采用synchronized关键字进行声明,只能被一个线程访问。如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。但如果线程B访问的是同一个类的不同对象,那么两个线程都不会被阻塞。
示例2
演示同一个对象上的静态synchronized方法与非静态synchronized方法可以在同一时间点被多个线程访问的问题,验证一下。
package concurrency;
public class Main8 {
public static void main(String[] args) {
Account account = new Account();
account.setBalance(1000);
Company company = new Company(account);
Thread companyThread = new Thread(company);
Bank bank = new Bank(account);
Thread bankThread = new Thread(bank);
System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
companyThread.start();
bankThread.start();
try {
//join()方法等待这两个线程运行完成
companyThread.join();
bankThread.join();
System.out.printf("Account : Final Balance: %f\n", account.getBalance());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*帐户*/
class Account{
/*这里改为静态变量*/
private static double balance = 0;
/*将传入的数据加到余额balance中,注意是用static修饰过的*/
public static synchronized void addAmount(double amount){
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp += amount;
balance = tmp;
}
/*将传入的数据从余额balance中扣除*/
public synchronized void subtractAmount(double amount){
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp -= amount;
balance = tmp;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
/*银行*/
class Bank implements Runnable{
private Account account;
public Bank(Account account){
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
account.subtractAmount(1000);
}
}
}
/*公司*/
class Company implements Runnable{
private Account account;
public Company(Account account){
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
account.addAmount(1000);
}
}
}
我只是把上个例子中的,balance加了static关键字修改,addAmount()方法也可以static关键字修饰。执行结果大家可以自己测试一下,每次执行都是不一样的结果!
一些总结:
synchronized是通过软件(JVM)实现的,简单易用,即使在JDK5之后有了Lock,仍然被广泛地使用。
synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,不过这种抢占的方式可以预防饥饿。
synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合解决了这个问题。
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。
猜你喜欢
- 目录一,功能二,工具三、效果图:四、数据库设计五、JAVA层次分析六、主要Java代码分析一,功能管理员登录图书借阅信息管理图书信息管理管理
- java引用传递的三种类型我这里使用了mldn视频里的例子,只用于学习交流。第一种结果:调用前:50调用后:1000分析:理解:好理解第二种
- 什么是递归?用Java写一个简单的递归程序递归的定义递归(recursion):以此类推是递归的基本思想,将规模大的问题转化为规模小的问题来
- 写在前面作为程序员,多多少少都会遇到一些内存溢出的场景,如果你还没遇到,说明你工作的年限可能比较短,或者你根本就是个假程序员!哈哈,开个玩笑
- Servlet 实现文件上传所谓文件上传就是将本地的文件发送到服务器中保存。例如我们向百度网盘中上传本地的资源或者我们将写好的博客上传到服务
- 实现要求1、使用Java图形界面组件设计软件,界面如图所示。2、软件能够满足基本的“加、减、乘、除"等运算要求。3、程序代码清晰,
- 一、注解是什么Java 注解用于为 Java 代码提供元数据,看完这句话也许你还是一脸懵逼,用人话说就是注解不直接影响你的代码执行,仅提供信
- 在java.lang.Runtime.exec的使用中,我们经常会用到将重定向命令执行的输入/结果或者将错误信息读取出来.那么,在使用过程中
- 一、Java把这些不同来源和目标的数据都统一抽象为数据流。Java语言的输入输出功能是十分强大而灵活的。在Java类库中,IO部分的内容是很
- 缘起工作时使用java开发服务器后台,用Jersey写Restful接口,发现有一个Post方法始终获取不到参数,查了半天,发现时获取参数的
- 说明:在阅读本篇文章之前建议大家先详细学习一下spring的相关知识,有助于更深刻的理解spirngboot的配置原理。一、什么是sprin
- 效果:原图加水印后的图片废话不多说,直接上代码代码:package com.example.demo;import java.awt.Alp
- 本文为大家分享了Java实现文件上传下载功能的具体代码,供大家参考,具体内容如下前端通过form表单的enctype属性,将数据传递方式修改
- 本文是一个 Spring 扩展支持 SPEL 的简单模式,方便第三方通过 Spring 提供额外功能。简化版方式这种方式可以在任何能获取Ap
- ThymeleafThymeleaf是最近SpringBoot推荐支持的模板框架,官网在thymeleaf.org这里。我们为什么要用Thy
- JoinPoint的getSignature方法在使用springboot写aop的时候,有个JoinPoint类,用来获取代理类和被代理类
- Android四种数据存储的应用方式作为一个完整的应用程序,数据存储操作是必不可少的。因此,Android系统一共提供了四种数据存储方式。分
- 在eclipse中默认的maven,它加载的是国外的镜像,那样速度会比较慢,如果使用国内镜像,比如阿里的中央仓库;速度会快很多。那如何修改m
- 一、安装Maven下载地址:https://maven.apache.org/download.cgi把下载的安装包解压tar -xvf a
- 相比于直线检测,直线拟合的最大特点是将所有数据只拟合出一条直线void fitLine( InputArray points, Output