Java高新技术——多线程与并发库(上)

本系列文章旨在分享Java5多线程与并法库的高级应用示例,所用到的大多数类均在java.util.concurrent包下。

传统线程技术回顾

package ustc.lichunchun.thread;

/*
 * 创建线程的两种传统方式
 */
public class TraditionalThread {

	public static void main(String[] args) {

		//在Thread子类覆盖的run方法中编写运行代码
		Thread t1 = new Thread(){
			public void run(){
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("1: " + Thread.currentThread().getName());
					System.out.println("2: " + this.getName());
				}
			}
		};
		t1.start();

		//-----------------------------------------------------------

		//在传递给Thread对象的Runnable对象的run方法中编写代码
		Thread t2 = new Thread(new Runnable(){

			@Override
			public void run() {
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("1: " + Thread.currentThread().getName());
				}
			}

		});
		t2.start();

		//-----------------------------------------------------------

		//涉及知识点:匿名内部类对象的构造方法如何调用父类的非默认构造方法
		new Thread(new Runnable(){

			@Override
			public void run() {
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("Runnable: " + Thread.currentThread().getName());
				}
			}

		}){
			public void run(){
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("Thread: " + Thread.currentThread().getName());
				}
			}
		}.start();//Thread: Thread-2
	}

}

生产者消费者模式回顾

package ustc.lichunchun.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Res{
	private String name;
	private int count = 1;
	private boolean flag;

	private Lock lock = new ReentrantLock();
	private Condition producer_con = lock.newCondition();
	private Condition consumer_con = lock.newCondition();

	public void set(String name){
		lock.lock();
		try{
			while(flag){
				try{
					producer_con.await();
				}catch(InterruptedException e){
					//e.printStackTrace();
				}
			}
			this.name = name + "-" + count;
			count++;
			System.out.println(Thread.currentThread().getName()+ "......生产者 ...... " + this.name);
			this.flag = true;
			consumer_con.signal();
		}finally{
			lock.unlock();
		}
	}

	public void get(){
		lock.lock();
		try{
			while(!flag){
				try{
					consumer_con.await();
				}catch(InterruptedException e){
					//e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+ "...消费者 ... " + this.name);
			this.flag = false;
			producer_con.signal();
		}finally{
			lock.unlock();
		}
	}
}

class Producer implements Runnable{
	private Res r;
	Producer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
		while(true){
			r.set("小龙虾");
		}
	}

}

class Consumer implements Runnable{
	private Res r;
	Consumer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
		while(true){
			r.get();
		}
	}

}

public class ProducerConsumerDemo {

	public static void main(String[] args) {
		Res r = new Res();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}

}

传统定时器技术回顾

package ustc.lichunchun.thread;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

//完成一个定时调度的程序,每个2秒打印一次时间

class MyTask extends TimerTask{	//任务调度类都要继承TimerTask

	@Override
	public void run() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		System.out.println("当前的系统时间为:" + sdf.format(new Date()));
	}

}

public class TraditionalTimerDemo {

	public static void main(String[] args) {
		Timer t = new Timer();	//建立Timer类对象
		MyTask mytask = new MyTask();	//定义任务
		t.schedule(mytask, 1000, 2000);	//设置任务的执行,1秒后开始,每2秒重复
	}

}

schedule()与scheduleAtFixedRate()方法的区别:

两者的区别在于重复执行任务时,对于时间间隔出现延迟的情况处理。

|--schedule()方法的执行时间间隔永远是固定的,如果之前出现了延迟的情况,之后也会继续按照设定好的间隔时间来执行

|--scheduleAtFixedRate()方法可以根据出现的延迟时间自动调整下一次间隔的执行时间

一般在web的开发中此内容比较有用,因为要维护一个容器不关闭才可以一直定时操作下去。

