java并发

线程基础

1.Java 线程的基本概念

在操作系统中两个比较容易混淆的概念是进程(Process)和线程(Thread)。操作系统中的进程是资源的组织单位。进程有一个包含了程序内容和数据的地址空间,以及其他的资源,包括打开的文件、子进程和信号处理器等。不同进程的地址空间是相互隔离的。而线程表示的是程序的执行流程,是CPU调度的基本单位。线程有自己的程序计数器、寄存器、栈和帧等引入线程的动机在于操作系统中阻塞式I/O的存在。当一个线程所执行的I/O被阻塞的时候,同一个进程的其他线程可以使用CPU来进行计算。这样的话,就提高了应用的执行效率。线程的概念在主流的操作系统和编程语言中都得到了支持。(infoQ:Java深度历险)。

2.线程的状态

线程状态

线程安全

1.并发内存模型

细说java多线程之内存可见性

2.volatile

通过较低层次的锁(JVM会向处理器发送一条LOCK指令)实现内存屏障,保证可见性。 JDK1.7并发包里新增一个队列集合类LinkedTranQueue,在使用Volatile变量时,用一种追加字节的方式来优化队列出队和入队的性能。

3.final

在JMM中要求final域(属性)的初始化动作必须在构造方法return之前完成。换言之,一个对象创建以及赋值给一个引用是两个动作,对象创建还需要经历分配空间和属性初始化的过程,普通的属性初始化允许发生在构造方法 return之后(指令重排序).

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

上边的代码并不能保证j = 4,因为不是final修饰的。

public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  // bad construction - allowing this to escape  逃逸问题
  global.obj = this;
}

那么,从global.obj中读取this的引用线程不会保证读取到的x的值为3。<br> 另外,如果final 所修饰的不是普通变量,而是数组、对象,那么它能保证自己本身的初始化,但是它作为对象,对内部的属性是无法保证的。

4.栈封闭

栈封闭算是一种概念,也就是线程操作的数据都是私有的,不会与其他的线程共享数据。简单来说,如果每个线程所访问的JVM区域是隔离的,那么这个系统就像一个单线程系统一样简单了,叫做栈 封闭。

一个是 使用局部变量,另一个就是 ThreadLocal.

5.ThreadLocal

ThreadLocal可以放一个线程级别的变量,但是它本身可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全。

数据库事务前提必须保证是用一个连接,当我们不用框架、不用连接池,使用原生javaWeb来操作时,connection怎么管理呢?

获取连接放入到ThreadLocal中,关闭连接时remove掉。

Spring使用Aop切入业务代码,会根据对应的事物管理器提取出相应的事物对象,DataSourceTransactionManager,就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。并且Spring重写了其中的getConnecttion(),该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。

不是说共享变量无法做到线程私有吗?ThreadLocal是怎么做到的?

原子性和锁

1.synchronized

Java中的每一个对象都可以作为锁,悲观锁。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块, 锁是synchronized括号里配置的对象。 synchronized的开销比较大,JDK1.8做了很多优化,基本和锁差不多了,推荐使用synchronized(道听途说的)。

2.什么是乐观锁

悲观锁意味着必须发生锁操作,不论多个线程会不会发生冲突都必须加锁。悲观锁将会产生非常多的指令开销来用以同步,甚至由于锁粒度的使用不当导致不应该锁住的代码被锁住了。而实际在系统中,细化锁粒度后,即使并发量很大,同一个临界区的征用概率也不会太大。

现在CPU基本都会提供一种CAS指令机制,这种机制就是先获取Old值,然后计算出New值,基于可见性的变量检测是否被修改,若没有修改,则回写到主内存表示成功。这个步骤大概几条指令就可以完成,如果发生征用则只有一个线程能成功,与悲观锁相比,绝大部分情况下没有那么大开销。

这种CAS机制子在java中应用到两个抽象层次: 一是应用到JVM级别本身的synchronized锁里面;二是应用到基于Java语言本身这个抽象层次上面,提供一种宏观上的锁机制,在某些时候可以达到更为细粒度的原子处理,这种CAS应用十分广泛,尤其是基于它来做自旋。

3.Atomic

Atomic 原子变量可以分为4种类型:

