线程同步

本文深入探讨了多线程环境中资源同步的问题,包括同步代码块、同步方法及Lock机制等,通过具体示例展示了如何确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前一篇讲到线程的两种实现方式,一种继承Thread类,一种实现了Runabble接口;也说到多个线程共享同一资源问题,由于线程在多cpu环境下是并行执行的,就会出现线程安全问题,此篇幅,我就来讲解一下多线程访问统一资源的安全问题:

(一)通过同步代码块和同步方法,在多线程之间通过加同步锁(synchronized)来操作如何访问共同资源的线程安全问题!

(1)同步代码块:

语法:synchronized(同步锁){

//需要同步操作的代码

}

同步锁:也称为同步监听器,同步监听对象,互斥锁(锁定的是多个线程共享的资源,切记不是当前线程对象)

目的:为了保证每个线程都能正常进行原子性操作(操作为一个整体,不可分割),Java引入了线程同步的机制;Java程序运行使用的任何对象都可作为同步监听对象,但是通常我们会把当前并发访问的共同资源作为同步监听对象(只有共同访问的临界资源,多线程之间才有互斥现象)。

注意:在任何时候访问共同资源(临界资源),最多只允许一个线程拥有同步锁,谁先获得锁就先进入同步代码块,其他的线程就只能等待!

//目的就是解决num--这两个操作时同步的,并且是原子性!(两者可分割)使用同步代码块解决!
public class SynchronizedBlockDemo {
	public static void main(String[] args) {
		// 创建申请那个线程(同学),吃苹果
		Apple1 a = new Apple1(); // Apple对象只有一个(资源共享)
		// 三个线程共享Apple中的成员变量资源(num资源)
		new Thread(a, "小A").start();
		new Thread(a, "小B").start();
		new Thread(a, "小C").start();

	}
}

class Apple1 implements Runnable {
	private int num = 50;

	public void run() {
		//synchronized (this) {	//加锁机制正确,锁定当前Apple1临界资源对象
			for (int i = 0; i < 50; i++) {
				//synchronized (this) {	//加锁机制正确,做出了判断操作!
					if (num > 0) {
						synchronized (this) {	//错误加锁,线程A、B、C同时拿到苹果编号为1时,进行减一操作,不判断,就会出现0、 -1 的情况!
							// 模拟网络延迟
							try {
								// A、B、C 线程同时拿到编号为1的苹果,C线程吃了该苹果,减一,打印;线程A也吃了该苹果,减一操作,打印;
								// 线程B也吃了该苹果,也减一操作,打印,就得到了这种结果!
								Thread.sleep(10);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							String threadName = Thread.currentThread().getName();// 获取当前的线程,进而获取线程名称
							System.out.println(threadName + "吃了编号为:" + num + "的苹果!");
							num--;
						}
					}
				//}
			}
		//}
	}
}

(2)使用synchronized关键字修饰的方法,叫做同步方法,保证了A线程在执行该方法外,其他线程只能在方法外面等待;

语法:public synchronized void doWork(){

//编写同步代码

}

那么同步锁是谁呢?

1)对于非static修饰的方法,同步锁就是this,当前类的实例对象(谁调用该方法,谁就锁定了当前类的对象,即临界资源对象-->Apple 对象,类实例对象);

2)对于static方法,相当于锁定当前类;

注意:不能使用synchronized关键字修饰run()方法,修饰之后,某一个线程就执行完所有的功能,达不到多个线程共同访问临界资源问题,好比是多个线程串行!(多线程多行道,这样操作就是多个线程串行在单行道中,不会同时在多行道中访问!)

解决方案把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,再在run()方法中调用该新的方法即可!

锁定同步方法,相当于锁定当前类的实例对象!(多线程访问的临界资源对象Apple对象)

建议:由于使用synchronized加锁机制来实现多线程同步,并且安全,但是效率极低,所以要尽量减少synchronized的作用域(越小越好)

//目的就是解决num--这两个操作时同步的,并且是原子性!(两者可分割)使用同步方法解决!
public class SynchronizedMethodDemo {
	public static void main(String[] args) {
		// 创建申请那个线程(同学),吃苹果
		Apple2 a = new Apple2(); // Apple对象只有一个(资源共享)
		// 三个线程共享Apple中的成员变量资源(num资源)
		new Thread(a, "小A").start();
		new Thread(a, "小B").start();
		new Thread(a, "小C").start();
	}
}
class Apple2 implements Runnable {
	private int num = 50;
	public void run() {
		for (int i = 0; i < 50; i++) {
			eatApple();
		}
	}
	private synchronized void eatApple(){
		if (num > 0) {
			// 模拟网络延迟
			String threadName = Thread.currentThread().getName();// 获取当前的线程,进而获取线程名称
			System.out.println(threadName + "吃了编号为:" + num + "的苹果!");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			num--;
		}
	}
}