package ustc.lichunchun.thread;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TraditionalTimerTest {

	private static int count = 0;

	class MyTimerTaskA extends TimerTask{
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + "......Bombing!");
			new Timer().schedule(new MyTimerTaskB(), 4000);
		}
	}

	class MyTimerTaskB extends TimerTask{
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + "......Bombing!");
			new Timer().schedule(new MyTimerTaskA(), 2000);
		}
	}

	public static void main(String[] args) {

		//1.主线程每个1秒打印一次当前秒数,定时器Timer线程设定为10秒后执行单次bombing打印
		new Timer().schedule(new TimerTask() {

			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "......Bombing!");
			}
		}, 10000);

		while(true){
			System.out.println(Thread.currentThread().getName() + "..." + new Date().getSeconds());
			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		//--------------------------------------------------------------------

		//2.Timer线程10秒后, 每隔2秒打印一次bombing
		new Timer().schedule(new TimerTask() {

			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "......Bombing!");
			}
		}, 10000, 2000);

		//--------------------------------------------------------------------

		//3.1.Timer线程每2秒、4秒各炸一次,循环往复(方法一)
		new Timer().schedule(new TraditionalTimerTest().new MyTimerTaskA(), 2000);
		while(true){
			System.out.println(Thread.currentThread().getName() + "..." + new Date().getSeconds());
			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}

		//--------------------------------------------------------------------

		//3.2.Timer线程每2秒、4秒各炸一次,循环往复(方法二)
		class MyTimerTaskC extends TimerTask{
			@Override
			public void run() {
				count = (count + 1) % 2;
				System.out.println(Thread.currentThread().getName() + "......Bombing!");
				new Timer().schedule(new MyTimerTaskC(), 2000 + 2000*count);
			}
		}
		new Timer().schedule(new MyTimerTaskC(), 2000);
		while(true){
			System.out.println(Thread.currentThread().getName() + "..." + new Date().getSeconds());
			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}

}

传统线程互斥技术

package ustc.lichunchun.thread;

import java.util.concurrent.locks.ReentrantLock;

/*
 * 线程要运行的代码就相当于共享资源厕所的一个坐席,互斥锁就相当于厕所坐席里的门闩。
 */
public class TraditionalThreadSynchronized {

	public static void main(String[] args) {
		new TraditionalThreadSynchronized().init();

		/*
		final ReentrantLock lock = new ReentrantLock();
		Thread t = new Thread(){
			public void run() {
				lock.lock();
				System.out.println("thread t execute");
				lock.unlock();
			};
		};
		lock.lock();
		lock.lock();
		t.start();
		Thread.sleep(200);
		System.out.println("release one once");
		lock.unlock();
		上面的代码会出现死锁,因为主线程2次获取了锁,但是却只释放1次锁,导致线程t永远也不能获取锁。
		*/
	}
	private void init(){
		final Outputer outputer = new Outputer();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output("lichunchun");
				}
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output2("alibaba");
				}
			}
		}).start();
	}
	static class Outputer{
		public void output(String name){
			int len = name.length();
			synchronized (Outputer.class) {	//this<-->output1() || Outputer.class<-->output2()
				for(int i = 0; i < len; i++){
					System.out.print(name.charAt(i));
				}
				System.out.println();
				//output2(name);//synchronized也是一把可重入锁,
			}
		}
		public synchronized void output1(String name){
			int len = name.length();
			for(int i = 0; i < len; i++){
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}
		public static synchronized void output2(String name){
			int len = name.length();
			for(int i = 0; i < len; i++){
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}
	}

}

传统线程同步通信技术

这里,我们通过一道面试题来讲解。

子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次。

方法一:

package ustc.lichunchun.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Business{
	private boolean bShouldSub = true;
	public synchronized void sub(int i){
		while(!bShouldSub){	//线程有可能在没有被通知的时候"伪唤醒",所以用while判断更加可靠
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j = 1; j <= 10; j++){
			System.out.println("sub thread sequence of " + j + ", loop of " + i);
		}
		bShouldSub = false;
		this.notify();
	}
	public synchronized void main(int i){
		while(bShouldSub){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j = 1; j <= 100; j++){
			System.out.println("main thread sequence of " + j + ", loop of " + i);
		}
		bShouldSub = true;
		this.notify();
	}
}
public class TraditionalThreadCommunication {
	public static void main(String[] args) {
		final Business business = new Business();
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 1; i <= 50; i++){
					business.sub(i);
				}
			}
		}).start();
		for(int i = 1; i <= 50; i++){
			business.main(i);
		}
	}
}