基本变量操作: 分别对Boolean、Integer、Long进行操作,对应的类名为AtomicBoolean、AtomicInteger和AtomicLong <br> 引用(reference) 操作:也就是对象引用的操作,可以做到原子级别,当多个线程对同一个引用发生修改时,只会有一个成功。对于这种操作还存在ABA问题,所以会有多个类处理引用的原子操作,如:AtomicReference, AtomicStampedRerence, AtomicMarkableReference <br> 数组操作:操作的不是数组,而是数组中的每一个元素。针对每一个元素的操作都是原子的。AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray <br> Updater: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater

4.Lock

读写锁 ReadWriteLock

基本操作

Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也是一个对象。两个线程执行的代码要实现同步互斥的效果,他们必须用同一个Lock对象。锁是在代表要操作的资源的类的内部方法中,而不是在线程代码中,想想上厕所。

读写锁: 分为读锁和写锁,多个读锁不互斥,读写锁互斥,写锁与写锁互斥,这是由JVM自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人呢在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁。

package com.ddyblackhat.javase.thread;

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁测试
 * 读与读不互斥, 读与写互斥,写与写互斥
 * @author dudy
 *
 */
public class ReadWriteLockTest {

	public static void main(String[] args) {
		final Queque3 q = new Queque3();

		for (int i = 0; i < 3; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					q.get();
				}
			}).start();

			new Thread(new Runnable() {
				@Override
				public void run() {
					q.set(new Random().nextInt(100));
				}
			}).start();
		}

//		Thread-0 be ready to read data!
//		Thread-0 have get the data :0
//      写与写,读与写互斥
//		Thread-1 be ready to set data
//		Thread-1 have set the data :46
//		Thread-3 be ready to set data
//		Thread-3 have set the data :92
//		Thread-5 be ready to set data
//		Thread-5 have set the data :60
//      读与读不互斥
//		Thread-4 be ready to read data!
//		Thread-2 be ready to read data!
//		Thread-2 have get the data :60
//		Thread-4 have get the data :60

	}

}

// 操作的资源对象类
class Queque3 {

	private int data;
    // 获取读写锁对象
	ReadWriteLock rw = new ReentrantReadWriteLock();
	public int get() {
		try {
			// 读锁
			rw.readLock().lock();
			System.out.println(Thread.currentThread().getName() + " be ready to read data!");
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName() + " have get the data :" + data);
			return this.data;
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			rw.readLock().unlock();
		}
		return 0;
	}

	public void set(int data) {
		try {
			// 写锁
			rw.writeLock().lock();
			System.out.println(Thread.currentThread().getName() + " be ready to set data");
			Thread.sleep(1000);
			this.data = data;
			System.out.println(Thread.currentThread().getName() + " have set the data :" + data);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			rw.writeLock().unlock();
		}
	}

}

简单缓存系统

Hibernate中session.get() 和 load(),load代理缓存,只不过是缓存的是一个对象。

//private Map<String, Object> cache = new ConcurrentHashMap<String, Object>();
private Map<String, Object> cache = new HashMap<String, Object>();
volatile boolean cacheValid = false;
volatile Object data = null;

ReadWriteLock rw = new ReentrantReadWriteLock();

