java并发问题概述

1什么是并发问题。

多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。

银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去50元,A先提交,B后提交。最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050。这就是典型的并发问题。如何解决?可以用锁。

2java中synchronized的用法

用法1

public class Test{
	public synchronized void print(){
		….;
	}
}

某线程执行print()方法,则该对象将加锁。其它线程将无法执行该对象的所有synchronized块。

用法2

public class Test{
	public void print(){
		synchronized(this){
			//锁住本对象
			…;
		}
	}
}

同用法1, 但更能体现synchronized用法的本质。

用法3

public class Test{
	private String a = “test”;
	public void print(){
		synchronized(a){
			//锁住a对象
			…;
		}
	}
	public synchronized void t(){
		…;
		//这个同步代码块不会因为print()而锁定.
	}
}

执行print(),会给对象a加锁,注意不是给Test的对象加锁,也就是说Test对象的其它synchronized方法不会因为print()而被锁。同步代码块执行完,则释放对a的锁。

为了锁住一个对象的代码块而不影响该对象其它synchronized块的高性能写法:

public class Test{
	private byte[] lock = new byte[0];
	public void print(){
		synchronized(lock){
			…;
		}
	}
	public synchronized void t(){
		…;
	}
}

静态方法的锁

public class Test{
	public synchronized static void execute(){
		…;
	}
}

效果同

public class Test{
	public static void execute(){
		synchronized(TestThread.class){
			…;
		}
	}
}

3 Java中的锁与排队上厕所。

锁就是阻止其它进程或线程进行资源访问的一种方式,即锁住的资源不能被其它请求访问。在JAVA中,sychronized关键字用来对一个对象加锁。比如:

public class MyStack {
	int idx = 0;
	char [] data = new char[6];
	public synchronized void push(char c) {
		data[idx] = c;
		idx++;
	}
	public synchronized char pop() {
		idx--;
		return data[idx];
	}
	public static void main(String args[]){
		MyStack m = new MyStack();
		/**
    下面对象m被加锁。严格的说是对象m的所有synchronized块被加锁。
    如果存在另一个试图访问m的线程T,那么T无法执行m对象的push和
    pop方法。
  */
		m.pop();
		//对象m被加锁。
	}
}

Java的加锁解锁跟多个人排队等一个公共厕位完全一样。第一个人进去后顺手把门从里面锁住,其它人只好排队等。第一个人结束后出来时,门才会打开(解锁)。轮到第二个人进去,同样他又会把门从里面锁住,其它人继续排队等待。

用厕所理论可以很容易明白:一个人进了一个厕位,这个厕位就会锁住,但不会导致另一个厕位也被锁住,因为一个人不能同时蹲在两个厕位里。对于Java就是说:Java中的锁是针对同一个对象的,不是针对class的。看下例:

MyStatckm1=newMyStack();
MyStatckm2=newMystatck();
m1.pop();
m2.pop();

m1对象的锁是不会影响m2的锁的,因为它们不是同一个厕位。就是说,假设有3线程t1,t2,t3操作m1,那么这3个线程只可能在m1上排队等,假设另2个线程t8,t9在操作m2,那么t8,t9只会在m2上等待。而t2和t8则没有关系,即使m2上的锁释放了,t1,t2,t3可能仍要在m1上排队。原因无它,不是同一个厕位耳。

Java不能同时对一个代码块加两个锁,这和数据库锁机制不同,数据库可以对一条记录同时加好几种不同的锁。

4何时释放锁?

一般是执行完毕同步代码块(锁住的代码块)后就释放锁,也可以用wait()方式半路上释放锁。wait()方式就好比蹲厕所到一半,突然发现下水道堵住了,不得已必须出来站在一边,好让修下水道师傅(准备执行notify的一个线程)进去疏通马桶,疏通完毕,师傅大喊一声:“已经修好了”(notify),刚才出来的同志听到后就重新排队。注意啊,必须等师傅出来啊,师傅不出来,谁也进不去。也就是说notify后,不是其它线程马上可以进入封锁区域活动了,而是必须还要等notify代码所在的封锁区域执行完毕从而释放锁以后,其它线程才可进入。