(3)同步锁(Lock)

   Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock全部都有,除此之外,Lock锁机制更加灵活更加能体现面向对象思想Lock的实现类为ReentrantLock类,ReentrantLock将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。

public class LockDemo {

	public static void main(String[] args) {
		// 创建申请那个线程(同学),吃苹果
		Apple3 a = new Apple3(); // Apple对象只有一个(资源共享)
		// 三个线程共享Apple中的成员变量资源(num资源)
		new Thread(a, "小A").start();
		new Thread(a, "小B").start();
		new Thread(a, "小C").start();
	}
}

class Apple3 implements Runnable {
	private int num = 50;
	
	//创建一个可重入锁
	private final Lock lock = new ReentrantLock();
	
	private void eatApple(){
		//进入方法,立马加锁
		lock.lock();//获得锁
		if(num > 0){
			try{
				String threadName = Thread.currentThread().getName();// 获取当前的线程,进而获取线程名称
				System.out.println(threadName + "吃了编号为:" + num + "的苹果!");
				Thread.sleep(10);
				num--;
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally{
				//释放锁
				lock.unlock();
			}
		}
	}

	public void run() {
		for (int i = 0; i < 50; i++) {
			eatApple();
		}
	}
}

(二)synchronized 加锁修饰类的静态变量、方法以及this、非静态方法的讲解:

(1)synchronized修饰非静态方法,锁定的是该类的实例,共享同一个实例在多线程中调用才会触发同步锁定,所以多个被synchronized修饰的非静态方法在同一个实例下,多线程只能同时调用一个,因为互斥。

public class TestThread {
	public static void main(String[] args) {
		Animal animal = new Animal();
		Thread t = new Thread(animal);
		t.start();
		animal.run1();
	}
}

class Animal implements Runnable{
	public synchronized void run1(){
		System.out.println(System.currentTimeMillis() + "  run1");
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("我是run1");
		System.out.println(System.currentTimeMillis() + "  run1");
	}
	
	@Override
	public void run() {
		synchronized(this){
			System.out.println(System.currentTimeMillis() + "  run");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("我是run1");
			System.out.println(System.currentTimeMillis() + "  run");
		}
	}
}
运行结果:

1497519926575  run1
我是run1
1497519926676  run1
1497519926676  run
我是run1
1497519928676  run

解释:锁定类中静态方法和this,都指的是当前类的实例对象,多个线程在访问这个类中非静态方法,以及同步代码块,由于锁定的同一个实例对象,谁先获得同步锁(即锁定当前临界资源对象),谁就先执行,执行完释放锁时,下一线程方可才能获取同步锁,然后执行操作!


(2)synchronized修饰的静态方法或者静态变量,锁定的是类本身,而不是类的一个实例对象,共享同一个类中所有被synchronized修饰的静态方法或者静态变量,由于互斥,多线程中只能同时调用一个;

public class TestStaticThread {
	public static void main(String[] args) throws InterruptedException {
		Dog dog = new Dog();
		Thread t = new Thread(dog);
		t.start();
		Thread.sleep(100);
		Dog.run1();
	}
}

class Dog implements Runnable{
	private static volatile int num = 0;
	public synchronized static void run1(){
		System.out.println(System.currentTimeMillis() + "---run1----" + num);
		try {
			Thread.sleep(100);
			num = 100;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("我是run1");
		System.out.println(System.currentTimeMillis() + " ----run1--- " + num);
	}
	
	@Override
	public void run() {
		System.out.println("=====================");
		synchronized(Dog.class){
			System.out.println(System.currentTimeMillis() + "----run----" + num);
			try {
				Thread.sleep(2000);
				num = 200;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("我是run");
			System.out.println(System.currentTimeMillis() + "----run-----" + num);
		}
	}
}
运行结果:

=====================
1497519997173----run----0
我是run
1497519999173----run-----200
1497519999173---run1----200
我是run1
1497519999273 ----run1--- 100


解释:synchronized修饰的静态方法,同步锁定的是当前类本身,而不是当前类的实例对象;静态变量和静态方法是属于某一个类,而不是类的实例对象,但类实例可以访问;通过同步代码块锁定当前类,以及同时访问类中的静态方法,多线程之间当然也会有一个先来后到的顺序,由于锁定的同一个类,谁先获的同步锁,谁就先执行,后续线程通过抢占式,谁先获取到同步锁谁就执行!

(3)synchronized块,直接加锁指定的对象,该对象在多个地方被同步锁锁定,多线程也只能同时执行其中的一个,其他未获得锁的线程需要等待期释放锁,然后获取所方可执行!


















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值