public Object getDataByRWLock(String key) { // data 要内存保证可见性
	rw.readLock().lock();
	try {
		data = cache.get(key);
		//if (!cacheValid) {
		if (data == null) {
			// 这里要写入数据,所以要先释放掉读锁,上写锁
			rw.readLock().unlock();
			rw.writeLock().lock(); // ①
			try {
				//if (!cacheValid) { // 再一次判断,因为可能多个线程同时进入执行到①
				if (data == null) {
					data = new Random().nextInt(1000);
					cache.put(key, data);
					cacheValid = true;
					System.out.println("data has write : " + data);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				rw.writeLock().unlock();
			}
			rw.readLock().lock();
		}

	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		rw.readLock().unlock();
	}
	return data;
}
Q:注意点
  • 在同一个线程中,持有读锁后,不能直接调用写锁的lock方法。会造成死锁,通被称为读锁不可升级。先读锁unlock后再写锁lock。

  • 在同一个线程中,持有写锁后,可调用读锁的lock方法,在此之后如果调用写锁的unlock方法,那么当前锁将降级为读锁。

API 文档中: 在 writer 释放写入锁时,reader 和 writer 都处于等待状态,在这时要确定是授予读取锁还是授予写入锁。Writer 优先比较普遍,因为预期写入所需的时间较短并且不那么频繁。Reader 优先不太普遍,因为如果 reader 正如预期的那样频繁和持久,那么它将导致对于写入操作来说较长的时延。公平或者“按次序”实现也是有可能的。<br> 在 reader 处于活动状态而 writer 处于等待状态时,确定是否向请求读取锁的 reader 授予读取锁。Reader 优先会无限期地延迟 writer,而 writer 优先会减少可能的并发。<br> 确定是否重新进入锁:可以使用带有写入锁的线程重新获取它吗?可以在保持写入锁的同时获取读取锁吗?可以重新进入写入锁本身吗?<br> 可以将写入锁在不允许其他 writer 干涉的情况下降级为读取锁吗?可以优先于其他等待的 reader 或 writer 将读取锁升级为写入锁吗? 当评估给定实现是否适合您的应用程序时,应该考虑所有这些情况。

condition

synchronized与wait()和notify()/notifyAll()方法结合可以实现等待/通知模型,ReentrantLock同样可以,但是需要借助Condition,且Condition有更好的灵活性,具体体现在: 1.一个Lock 里边可以创建多个Condition实例,实现多路通知 2.notify()方法进行通知时,被通知的线程时Java虚拟机随机选择的,但是ReentranLock结合Condition可以实现有选择性的通知,这是非常重要的。

看下边的ABC打印输出的例子,这个例子感觉不好。

/**
 * java contitionTest 使用 Condition实现 传统的例子, 一个数 +1 -1,循环5次
 *                                       面试 ABC打印输出的例子
 * @author dudy
 *
 */
public class ConditionTest {

	public static void main(String[] args) {
		final ABCPrint p = new ABCPrint();

		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int i = 0; i < 5; i++) {
					p.printA();
				}
			}
		}).start();

		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int i = 0; i < 5; i++) {
					p.printB();
				}
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int i = 0; i < 5; i++) {
					p.printC();
				}
			}
		}).start();

	}

}

class ABCPrint {
	private Lock lock = new ReentrantLock();

	private int state = 0;
	Condition conditonA = lock.newCondition();
	Condition conditonB = lock.newCondition();
	Condition conditonC = lock.newCondition();