这里是wait与notify代码示例:

public synchronized char pop() {
	char c;
	while (buffer.size() == 0) {
		try {
			this.wait();
			//从厕位里出来
		}
		catch (InterruptedException e) {
			// ignore it…
		}
	}
	c = ((Character)buffer.remove(buffer.size()-1)).
	charValue();
	return c;
}
public synchronized void push(char c) {
	this.notify();
	//通知那些wait()的线程重新排队。注意:仅仅是通知它们重新排队。
	Character charObj = new Character(c);
	buffer.addElement(charObj);
}
//执行完毕,释放锁。那些排队的线程就可以进来了。

再深入一些。

由于wait()操作而半路出来的同志没收到notify信号前是不会再排队的,他会在旁边看着这些排队的人(其中修水管师傅也在其中)。注意,修水管的师傅不能插队,也得跟那些上厕所的人一样排队,不是说一个人蹲了一半出来后,修水管师傅就可以突然冒出来然后立刻进去抢修了,他要和原来排队的那帮人公平竞争,因为他也是个普通线程。如果修水管师傅排在后面,则前面的人进去后,发现堵了,就wait,然后出来站到一边,再进去一个,再wait,出来,站到一边,只到师傅进去执行notify.这样,一会儿功夫,排队的旁边就站了一堆人,等着notify.

终于,师傅进去,然后notify了,接下来呢?

1.有一个wait的人(线程)被通知到。

2.为什么被通知到的是他而不是另外一个wait的人?取决于JVM.我们无法预先

判断出哪一个会被通知到。也就是说,优先级高的不一定被优先唤醒,等待

时间长的也不一定被优先唤醒,一切不可预知!(当然,如果你了解该JVM的

实现,则可以预知)。

3.他(被通知到的线程)要重新排队。

4.他会排在队伍的第一个位置吗?回答是:不一定。他会排最后吗?也不一定。

但如果该线程优先级设的比较高,那么他排在前面的概率就比较大。

5.轮到他重新进入厕位时,他会从上次wait()的地方接着执行,不会重新执行。

恶心点说就是,他会接着拉巴巴,不会重新拉。

6.如果师傅notifyAll().则那一堆半途而废出来的人全部重新排队。顺序不可知。

JavaDOC上说,Theawakenedthreadswillnotbeabletoproceeduntilthecurrentthreadrelinquishesthelockonthisobject(当前线程释放锁前,唤醒的线程不能去执行)。

这用厕位理论解释就是显而易见的事。

5Lock的使用

用synchronized关键字可以对资源加锁。用Lock关键字也可以。它是JDK1.5中新增内容。用法如下:

class BoundedBuffer {
	final Lock lock = new ReentrantLock();
	final Condition notFull = lock.newCondition();
	final Condition notEmpty = lock.newCondition();
	final Object[] items = new Object[100];
	int putptr, takeptr, count;
	public void put(Object x) throws InterruptedException {
		lock.lock();
		try {
			while (count == items.length)
			      notFull.await();
			items[putptr] = x;
			if (++putptr == items.length) putptr = 0;
			++count;
			notEmpty.signal();
		}
		finally {
			lock.unlock();
		}
	}
	public Object take() throws InterruptedException {
		lock.lock();
		try {
			while (count == 0)
			      notEmpty.await();
			Object x = items[takeptr];
			if (++takeptr == items.length) takeptr = 0;
			--count;
			notFull.signal();
			return x;
		}
		finally {
			lock.unlock();
		}
	}
}

(注:这是JavaDoc里的例子,是一个阻塞队列的实现例子。所谓阻塞队列,就是一个队列如果满了或者空了,都会导致线程阻塞等待。Java里的ArrayBlockingQueue提供了现成的阻塞队列,不需要自己专门再写一个了。)