方法二:

/*
 * 下面使用jdk5中的并发库来实现
 */
class Business{
	private boolean bShouldSub = true;
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	public void sub(int i){
		lock.lock();
		try{
			while(!bShouldSub){
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			for(int j = 1; j <= 10; j++){
				System.out.println("sub thread sequence of " + j + ", loop of " + i);
			}
			bShouldSub = false;
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
	public void main(int i){
		lock.lock();
		try{
			while(bShouldSub){
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			for(int j = 1; j <= 100; j++){
				System.out.println("main thread sequence of " + j + ", loop of " + i);
			}
			bShouldSub = true;
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
}
public class TraditionalThreadCommunication{
	public static void main(String[] args) {
		final Business business = new Business();
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 1; i <= 50; i++){
					business.sub(i);
				}
			}
		}).start();
		for(int i = 1; i <= 50; i++){
			business.main(i);
		}
	}
}

线程范围内的共享变量原理

package ustc.lichunchun.thread;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/*
 * 线程范围内的共享变量,各线程之间相互独立
 */
public class ThreadScopeShareData {
	//private static int data = 0;
	private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();
	public static void main(String[] args) {
		for(int i = 0; i < 2; i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data: " + data);
					threadData.put(Thread.currentThread(), data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	static class A{
		public void get(){
			int data = threadData.get(Thread.currentThread());
			System.out.println("A from " + Thread.currentThread().getName() + " get data: " + data);
		}
	}
	static class B{
		public void get(){
			int data = threadData.get(Thread.currentThread());
			System.out.println("B from " + Thread.currentThread().getName() + " get data: " + data);
		}
	}
}

ThreadLocal类实现线程范围内共享变量

线程范围内共享变量的应用:ThreadLocal类的实用技巧

由上一节的原理代码示例和插图可以知道,ThreadLocal类的作用和目的:

用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。

----对基本类型的数据的封装,这种应用相对很少见。

----对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。

实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。

package ustc.lichunchun.thread;

import java.util.Random;
/*
定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,
接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,
就可以看到多个类在同一个线程中共享同一份数据。
*/
public class ThreadLocalTest{
	private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
	private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
	public static void main(String[] args) {
		for(int i = 0; i < 2; i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data: " + data);
					x.set(data);
					MyThreadScopeData myData = new MyThreadScopeData();
					myData.setName("name:"+data);
					myData.setAge(data);
					myThreadScopeData.set(myData);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	static class A{
		public void get(){
			int data = x.get();
			System.out.println("A from " + Thread.currentThread().getName()
					+ " get data :" + data);
			MyThreadScopeData myData = myThreadScopeData.get();
			System.out.println("A from " + Thread.currentThread().getName()
					+ " getMyData: " + myData.getName() + "," +
					myData.getAge());
		}
	}
	static class B{
		public void get(){
			int data = x.get();
			System.out.println("B from " + Thread.currentThread().getName()
					+ " get data :" + data);
			MyThreadScopeData myData = myThreadScopeData.get();
			System.out.println("B from " + Thread.currentThread().getName()
					+ " getMyData: " + myData.getName() + "," +
					myData.getAge());
		}
	}
}

进阶版案例:

package ustc.lichunchun.thread;

import java.util.Random;

public class ThreadLocalTest {

	public static void main(String[] args) {
		for (int i = 0; i < 2; i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data: " + data);
					MyThreadScopeData.getInstance().setName("name:" + data);
					MyThreadScopeData.getInstance().setAge(data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	static class A{
		public void get() {
			MyThreadScopeData myData = MyThreadScopeData.getInstance();
			System.out.println("A from " + Thread.currentThread().getName()
					+ " getMyData: " + myData.getName() + ", age:" +
					myData.getAge());
		}
	}
	static class B{
		public void get() {
			MyThreadScopeData myData = MyThreadScopeData.getInstance();
			System.out.println("B from " + Thread.currentThread().getName()
					+ " getMyData: " + myData.getName() + ", age:" +
					myData.getAge());
		}
	}
}
//基于饿汉单例模式的改造,每个线程对应一个MyThreadScopeData实例
class MyThreadScopeData{
	private MyThreadScopeData(){}

	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
	public static MyThreadScopeData getInstance(){
		MyThreadScopeData instance = map.get();
		if(instance == null){
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}

	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}

ThreadLocal的应用场景

1.订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,

如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。

2.银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,

它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。

3.例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,

对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。

多个线程之间共享数据的方式探讨

1. 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据。

例如,四个窗口售100张票:

package ustc.lichunchun.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MultiThreadShareData {

	public static void main(String[] args) {
		ShareData1 data1 = new ShareData1();
		new Thread(data1).start();
		new Thread(data1).start();
		new Thread(data1).start();
		new Thread(data1).start();
	}

}
class ShareData1 implements Runnable{
	private int count = 100;
	@Override
	public void run() {
		while(true){
			synchronized(this){
				if(this.count > 0){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+": "+count--);
				}else{
					break;
				}
			}
		}

	}
}

2. 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下三种方式来实现这些Runnable对象之间的数据共享:

(1)将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

public class MultiThreadShareData{
	public static void main(String[] args) {
		ShareData data = new ShareData();//含有共享数据的对象只有一个,并作为参数传递给各个Runnable线程对象作为其各自的任务
		for(int i = 0; i < 2; i++){
			new Thread(new Inc(data)).start();
			new Thread(new Dec(data)).start();
		}
	}
}
class ShareData{
	private int j = 0;//共享数据
	private Lock lock = new ReentrantLock();
	//private Condition con = lock.newCondition();//可用于等待唤醒机制,配上flag标志

	//下面是对共享数据进行要进行的两个操作,定义在这个对象中,可以方便的实现互斥
	public void inc(){
		lock.lock();
		j++;
		System.out.println("j="+(this.j-1)+" increase by "+Thread.currentThread().getName()+": j="+this.j);
		lock.unlock();
	}
	public void dec(){
		lock.lock();
		j--;
		System.out.println("j="+(this.j+1)+" decrease by "+Thread.currentThread().getName()+": j="+this.j);
		lock.unlock();
	}
}
class Inc implements Runnable{//对共享数据进行加法的线程任务
	private ShareData data;
	public Inc(ShareData data){
		this.data = data;
	}
	@Override
	public void run() {
		while(true){
			data.inc();
		}
	}
}
class Dec implements Runnable{//对共享数据进行减法的线程任务
	private ShareData data;
	public Dec(ShareData data){
		this.data = data;
	}
	@Override
	public void run() {
		while(true){
			data.dec();
		}
	}
}

(2)将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

public class MultiThreadShareData{
	private static ShareData data = new ShareData();
	public static void main(String[] args) {
		//final ShareData data = new ShareData();//方法的局部final变量也可以
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(true){
					data.inc();
				}
			}
		}).start();
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(true){
					data.dec();
				}
			}
		}).start();
	}
}
class ShareData{
	private int j = 0;
	public synchronized void inc(){
		j++;
		System.out.println("j="+(this.j-1)+" increase by "+Thread.currentThread().getName()+": j="+this.j);
	}
	public synchronized void dec(){
		j--;
		System.out.println("j="+(this.j+1)+" decrease by "+Thread.currentThread().getName()+": j="+this.j);
	}
}

(3)将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量(内部类可以直接操作外部类成员变量),每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

public class MultiThreadShareData{
	private int j = 0;
	public static void main(String[] args) {
		MultiThreadShareData data = new MultiThreadShareData();
		Inc inc = data.new Inc();
		Dec dec = data.new Dec();
		for(int i = 0; i < 2; i++){
			new Thread(inc).start();
			new Thread(dec).start();
		}
	}
	private synchronized void inc(){
		j++;
		System.out.println(Thread.currentThread().getName()+" inc: "+j);
	}
	private synchronized void dec(){
		j--;
		System.out.println(Thread.currentThread().getName()+" dec: "+j);
	}
	class Inc implements Runnable{
		@Override
		public void run() {
			for(int i = 0; i < 100; i++){
				inc();
			}
		}
	}
	class Dec implements Runnable{
		@Override
		public void run() {
			for(int i = 0; i < 100; i++){
				dec();
			}
		}
	}
}

总结:要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

原子操作

1. 何谓原子操作?

Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的Atomic是指不能分割成若干部分的意思。如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的。通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成)

在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。

2. JDK1.5的原子包:java.util.concurrent.atomic

这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。其中的类可以分成4组

  • AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • AtomicIntegerArray,AtomicLongArray
  • AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

Atomic类的作用

  • 使得让对单一数据的操作,实现了原子化
  • 使用Atomic类构建复杂的,无需阻塞的代码
    • 访问对2个或2个以上的atomic变量(或者对单个atomic变量进行2次或2次以上的操作)通常认为是需要同步的,以达到让这些操作能被作为一个原子单元。

3. AtomicBoolean , AtomicInteger, AtomicLong, AtomicReference

这四种基本类型用来处理布尔,整数,长整数,对象四种数据。

  • 构造函数(两个构造函数)

    • 默认的构造函数:初始化的数据分别是false,0,0,null
    • 带参构造函数:参数为初始化的数据
  • set( )和get( )方法:可以原子地设定和获取atomic的数据。类似于volatile,保证数据会在主存中设置或读取
  • getAndSet( )方法
    • 原子的将变量设定为新数据,同时返回先前的旧数据
    • 其本质是get( )操作,然后做set( )操作。尽管这2个操作都是atomic,但是他们合并在一起的时候,就不是atomic。在Java的源程序的级别上,如果不依赖synchronized的机制来完成这个工作,是不可能的。只有依靠native方法才可以。
  • compareAndSet( ) 和weakCompareAndSet( )方法
    • 这两个方法都是conditional modifier方法。这2个方法接受2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。
  • 对于AtomicInteger、AtomicLong还提供了一些特别的方法。getAndIncrement( )、incrementAndGet( )、getAndDecrement( )、decrementAndGet ( )、addAndGet( )、getAndAdd( )以实现一些加法,减法原子操作。(注意 --i、++i不是原子操作,其中包含有3个操作步骤:第一步,读取i;第二步,加1或减1;第三步:写回内存)

4. 示例

    例如,类 AtomicLong 和 AtomicInteger 提供了原子增量方法。一个应用程序将按以下方式生成序列号:

class Sequencer {
<span style="white-space:pre">	</span>private AtomicLong sequenceNumber = new AtomicLong(0);
<span style="white-space:pre">	</span>public long next() { return sequenceNumber.getAndIncrement(); }
}

AtomicBoolean使用示例:

private AtomicBoolean running = new AtomicBoolean(false);
	@Override
	protected Object execute() throws Exception {
		if (running.compareAndSet(false, true)) {
			try {
			//TODO
			} finally {
				running.set(false);
			}
		}
	}
时间: 2024-10-10 14:49:39

Java高新技术——多线程与并发库(上)的相关文章

Java复习——多线程与并发库

开启一个线程 实现一个线程的方式有两种:继承Thread类.实现Runnable接口.这两种方法都需要重写Run方法,具体的线程逻辑代码写在Run方法中.其实Thread类就实现了Runnable接口,但是并没有什么说法是使用哪种方式存在效率高低的问题,推荐使用实现Runnable接口的方式,因为更加面向对象,而且实现一个接口比继承一个类更灵活.我们可以使用匿名内部类的方式很方便的开启一个线程(使用Tread类的start方法开启一个线程): Thread : new Thread(){ pub

Java多线程与并发库高级应用之公共屏障点CyclicBarrier

一个小队去登山,每位队员登山的速度不同.山上有几个集合点,在每一集合点处,先到达的队员只有等后面的队员全部到达集合点后才能继续向下一个集合点出发. JDK1.5提供的CyclicBarrier模拟了这种情况.每一个线程相当于一个登山队员,CyclicBarrier相当于山上的集合点.只有等所有线程都执行到了CyclicBarrier后才可以继续向下执行. CyclicBarrier允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程

Java多线程与并发库高级应用之信号量Semaphore

JDK1.5提供了一个计数信号量Semaphore类.Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目,并提供了同步机制. Semaphore提供了两个构造器来创建对象: 1)Semaphore(int permits):创建具有给定的许可数和非公平的公平设置的Semaphore. 2)Semaphore(int permits, boolean fair):创建具有给定的许可数和给定的公平设置的Semaphore.如果此信号量保证在争用时按先进先出的顺序授予许可,则为

Java多线程与并发库高级应用之阻塞队列BlockingQueue

JDK1.5提供了阻塞队列接口BlockingQueue,它是一个有界阻塞队列.BlockingQueue实现是线程安全的,可以安全地与多个生产者和多个使用者一起使用. 使用时用其实现类 ArrayBlockingQueue,它一个由数组支持的有界阻塞队列.此队列按 FIFO(先进先出)原则对元素进行排序.队列的头部 是在队列中存在时间最长的元素.队列的尾部是在队列中存在时间最短的元素.新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素. 这是一个典型的"有界缓存区",固定

Java多线程与并发库高级应用之线程数据交换Exchanger

JDK1.5提供了Exchanger用于两个线程的数据交换.两个线程先后到达交换点,先到达的线程会等待后到达的线程,然后两个线程互相交换数据,交换后双方持对方的数据. Exchanger只提供了一个构造器: Exchanger():创建一个新的Exchanger. Exchanger中也只有两个方法: V exchange(V x): 等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象. V exchange(V x, long timeout,

Java多线程与并发库高级应用之倒计时计数器

CountDownLatch 类是一个倒计时计数器,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待.用给定的计数初始化 CountDownLatch.由于调用了countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞.之后,会释放所有等待的线程,await 的所有后续调用都将立即返回. CountDownLatch 是一个通用同步工具,它有很多用途.将计数1初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 c

[Java] 转:多线程 (并发)总结

一概念 二创建多线程方法 三线程常用方法不完整可以自己查阅JDK文档 四线程的生命周期与转换 五同步 六竞争者消费者 七线程池 八JDK 线程工具 线程基础: 1. 创建 2. 状态切换 3. sleep与wait的区别 前者使线程阻塞固定时间后进入Runnable状态,后者使用notify后可以处于可执行状态. 4. synchroized 与 Lock 区别 synchroized 可以针对当前对象.某变量设置相应的对象锁 lock 控制粒度更细,使用ReentrantLook.look()

【Java多线程与并发库】4.传统线程同步通信技术

我们先通过一道面试题来了解传统的线程同步通信. 题目:子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次,请写出程序. 我没有看答案,先用自己的思路写了一段代码,有一些是借鉴传统的“生产者与消费者”的多线程模型写出来的:[java] view plain copy 在CODE上查看代码片派生到我的代码片package cn.edu.hpu.test; /** * 要求的操作: * 子线程循环10次,接着主线程循环100次,接着又回

Java多线程与并发库高级应用-java5线程并发库

java5 中的线程并发库 主要在java.util.concurrent包中 还有 java.util.concurrent.atomic子包和java.util.concurrent.lock子包