	public void printA() {
		lock.lock();
		try {
			while(state != 0){// 如果state != 0 ,当前线程就等待。
				conditonA.await();
			}
			System.out.println("A");
			state = 1;
			conditonB.signal(); // 通知特定的线程来执行
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void printB() {
		lock.lock();
		try {
			while(state != 1){
				conditonB.await();
			}
			System.out.println("B");
			state = 2;
			conditonC.signal();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void printC() {
		lock.lock();
		try {
			while(state != 2){
				conditonC.await();
			}
			System.out.println("C");
			System.out.println("---------------------------");
			state = 0;
			conditonA.signal();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

API中有一个 阻塞队列的例子,ArrayBlockingQueue类似的功能。

6.并发编程核心AQS原理

7.锁的自身优化方法

常见并发编程工具

Semaphore & CyclicBarrier & CountDownLatch

Semaphore 信号量

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。 类似于 10个人上厕所,只有3个坑,走了一个人之后下一个人才可以。

SemaphoreTest.java

public class SemaphoreTest {
	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		final Semaphore sp = new Semaphore(3,true);
		for (int i = 0; i < 10; i++) {
			Runnable runnable = new Runnable() {
				public void run() {
					try {
						sp.acquire();
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					System.out.println(
							"线程" + Thread.currentThread().getName() + "进入,当前已有" + (3 - sp.availablePermits()) + "个并发");
					try {
						Thread.sleep((long) (Math.random() * 10000));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("线程" + Thread.currentThread().getName() + "即将离开");
					sp.release();
					// 下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
					System.out.println(
							"线程" + Thread.currentThread().getName() + "已离开,当前已有" + (3 - sp.availablePermits()) + "个并发");
				}
			};
			service.execute(runnable);
		}
		
		service.shutdown();
	}

}

执行结果

线程pool-1-thread-1进入,当前已有3个并发
线程pool-1-thread-2进入,当前已有3个并发
线程pool-1-thread-3进入,当前已有3个并发
线程pool-1-thread-1即将离开
线程pool-1-thread-1已离开,当前已有2个并发
线程pool-1-thread-4进入,当前已有3个并发
线程pool-1-thread-2即将离开
线程pool-1-thread-2已离开,当前已有2个并发
线程pool-1-thread-5进入,当前已有3个并发
线程pool-1-thread-3即将离开
线程pool-1-thread-3已离开,当前已有2个并发
线程pool-1-thread-7进入,当前已有3个并发
线程pool-1-thread-4即将离开
线程pool-1-thread-4已离开,当前已有2个并发
线程pool-1-thread-9进入,当前已有3个并发
线程pool-1-thread-7即将离开
线程pool-1-thread-7已离开,当前已有2个并发
线程pool-1-thread-6进入,当前已有3个并发
线程pool-1-thread-5即将离开
线程pool-1-thread-5已离开,当前已有2个并发
线程pool-1-thread-10进入,当前已有3个并发
线程pool-1-thread-9即将离开
线程pool-1-thread-9已离开,当前已有2个并发
线程pool-1-thread-8进入,当前已有3个并发
线程pool-1-thread-6即将离开
线程pool-1-thread-6已离开,当前已有2个并发
线程pool-1-thread-8即将离开
线程pool-1-thread-8已离开,当前已有1个并发
线程pool-1-thread-10即将离开
线程pool-1-thread-10已离开,当前已有0个并发
CyclicBarrier

CyclicBarrier(循环路障):维护一个计数器,等待这个CyclicBarrier的线程必须等到计数器达到某个值时,才可以继续。好比整个公司的人员利用周末时间集体去郊游一样,先各自从家出发到公司集合,再同时出发到公园游玩,在指定地点集合后再同时开始就餐。

public class CyclicBarrierTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		final  CyclicBarrier cb = new CyclicBarrier(3);
		for(int i=0;i<3;i++){
			Runnable runnable = new Runnable(){
					public void run(){
					try {
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));						
						cb.await();
						
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
						cb.await();	
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));						
						cb.await();						
					} catch (Exception e) {
						e.printStackTrace();
					}				
				}
			};
			service.execute(runnable);
		}
		service.shutdown();
	}
}

运行结果:

线程pool-1-thread-2即将到达集合地点1,当前已有1个已经到达,正在等候
线程pool-1-thread-3即将到达集合地点1,当前已有2个已经到达,正在等候
线程pool-1-thread-1即将到达集合地点1,当前已有3个已经到达,都到齐了,继续走啊
线程pool-1-thread-1即将到达集合地点2,当前已有1个已经到达,正在等候
线程pool-1-thread-3即将到达集合地点2,当前已有2个已经到达,正在等候
线程pool-1-thread-2即将到达集合地点2,当前已有3个已经到达,都到齐了,继续走啊
线程pool-1-thread-2即将到达集合地点3,当前已有1个已经到达,正在等候
线程pool-1-thread-1即将到达集合地点3,当前已有2个已经到达,正在等候
线程pool-1-thread-3即将到达集合地点3,当前已有3个已经到达,都到齐了,继续走啊
CountDownLatch

一个或者是一部分线程 ,等待另外一部线程都完成了,再继续执行

/**
 *  一个或者是一部分线程 ,等待另外一部线程都完成了,再继续执行
 * @author dudy
 *
 */
public class CountdownLatchTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		final CountDownLatch cdOrder = new CountDownLatch(1);
		final CountDownLatch cdAnswer = new CountDownLatch(3);		
		for(int i=0;i<3;i++){
			Runnable runnable = new Runnable(){
					public void run(){
					try {
						System.out.println("线程" + Thread.currentThread().getName() + 
								"正准备接受命令");						
						cdOrder.await();
						System.out.println("线程" + Thread.currentThread().getName() + 
						"已接受命令");								
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"回应命令处理结果");						
						cdAnswer.countDown();						
					} catch (Exception e) {
						e.printStackTrace();
					}				
				}
			};
			service.execute(runnable);
		}		
		try {
			Thread.sleep((long)(Math.random()*10000));
		
			System.out.println("线程" + Thread.currentThread().getName() + 
					"即将发布命令");						
			cdOrder.countDown();
			System.out.println("线程" + Thread.currentThread().getName() + 
			"已发送命令,正在等待结果");	
			cdAnswer.await();
			System.out.println("线程" + Thread.currentThread().getName() + 
			"已收到所有响应结果");	
		} catch (Exception e) {
			e.printStackTrace();
		}				
		service.shutdown();

	}
}

参考:

张孝祥张老师多线程视频。

Java特种兵第5章。

并发编程网

转载于:https://my.oschina.net/ddyblackhat/blog/682236

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值