理解线程安全:保护你的代码免受并发问题困扰

目录

前言

一、什么是线程安全?

二、为什么需要线程安全?

三、实现线程安全的方法

四、synchronized

使用 synchronized 关键字时,需要注意以下几点:

五、Demo讲解


前言

        在现代软件开发中,尤其是在多线程编程中,线程安全(Thread Safety)是一个至关重要但又复杂的话题。本文将从基础概念开始,逐步深入,帮助你理解什么是线程安全,以及如何在实际开发中实现线程安全。

一、什么是线程安全?

        线程安全是指多个线程可以同时访问和修改共享数据而不导致数据的不一致性或程序错误。简单来说,当一个对象或函数在多线程环境下能够正确地执行,并且不会引起任何未定义的行为时,它就是线程安全的。

二、为什么需要线程安全?

        多线程编程的主要目的是为了提高程序性能和响应速度。然而,当多个线程同时操作共享资源时,可能会出现竞争条件(Race Condition)、死锁(Deadlock)以及饥饿(Starvation)等问题。这些问题不仅会导致程序运行结果不正确,还可能引发崩溃和难以调试的错误。

三、实现线程安全的方法

  1. 互斥同步:使用锁(如 synchronized、ReentrantLock 等)来保护共享数据,在同一时刻只允许一个线程访问共享数据,其他线程需要等待锁释放后才能访问。

  2. 原子操作:使用原子操作来确保对共享数据的操作是不可分割的,不会被中断,常见的原子操作包括 atomic 包下的原子类,以及 volatile 关键字修饰的变量。

  3. 无锁并发编程:通过使用无锁的数据结构和算法来实现线程安全,例如 CAS(Compare and Swap)操作,乐观锁机制等。

  4. 线程封闭:将共享数据限制在单个线程内部,避免多个线程之间直接访问共享数据,从而避免竞态条件。

  5. 不可变对象:通过创建不可变对象来避免多线程并发修改共享状态,从而避免线程安全问题。

四、synchronized

        当一个线程访问一个被 synchronized 修饰的方法或代码块时,会自动获取该方法或代码块对应的锁,其他线程必须等待该线程释放锁后才能获取锁并进入临界区。这种方式可以有效避免多个线程同时访问共享数据时发生竞态条件和其他线程安全问题。

使用 synchronized 关键字时,需要注意以下几点:

  1. 修饰方法:在修饰方法时,锁对象默认为当前对象(this),不同的线程需要获取的是同一个对象的锁。

  2. 修饰代码块:在修饰代码块时,需要指定锁对象,一般可以使用 this、类名.class 等作为锁对象。需要注意的是,锁对象必须是同一个对象才能保证线程同步。

  3. 可重入性:在同一个线程中,如果一个方法已经获取了锁,那么在调用另一个被 synchronized 修饰的方法时,该线程仍然能够获取到同一个锁,即 synchronized 具有可重入性。

  4. 锁的释放:在 synchronized 块执行完毕或者抛出异常时,锁会自动释放。如果在 synchronized 块中使用了 wait() 方法,则锁也会被释放,等待其他线程唤醒后再次获取锁。

五、Demo讲解

package com.ctb.demo;


/**
 * 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的
 * synchronized:可以在任意对象或方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"
 * @author 彪
 *
 */
public class MyThread extends Thread{
	private int count = 5;
	
	
	public void run() {
		count--;
		System.out.println(this.currentThread().getName()+"count = "+count);
	}
	
	public static void main(String[] args) {
		/**
		* 	当多个线程访问MyThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的)
		*   一个线程想要执行synchronized修饰的方法里的代码
		*   1.尝试获得锁
		*   2.如果拿到锁,执行synchronized代码内容:拿不到锁,这个线程就会不断尝试获得这把锁,直道拿到为止,
		*   	而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
		*/
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}

}

结果:并不是我们想要的,count--

package com.ctb.demo;


/**
 * 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的
 * synchronized:可以在任意对象或方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"
 * @author 彪
 *
 */
public class MyThread extends Thread{
	private int count = 5;
	
	
	//synchronized加锁
	public synchronized void run() {
		count--;
		System.out.println(this.currentThread().getName()+"count = "+count);
	}
	
	public static void main(String[] args) {
		/**
		* 	当多个线程访问MyThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的)
		*   一个线程想要执行synchronized修饰的方法里的代码
		*   1.尝试获得锁
		*   2.如果拿到锁,执行synchronized代码内容:拿不到锁,这个线程就会不断尝试获得这把锁,直道拿到为止,
		*   	而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
		*/
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}

}

结果:

注意:

当多个线程访问MyThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的) ​ 一个线程想要执行synchronized修饰的方法里的代码 ​ 1.尝试获得锁 ​ 2.如果拿到锁,执行synchronized代码内容:拿不到锁,这个线程就会不断尝试获得这把锁,直道拿到为止, ​ 而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题:前面线程count不是按顺序的)

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值