一个对象的lock.lock()和lock.unlock()之间的代码将会被锁住。这种方式比起synchronize好在什么地方?简而言之,就是对wait的线程进行了分类。用厕位理论来描述,则是那些蹲了一半而从厕位里出来等待的人原因可能不一样,有的是因为马桶堵了,有的是因为马桶没水了。通知(notify)的时候,就可以喊:因为马桶堵了而等待的过来重新排队(比如马桶堵塞问题被解决了),或者喊,因为马桶没水而等待的过来重新排队(比如马桶没水问题被解决了)。这样可以控制得更精细一些。不像synchronize里的wait和notify,不管是马桶堵塞还是马桶没水都只能喊:刚才等待的过来排队!假如排队的人进来一看,发现原来只是马桶堵塞问题解决了,而自己渴望解决的问题(马桶没水)还没解决,只好再回去等待(wait),白进来转一圈,浪费时间与资源。

Lock方式与synchronized对应关系:

LockawaitsignalsignalAll

synchronizedwaitnotifynotifyAll

注意:不要在Lock方式锁住的块里调用wait、notify、notifyAll

6利用管道进行线程间通信

原理简单。两个线程,一个操作PipedInputStream,一个操作PipedOutputStream。PipedOutputStream写入的数据先缓存在Buffer中,如果Buffer满,此线程wait。PipedInputStream读出Buffer中的数据,如果Buffer没数据,此线程wait。

jdk1.5中的阻塞队列可实现同样功能。

