Java多线程编程——synchronized用法总结

本文探讨了Java多线程编程中线程安全的重要性,重点介绍了synchronized关键字的两种用法:修饰类方法实现对象级锁和构成同步代码块以提高资源利用率。通过实例展示了如何避免死锁等问题。

在Java多线程编程的问题中,“线程安全”和“非线程安全”问题成为整个多线程编程的核心思考问题,其中主要涉及到多个线程对共享变量访问时可能出现的各种错误,通过synchronized的使用机制,以最小程度的实现部分代码的同步执行。Java通过提供synchronized机制,来实现同步。由于synchronized的使用过于的灵活,因此,本文主要对synchronized的一些用法、锁对象和相关问题进行了总结。

synchronized的用法大体上可分为两类,其一,synchronized通过直接修饰类方法,实现同步;其二,synchronized通过构成同步代码块来实现代码块里的程序的同步性。

a. synchronized通过直接修饰类方法

	synchronized public void addI(String username) {
		if(username.equals("a")) {
			num = 100;
			System.out.println("a set over");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			num = 200;
			System.out.println("b set over");
		}
		System.out.println(username + " num="+num);
	}
特点:synchronized通过修饰非静态方法,可使多线程访问该方法时,获得基于对象的锁,从而实现同步的对该方法进行访问

优点:能够实现方法的同步性,从而确保方法的执行不会由于多线程的访问而导致错误的发生

缺点:由于同步访问将导致执行效率的下降,计算机的吞吐率下降,多线程执行时间将更长

注:当synchronized修饰的类方法为静态方法,即由static进行修饰的方法时,线程将获得基于类的锁,即其余未获得锁的线程将无法访问该类的任何部分。区别:当修饰的类方法不是static修饰的方法时,其余线程可访问非同步(synchronized修饰)的方法。

只要对象不变,即使对象的属性被改变,运行的结果还是同步

重要结论:

1. synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁

2. 只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要

b. synchronized同步代码块

	public void doLongTimeTask() {
		for(int i = 0; i < 100; i++) {
			System.out.println("nosynchronized threadName="+Thread.currentThread().getName()+" i="+(i+1));
		}
		System.out.println("");
		synchronized(this) {
			for(int i = 0; i < 100; i++) {
				System.out.println("synchronized threadName="+Thread.currentThread().getName()+" i="+(i+1));
			}
		}
	}
该例可看出,相对于synchronized修饰类方法,synchronized同步块能够进一步缩小同步代码的范围,从而保证更多部分的代码是异步执行的,从而很好的保证了多线程下的高资源利用率以及系统的吞吐量

在synchronized同步代码块中,利用synchronized(Object){}该同步形式,可以灵活的对不同的对象Object进行锁限制,通过控制共享变量object来实现不同的同步,由此可能产生死锁问题

	public void run() {
		if(username.equals("a")) {
			synchronized(lock1) {
				System.out.println("username = "+username);
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized(lock2) {
					System.out.println("按lock1->lock2代码顺序执行了");
				}
			}
		}
		if(username.equals("b")) {
			synchronized(lock2) {
				System.out.println("username = "+username);
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			synchronized(lock1) {
				System.out.println("按lock2->lock1代码顺序执行了");
			}
		}
	}








### Java多线程编程信号量的实现方式 #### 什么是信号量? 信号量(Semaphore)是一种用于控制多个线程对共享资源访问数量的同步工具。它可以限制同时访问某一特定资源的操作数,从而防止竞争条件的发生。Java 提供了 `java.util.concurrent.Semaphore` 来支持信号量的功能。 #### Semaphore 的基本概念 `Semaphore` 可以维护一组许可证的概念。每当调用 `acquire()` 方法时,如果还有可用的许可,则会获取一个;如果没有剩余许可,则当前线程会被阻塞直到有新的许可释放出来。当某个线程完成操作后,可以通过调用 `release()` 方法返回一个许可给其他等待中的线程使用[^3]。 #### 创建和初始化 Signal 创建一个 `Semaphore` 对象通常需要指定初始的许可数目以及是否采用公平策略: - **参数一**:表示有多少个许可可供分配; - **参数二**(可选布尔值):决定该 semaphore 是否遵循 FIFO 公平调度原则,默认为 false 表示非公平模式。 下面展示如何声明具有固定容量的 semaphores 并设置其属性: ```java // 非公平, 初始计数值为3 Semaphore semaphore = new Semaphore(3); ``` 或者启用公平定机制: ```java // 使用公平算法, 同样设定最大并发连接数为3 Semaphore semaphoreFair = new Semaphore(3, true); ``` #### 应用场景举例——数据库连接池模拟 假设我们有一个简单的数据库连接池管理器,只允许最多三个客户端同时建立连接查询数据。此时就可以借助 semaphore 来协调这些请求。 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.concurrent.Semaphore; public class DBConnectionPool { private final int MAX_CONNECTIONS = 3; // 定义最大的连接数 private Connection[] connections; private Semaphore availableConnections; public DBConnectionPool(String url) throws SQLException { this.availableConnections = new Semaphore(MAX_CONNECTIONS); connections = new Connection[MAX_CONNECTIONS]; for (int i=0;i<MAX_CONNECTIONS;i++) { connections[i]=DriverManager.getConnection(url); } } /** * 获取数据库连接的方法. */ public Connection getConnection() throws InterruptedException{ try { availableConnections.acquire(); // 尝试获得一个许可 return getFreeConnection(); } catch (InterruptedException e){ throw e; } } private synchronized Connection getFreeConnection(){ for(Connection conn :connections ){ if(!conn.isClosed()){ return conn ; } } return null ; //理论上不会到达这里 } /** * 归还数据库连接到池子里面去. */ public void releaseConnection(Connection connection){ if(connection !=null && !connection.isClosed()) { try { connection.close(); // 关闭实际物理链接 }catch(SQLException ex){} availableConnections.release(); // 返回一个许可回semaphore } } } ``` 上述例子展示了如何利用 semaphore 控制同一时间内的活动线程数量不超过预设上限,并且保证每次只有一个线程能够成功取得空闲状态下的真实 JDBC 连接对象[^4]。 #### 总结 通过以上分析可以看出,在 Java 编程环境中运用 signal 不仅能有效解决多线程间资源共享问题,而且还能简化程序逻辑结构提高开发效率。然而需要注意的是,不当配置可能会引发死现象或者其他性能瓶颈等问题,因此在实际项目应用过程中应当谨慎考虑各种边界情况并做好充分测试工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值