`

【线程】使用同步:解决多线程引发的内存一致性错误问题

    博客分类:
  • Java
 
阅读更多

首先,需要理解一些理论上的东西。

 

多个线程并发对同一个资源进行操作,很可能发生内存一致性错误

 

究其原因,线程很多情况下对资源的操作不是原子的,这些代码会被分为若干条指令去执行,而在一个CPU时间片内又不能将这些指令全部执行完毕。

当多个线程同时操作同一个共享资源时,线程B拿着线程A的半成品进行操作,内存一致性错误就发生了。

 

如何解决?

1.同步,即:加锁。通过加锁的方式,可以确保唯一获取锁的线程可以不受干扰的执行完自己的程序片段。只有当前线程释放锁之后,其它线程才能对此资源进行操作。否则,只能等待当前线程执行完毕后释放锁(等待锁的线程全部被阻塞了);

 

2.资源不可变,即:只读。只读意味着数据不会变化,因此对只读数据的操作不可能发生内存不一致问题。

 

同步锁

同步机制的建立是基于其内部一个叫内部锁或者监视锁的实体。

内部锁在同步机制中起到两方面的作用:

对一个对象的排他性访问;

建立一种happens-before关系,而这种关系正是可见性问题的关键所在。

每个对象都有一个与之关联的内部锁。
当一个线程需要排他性的访问一个对象的域时,首先请求该对象的内部锁,当访问结束时释放内部锁。
在线程获得内部锁到释放内部锁的这段时间里,只有当前线程拥有这个内部锁。
当一个线程拥有一个内部锁时,其他线程将无法获得该内部锁。其他线程如果去尝试获得该内部锁,则会被阻塞。
当线程释放其拥有的内部锁时,该操作和对该锁的后续请求间将建立happens-before关系。

 

 

同步(synchronized)的两种方式:

1. 同步方法

非静态方法-this

当线程调用一个同步方法时,它会自动请求该方法所在对象的内部锁。
当方法返回结束时则自动释放该内部锁,即使退出是由于发生了未捕获的异常,内部锁也会被释放。
静态方法-Class
当调用一个静态的同步方法时,由于静态方法是和类(而不是对象)相关的,所以线程会请求类对象(Class Object)的内部锁。
因此用来控制类的静态域访问的锁不同于控制对象访问的锁。

 

2. 同步代码块

同步块必须指定所请求的是哪个对象的内部锁

可以实现更细粒度的控制

 

可重入同步

一个线程不能获得其他线程所拥有的锁,但是它可以获得自己已经拥有的锁。
允许一个线程多次获得同一个锁实现了可重入同步。
避免了线程自己阻塞自己。

 

 

线程同步可以解决内存不一致错误,但也引入了其它的问题

死锁、饥饿、活锁

 

A.线程死锁

两个或多个线程永久阻塞,互相等待对方释放资源。

 

package org.thread;
public class DeadLock {
	
	public static void main(String[] args) {
		final Friend zs = new Friend("zs");
		final Friend ls = new Friend("ls");
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true)
					zs.bow(ls);
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true)
					ls.bow(zs);
			}
		}).start();
		
	}
	
	static class Friend {
		private String name;
		
		public Friend(String name) {
			this.name = name;
		}
		
		public synchronized void bow(Friend friend) {
			System.out.println(Thread.currentThread().getName()+":::"+this.name+" bow to " + friend.name);
			friend.bowBack(this);//同步中调用其它对象的同步方法可能会发生死锁:相互等待对方释放锁
			System.out.println(Thread.currentThread().getName()+":::"+"bow done!");
		}
		
		public synchronized void bowBack(Friend friend) {
			System.out.println(Thread.currentThread().getName()+":::"+this.name+" bow to " + friend.name);
		}
	}
}

 

引起死锁的原因:需要相互获取对方锁的线程同时占有了自己的锁,导致对方无法获取到锁。

解决办法:通过设置同一个锁实现排他性访问,不给线程同时占有锁的机会。

 

package org.thread;
public class AvoidDeadLock {
	
	public static void main(String[] args) {
		final Friend zs = new Friend("zs");
		final Friend ls = new Friend("ls");
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true)
					zs.bowEachOther(ls);
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true)
					ls.bowEachOther(zs);
			}
		}).start();
		
	}
	
	static class Friend {
		private String name;
		
		public Friend(String name) {
			this.name = name;
		}
		
		public synchronized void bow(Friend friend) {
			System.out.println(Thread.currentThread().getName()+":::"+this.name+" bow to " + friend.name);
			friend.bowBack(this);//同步中调用其它对象的同步方法可能会发生死锁:相互等待对方释放锁
			System.out.println(Thread.currentThread().getName()+":::"+"bow done!");
		}
		
		public synchronized void bowBack(Friend friend) {
			System.out.println(Thread.currentThread().getName()+":::"+this.name+" bow to " + friend.name);
		}
		//额外引入一个能让线程排他性访问的方法,这些线程需要争夺同一个锁
		public void bowEachOther(Friend friend) {
			//使用同一个锁,实现排他性的访问
			synchronized (Friend.class) {
				bow(friend);
			}
		}
	}
}

 

B.线程饥饿

当某个线程占有了锁,并且需要执行很长一段时间才释放锁,这就导致其它等待锁释放的线程处于阻塞状态。这种情况下其它线程便处于“饥饿”状态。

 

C.线程活锁

活锁指的是线程间相互响应时,由于响应结果不正确而导致彼此一直都处于响应状态。

线程并没有阻塞,只是一直在响应而无法恢复到正常的工作中。

 

 

线程协作

实际开发中,很多场景都可以归结为生产者-消费者的协作关系。

基本规则:

任务池满的时候,阻塞生产者,直到任务池中有任务被取走;

任务池空的时候,阻塞消费者,直到任务池增加了新的任务;

实现原理:

1. 同步:生产者与消费者使用同一个锁

2. 协作:

wait()  线程判断条件不满足时,等待

notify()/notifyAll()  解除对方的等待

 

以下示例通过synchronized、while->wait()、notifyAll()模拟生产者-消费者的模型。

实际开发中,不需要自己再去发明轮子了,请使用java.util.concurrent包中的工具类完成需要的功能

 

package org.thread;
import java.util.concurrent.ThreadLocalRandom;

public class ConsumerProduerDemo {
	public static void main(String[] args) {
		Messenger messenger = new Messenger();
		new Consumer(messenger).start();
		new Producer(messenger).start();
	}

}

// ---共享资源:message
class Messenger {
	private String message;
	boolean empty = true;

	public synchronized String take() {
		while (empty) {
			try {
				System.out.println("Consumer waiting...");
				wait();
			} catch (InterruptedException e) {
			}
		}
		empty = true;
		notifyAll();
		return message;
	}

	public synchronized void put(String message) {
		while (!empty) {
			try {
				System.out.println("Producer waiting...");
				wait();
			} catch (InterruptedException e) {}
		}
		this.message = message;
		empty = false;
		notifyAll();
	}
}

// ---生产者
class Producer implements Runnable {

	private Messenger messenger;

	public Producer(Messenger messenger) {
		this.messenger = messenger;
	}

	@Override
	public void run() {
		String[] msgs = { "First msg", "Second msg", "Third msg", "Fouth msg" };
		for (int i = 0; i < msgs.length; i++) {
			messenger.put(msgs[i]);
			try {
				Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
			} catch (InterruptedException e) {
			}
		}
		messenger.put("Done");
	}

	public Thread start() {
		Thread t = new Thread(this);
		t.start();
		return t;
	}
}

// ---消费者
class Consumer implements Runnable {

	private Messenger messenger;

	public Consumer(Messenger messenger) {
		this.messenger = messenger;
	}

	@Override
	public void run() {
		for (String msg = messenger.take(); !"Done".equals(msg); msg = messenger.take()) {
			System.out.println("Message take: " + msg);
			try {
				Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
			} catch (InterruptedException e) {
			}
		}
	}

	public Thread start() {
		Thread t = new Thread(this);
		t.start();
		return t;
	}
}

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    3种多线程实现同步方法

    然而,多线程也带来了数据同步的问题,因为多个线程可能同时访问共享资源,如果不加以控制,就可能导致数据不一致或引发错误。本篇文章将深入探讨三种在C++中实现多线程同步的方法:事件对象、关键代码段和互斥对象...

    解决多线程编程中的同步互斥问题

    使用关键段可以有效地解决多线程编程中的同步互斥问题,确保了在多线程环境中对共享资源的安全访问。但是需要注意的是,关键段仅能解决单个CPU核心上的互斥访问问题,对于多处理器或多核心环境下的同步问题可能需要...

    Java多线程-同步机制解决线程安全问题方式一:同步代码块

    总结来说,Java中的同步代码块是一种有效的解决线程安全问题的手段,通过限制对共享资源的并发访问,可以保证数据的一致性和线程的安全性。然而,过度使用同步可能会降低程序的性能,因此需要合理设计同步范围,确保...

    多线程数据同步

    "countErr"这个文件名可能是记录线程同步过程中错误计数的文件,这在调试多线程程序时非常有用,可以帮助开发者识别和修复同步问题。 总之,多线程数据同步是并发编程的关键挑战,通过使用临界区对象等同步机制,...

    Java多线程-同步机制解决线程安全问题方式二:同步方法

    在Java多线程编程中,线程安全问题通常是指多个线程访问共享资源时可能出现的不一致或错误状态。为了确保并发执行的线程能够正确地处理这些共享资源,Java提供了多种同步机制,其中之一就是同步方法。同步方法通过在...

    多线程同步:事件(event)

    总结起来,事件(event)在多线程同步中起着关键作用,它通过提供一种线程间通信的方式,确保了线程按照指定顺序访问共享资源,避免了并发执行可能导致的数据不一致问题。在设计多线程程序时,合理利用事件同步能有效...

    多线程的批量线程同步解决方案

    "多线程的批量线程同步解决方案"这个标题暗示我们探讨的是如何在多线程环境下有效地管理和同步多个任务,确保数据一致性与程序正确性。下面将详细阐述相关知识点。 一、多线程基础 多线程是指在一个进程中同时执行...

    MFC多线程同步类的使用

    【MFC多线程同步类的使用】 在MFC(Microsoft Foundation Classes)中,多线程编程是一项重要的技术,尤其在开发复杂的、并发执行的任务时。多线程允许程序同时执行多个任务,提升效率和响应速度。然而,线程间的...

    Delphi多线程同步的例子

    2. **线程同步**:当多个线程访问共享资源时,可能会引发竞态条件,导致数据不一致。为避免这种情况,需要进行线程同步。Delphi 提供了几种同步机制,如 `TEvent`, `TMutex`, `TCriticalSection` 和 `TSemaphore`。...

    创建多线程线程同步

    总结,多线程和线程同步是提高程序效率和解决并发问题的关键技术。理解并掌握CEVENT对象等同步机制,对于编写高效、可靠的多线程程序至关重要。在实际编程中,需要根据具体需求选择合适的同步工具,确保程序的正确...

    用多线程同步方法解决生产者-消费者问题(操作系统课设

    多线程同步方法解决生产者-消费者问题 多线程同步方法是解决生产者-消费者问题的常用方法。生产者-消费者问题是操作系统中经典的问题之一,它是指在多线程环境下,多个生产者线程和消费者线程访问同一个共享缓冲区...

    线程同步机制解决多线程资源访问冲突

    在多线程编程中,线程同步是一种至关重要的技术,用于协调并发执行的线程,以确保它们正确、有序地访问共享资源,防止数据不一致和资源竞争问题。本篇文章将深入探讨线程同步机制以及如何使用事件对象来实现这一目标...

    多线程临界段同步演示1

    本例中的"多线程不同步演示2"可能展示了没有同步机制时,多线程访问共享资源可能导致的问题,而"多线程临界段同步演示1"则可能是解决这个问题的示例。 临界区是Windows API提供的一种轻量级的同步工具,适合于在...

    简单实现多线程同步示例(模拟购票系统)

    本示例“简单实现多线程同步示例(模拟购票系统)”旨在通过一个具体的实例,帮助开发者理解如何在Java中创建并管理多线程以及如何实现线程同步,确保数据的一致性和正确性。 首先,我们要明确多线程的基本概念。在...

    内存模型-多线程内存模型

    通过对内存模型的理解和合理运用,可以有效地解决多线程程序中的常见问题,并确保程序的一致性和可移植性。随着未来计算平台的不断发展,多线程编程将会变得更加重要,而C++09提供的内存模型将继续发挥其核心作用。

    C#实现多线程同步并发操作

    1. **线程同步**:指的是控制多个线程对共享资源的访问,避免竞态条件(race condition),确保数据的一致性和完整性。 2. **临界区**:指代码中访问和修改共享资源的部分,必须确保一次只有一个线程能够进入临界...

    多线程的同步机制 VC++

    多线程同步机制在软件开发中扮演着至关重要的角色,特别是在多处理器系统或者并发执行的任务中,确保线程间的正确协作和数据一致性是必不可少的。VC++中提供了多种同步机制来处理多线程间的同步问题,其中Event是...

    Python语言基础:线程同步.pptx

    通过使用线程同步,我们可以确保在多线程环境中数据的一致性和正确性,避免出现不可预知的错误。`Lock()`对象是最基础的同步原语,Python还提供了其他同步工具,如`Semaphore`、`Event`、`Condition`等,以适应更...

    linux上实现多进程和多线程实现同步互斥(源代码)

    在Linux操作系统中,多进程和多线程是两种并发执行的方式,它们在处理并发问题时,经常需要进行同步和互斥操作,以确保数据的一致性和程序的正确性。本篇将详细介绍这两种并发模型以及如何在Linux环境中实现同步互斥...

Global site tag (gtag.js) - Google Analytics