package io;
import java.io.*;
public class PipedStreamTest {
	public static void main(String[] args) {
		PipedOutputStream ops=new PipedOutputStream();
		PipedInputStream pis=new PipedInputStream();
		try{
			ops.connect(pis);
			//实现管道连接
			new Producer(ops).run();
			new Consumer(pis).run();
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
}
//生产者
class Producer implements Runnable{
	private PipedOutputStream ops;
	public Producer(PipedOutputStream ops)
	{
		this.ops=ops;
	}
	public void run()
	{
		try{
			ops.write("hell,spell".getBytes());
			ops.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}
//消费者
class Consumer implements Runnable{
	private PipedInputStream pis;
	public Consumer(PipedInputStream pis)
	{
		this.pis=pis;
	}
	public void run()
	{
		try{
			byte[] bu=new byte[100];
			int len=pis.read(bu);
			System.out.println(new String(bu,0,len));
			pis.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}

例2 对上面的程序做少许改动就成了两个线程。

package io;
import java.io.*;
public class PipedStreamTest {
	public static void main(String[] args) {
		PipedOutputStream ops=new PipedOutputStream();
		PipedInputStream pis=new PipedInputStream();
		try{
			ops.connect(pis);
			//实现管道连接
			Producer p = new Producer(ops);
			new Thread(p).start();
			Consumer c = new Consumer(pis);
			new Thread(c).start();
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
}
//生产者
class Producer implements Runnable{
	private PipedOutputStream ops;
	public Producer(PipedOutputStream ops)
	{
		this.ops=ops;
	}
	public void run()
	{
		try{
			for (;;){
				ops.write("hell,spell".getBytes());
				ops.close();
			}
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}
//消费者
class Consumer implements Runnable{
	private PipedInputStream pis;
	public Consumer(PipedInputStream pis)
	{
		this.pis=pis;
	}
	public void run()
	{
		try{
			for (;;){
				byte[] bu=new byte[100];
				int len=pis.read(bu);
				System.out.println(new String(bu,0,len));
			}
			pis.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}

例3. 这个例子更加贴进应用

import java.io.*;
public class PipedIO {
	//程序运行后将sendFile文件的内容拷贝到receiverFile文件中
	public static void main(String args[]){
		try{
			//构造读写的管道流对象
			PipedInputStream pis=new PipedInputStream();
			PipedOutputStream pos=new PipedOutputStream();
			//实现关联
			pos.connect(pis);
			//构造两个线程,并且启动。
			new Sender(pos,”c:\text2.txt”).start();
			new Receiver(pis,”c:\text3.txt”).start();
		}
		catch(IOException e){
			System.out.println(“Pipe Error”+ e);
		}
	}
}
//线程发送
class Sender extends Thread{
	PipedOutputStream pos;
	File file;
	//构造方法
	Sender(PipedOutputStream pos, String fileName){
		this.pos=pos;
		file=new File(fileName);
	}
	//线程运行方法
	public void run(){
		try{
			//读文件内容
			FileInputStream fs=new FileInputStream(file);
			int data;
			while((data=fs.read())!=-1){
				//写入管道始端
				pos.write(data);
			}
			pos.close();
		}
		catch(IOException e) {
			System.out.println(“Sender Error” +e);
		}
	}
}
//线程读
class Receiver extends Thread{
	PipedInputStream pis;
	File file;
	//构造方法
	Receiver(PipedInputStream pis, String fileName){
		this.pis=pis;
		file=new File(fileName);
	}
	//线程运行
	public void run(){
		try {
			//写文件流对象
			FileOutputStream fs=new FileOutputStream(file);
			int data;
			//从管道末端读
			while((data=pis.read())!=-1){
				//写入本地文件
				fs.write(data);
			}
			pis.close();
		}
		catch(IOException e){
			System.out.println("Receiver Error" +e);
		}
	}
}

7阻塞队列

阻塞队列可以代替管道流方式来实现进水管/排水管模式(生产者/消费者).JDK1.5提供了几个现成的阻塞队列.现在来看ArrayBlockingQueue的代码如下:

这里是一个阻塞队列

BlockingQueue blockingQ = new ArrayBlockingQueue 10; 

一个线程从队列里取

for(;;){
Object o = blockingQ.take();//队列为空,则等待(阻塞)
} 

另一个线程往队列存

for(;;){
blockingQ.put(new Object());//队列满,则等待(阻塞)
} 

可见,阻塞队列使用起来比管道简单。

8使用Executors、Executor、ExecutorService、ThreadPoolExecutor

可以使用线程管理任务。还可以使用jdk1.5提供的一组类来更方便的管理任务。从这些类里我们可以体会一种面向任务的思维方式。这些类是:

Executor接口。使用方法:

Executor executor = anExecutor;//生成一个Executor实例。
executor.execute(new RunnableTask1()); 

用意:使用者只关注任务执行,不用操心去关注任务的创建、以及执行细节等这些第三方实现者关心的问题。也就是说,把任务的调用执行和任务的实现解耦。

实际上,JDK1.5中已经有该接口出色的实现。够用了。

Executors是一个如同Collections一样的工厂类或工具类,用来产生各种不同接口的实例。

ExecutorService接口它继承自Executor.Executor只管把任务扔进executor()里去执行,剩余的事就不管了。而ExecutorService则不同,它会多做点控制工作。比如:

class NetworkService {
	private final ServerSocket serverSocket;
	private final ExecutorService pool;
	public NetworkService(int port, int poolSize) throws IOException {
		serverSocket = new ServerSocket(port);
		pool = Executors.newFixedThreadPool(poolSize);
	}
	public void serve() {
		try {
			for (;;) {
				pool.execute(new Handler(serverSocket.accept()));
			}
		}
		catch (IOException ex) {
			pool.shutdown();
			//不再执行新任务
		}
	}
}
class Handler implements Runnable {
	private final Socket socket;
	Handler(Socket socket) {
		this.socket = socket;
	}
	public void run() {
		// read and service request
	}
}

ExecutorService(也就是代码里的pool对象)执行shutdown后,它就不能再执行新任务了,但老任务会继续执行完毕,那些等待执行的任务也不再等待了。

任务提交者与执行者通讯

public static void main(String args[])throws Exception {
	ExecutorService executor = Executors.newSingleThreadExecutor();
	Callable task = new Callable(){
		public String call()throws Exception{
			return “test”;
		}
	}
	;
	Future f = executor.submit(task);
	String result = f.get();
	//等待(阻塞)返回结果
	System.out.println(result);
	executor.shutdown();
}

Executors.newSingleThreadExecutor()取得的Executor实例有以下特性:

任务顺序执行.比如:

executor.submit(task1);
executor.submit(task2); 

必须等task1执行完,task2才能执行。

task1和task2会被放入一个队列里,由一个工作线程来处理。即:一共有2个线程(主线程、处理任务的工作线程)。

其它的类请参考JavaDoc

9并发流程控制

本节例子来自温少的Java并发教程,可能会有改动。向温少致敬。

CountDownLatch门插销计数器

启动线程,然后等待线程结束。即常用的主线程等所有子线程结束后再执行的问题。

public static void main(String[] args)throws Exception {
	// TODO Auto-generated method stub
	final int count=10;
	final CountDownLatch completeLatch = new CountDownLatch(count);
	//定义了门插销的数目是10
	for (int i=0;i<count;i++){
		Thread thread = new Thread("worker thread"+i){
			public void run(){
				//do xxxx
				completeLatch.countDown();
				//减少一根门插销
			}
		}
		;
		thread.start();
	}
	completeLatch.await();
	//如果门插销还没减完则等待。
}

JDK1.4时,常用办法是给子线程设置状态,主线程循环检测。易用性和效率都不好。

启动很多线程,等待通知才能开始

public static void main(String[] args) throws Exception {
	// TODO Auto-generated method stub
	final CountDownLatch startLatch = new CountDownLatch(1);
	//定义了一根门插销
	for (int i = 0; i < 10; i++) {
		Thread thread = new Thread("worker thread" + i) {
			public void run() {
				try {
					startLatch.await();
					//如果门插销还没减完则等待
				}
				catch (InterruptedException e) {
				}
				// do xxxx
			}
		}
		;
		thread.start();
	}
	startLatch.countDown();
	//减少一根门插销
}

CycliBarrier. 等所有线程都达到一个起跑线后才能开始继续运行。

public class CycliBarrierTest implements Runnable {
	private CyclicBarrier barrier;
	public CycliBarrierTest(CyclicBarrier barrier) {
		this.barrier = barrier;
	}
	public void run() {
		//do xxxx;
		try {
			this.barrier.await();
			//线程运行至此会检查是否其它线程都到齐了,没到齐就继续等待。到齐了就执行barrier的run函数体里的内容
		}
		catch (Exception e) {
		}
	}
	/**
 * @param args
 */
	public static void main(String[] args) {
		//参数2代表两个线程都达到起跑线才开始一起继续往下执行
		CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
			public void run() {
				//do xxxx;
			}
		}
		);
		Thread t1 = new Thread(new CycliBarrierTest(barrier));
		Thread t2 = new Thread(new CycliBarrierTest(barrier));
		t1.start();
		t2.start();
	}
}

这简化了传统的用计数器+wait/notifyAll来实现该功能的方式。

10并发3定律

Amdahl定律.给定问题规模,可并行化部分占12%,那么即使把并行运用到极致,系统的性能最多也只能提高1/(1-0.12)=1.136倍。即:并行对提高系统性能有上限。

Gustafson定律.Gustafson定律说Amdahl定律没有考虑随着cpu的增多而有更多的计算能力可被使用。其本质在于更改问题规模从而可以把Amdahl定律中那剩下的88%的串行处理并行化,从而可以突破性能门槛。本质上是一种空间换时间。

Sun-Ni定律.是前两个定律的进一步推广。其主要思想是计算的速度受限于存储而不是CPU的速度.所以要充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解.

11由并发到并行

计算机识别物体需要飞速的计算,以至于芯片发热发烫,而人在识别物体时却一目了然,却并不会导致某个脑细胞被烧热烧焦(夸张)而感到不适,是由于大脑是一个分布式并行运行系统,就像google用一些廉价的linux服务器可以进行庞大复杂的计算一样,大脑内部无数的神经元的独自计算,互相分享成果,从而瞬间完成需要单个cpu万亿次运算才能有的效果。试想,如果在并行处理领域有所创建,将对计算机的发展和未来产生不可估量的影响。当然,其中的挑战也可想而知:许多的问题是并不容易轻易就“分割”的了的。

总结

以上就是本文关于java并发问题概述的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

(0)

相关推荐

  • Java并发实例之CyclicBarrier的使用

    最近一直整并发这块东西,顺便写点Java并发的例子,给大家做个分享,也强化下自己记忆,如果有什么错误或者不当的地方,欢迎大家斧正. CyclicBarrier是一种多线程并发控制实用工具,和CountDownLatch非常类似,它也可以实现线程间的计数等待,但是它的功能比CountDownLatch更加复杂且强大. CyclicBarrier的介绍 CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)

  • java并发编程之同步器代码示例

    同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作.最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier和Exchanger 队列同步器AbstractQueuedSynchronizer是用来构建锁或者其他同步组件的基础框架,它内部使用了一个volatiole修饰的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作. 同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽

  • 深入分析java并发编程中volatile的实现原理

    引言 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性".可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值.它在某些情况下比synchronized的开销更小,本文将深入分析在硬件层面上Inter处理器是如何实现Volatile的,通过深入分析能帮助我们正确的使用Volatile变量. 术语定义 术语 英文单词 描述 共享变量 在多个线

  • Java并发底层实现原理学习心得

    我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为汇编语言,然后转为操作系统指令,然后转为1,0,最后CPU进行识别执行. 提到java的并发,我们不由的就会想到java中常见的键字:volatile和synchronized,我们接下来就会从这两个关机字展开分析: volatile的底层实现原理 synchronized的实现原理和应用 volatile 说到volatile,在java

  • Java并发问题之乐观锁与悲观锁

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.再比如Java里面的同步原语synchronized关键字的实现也是悲观锁. 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版

  • 聊聊Java并发中的Synchronized

    1 引言 在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,本文详细介绍了Java SE1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程. 2 术语定义 术语 英文 说明 CAS Compare and Swap 比较并设置.用于在硬件层面上提供原子性操作.在 Intel 处理器中,比较并交换通过指令cmpxch

  • java并发等待条件的实现原理详解

    前言 前面介绍了排它锁,共享锁的实现机制,本篇继续学习AQS中的另外一个内容-Condition.想必学过java的都知道Object.wait和Object.notify,同时也应该知晓这两个方法的使用离不开synchronized关键字.synchronized是jvm级别提供的同步原语,它的实现机制隐藏在jvm实现中.作为Lock系列功能中的Condition,就是用来实现类似 Object.wait和Object.notify 对应功能的. 使用场景 为了更好的理解Lock和Condit

  • java并发学习之BlockingQueue实现生产者消费者详解

    1.介绍 阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满:从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空.并发包下很多高级同步类的实现都是基于BlockingQueue实现的. JDK7提供了以下7个阻塞队列: ArrayBlockingQueue :由数组结构组成的有界阻塞队列. LinkedBloc

  • java并发问题概述

    1什么是并发问题. 多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题. 银行两操作员同时操作同一账户就是典型的例子.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去50元,A先提交,B后提交.最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050.这就是典型的并发问题.如何解决?可以用锁. 2java中synchronized的用法 用法1 public class Test{ public

  • Java并发编程之Semaphore(信号量)详解及实例

    Java并发编程之Semaphore(信号量)详解及实例 概述 通常情况下,可能有多个线程同时访问数目很少的资源,如客户端建立了若干个线程同时访问同一数据库,这势必会造成服务端资源被耗尽的地步,那么怎样能够有效的来控制不可预知的接入量呢?及在同一时刻只能获得指定数目的数据库连接,在JDK1.5 java.util.concurrent 包中引入了Semaphore(信号量),信号量是在简单上锁的基础上实现的,相当于能令线程安全执行,并初始化为可用资源个数的计数器,通常用于限制可以访问某些资源(物

  • Java并发编程之常用的多线程实现方式分析

    本文实例讲述了Java并发编程之常用的多线程实现方式.分享给大家供大家参考,具体如下: 概述 常用的多线程实现方式有2种: 1. 继承Thread类 2. 实现Runnable接口 之所以说是常用的,是因为通过还可以通过JUC(java.util.concurrent)包中的线程池来实现多线程.关于线程池的内容,我们以后会详细介绍:现在,先对的Thread和Runnable进行了解. Thread简介 Thread 是一个类.Thread本身就实现了Runnable接口.它的声明如下: publ

  • java并发编程专题(七)----(JUC)ReadWriteLock的用法

    前面我们已经分析过JUC包里面的Lock锁,ReentrantLock锁和semaphore信号量机制.Lock锁实现了比synchronized更灵活的锁机制,Reentrantlock是Lock的实现类,是一种可重入锁,都是每次只有一次线程对资源进行处理:semaphore实现了多个线程同时对一个资源的访问:今天我们要讲的ReadWriteLock锁将实现另外一种很重要的功能:读写分离锁. 假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁.在没有写操作的时候,两个线

  • Java并发编程之ReentrantLock可重入锁的实例代码

    目录 1.ReentrantLock可重入锁概述2.可重入3.可打断4.锁超时5.公平锁6.条件变量 Condition 1.ReentrantLock可重入锁概述 相对于 synchronized 它具备如下特点 可中断 synchronized锁加上去不能中断,a线程应用锁,b线程不能取消掉它 可以设置超时时间 synchronized它去获取锁时,如果对方持有锁,那么它就会进入entryList一直等待下去.而可重入锁可以设置超时时间,规定时间内如果获取不到锁,就放弃锁 可以设置为公平锁

  • 如何使用JCTools实现Java并发程序

    概述 在本文中,我们将介绍JCTools(Java并发工具)库. 简单地说,这提供了许多适用于多线程环境的实用数据结构. 非阻塞算法 传统上,在可变共享状态下工作的多线程代码使用锁来确保数据一致性和发布(一个线程所做的更改对另一个线程可见). 这种方法有许多缺点: 线程在试图获取锁时可能会被阻塞,在另一个线程的操作完成之前不会取得任何进展-这有效地防止了并行性 锁争用越重,JVM处理调度线程.管理争用和等待线程队列的时间就越多,实际工作就越少 如果涉及多个锁,并且它们以错误的顺序获取/释放,则可

  • 轻轻松松吃透Java并发fork/join框架

    目录 一.概述 二.说一说 RecursiveTask 三. Fork/Join框架基本使用 四.工作顺序图 1.ForkJoinPool构造函数 2.fork方法和join方法 五.使用Fork/Join解决实际问题 1.使用归并算法解决排序问题 2.使用Fork/Join运行归并算法 Fork / Join 是一个工具框架 , 其核心思想在于将一个大运算切成多个小份 , 最大效率的利用资源 , 其主要涉及到三个类 : ForkJoinPool / ForkJoinTask / Recursi

  • Java并发编程之线程状态介绍

    目录 线程状态概述 睡眠sleep方法 等待和唤醒 等待唤醒的一个小例子 线程状态概述 线程由生到死的完整过程: 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态: 线程状态 导致状态发生条件 NEW(新建) 线程刚被创建,但是并未启动.还没调用start方法.MyThread t = new MyThread只有线程对象,没有线程特征. Runna

  • 深入了解Java并发AQS的独占锁模式

    目录 概述 自定义独占锁例子 核心原理机制 源码解析 成员变量 独占锁获取acquire(int) 独占锁释放release(int) 总结 概述 稍微对并发源码了解的朋友都知道,很多并发工具如ReentrantLock.CountdownLatch的实现都是依赖AQS, 全称AbstractQueuedSynchronizer. AQS是一种提供了原子式管理同步状态.阻塞和唤醒线程功能以及队列模型的简单框架.一般来说,同步工具实现锁的控制分为独占锁和共享锁,而AQS提供了对这两种模式的支持.

  • 一文搞懂Java并发AQS的共享锁模式

    目录 概述 自定义共享锁例子 核心原理机制 源码解析 成员变量 共享锁获取acquireShared(int) 共享释放releaseShared(int) 概述 这篇文章深入浅出理解Java并发AQS的独占锁模式讲解了AQS的独占锁实现原理,那么本篇文章在阐述AQS另外一个重要模式,共享锁模式,那什么是共享锁呢? 共享锁可以由多个线程同时获取, 比较典型的就是读锁,读操作并不会产生副作用,所以可以允许多个线程同时对数据进行读操作而不会有线程安全问题,jdk中的很多并发工具比如ReadWrite

随机推荐