java并发编程——线程异常处理\资源共享的问题\ThreadLocal

Thread

到目前为之,我们应该清楚Thread只是你用来驱动任务(Runnable、Callable)的一个线程,我们对Thread并没有任何控制权,尤其是使用ExecutorService。Thread本身并没有任何逻辑,我们一般说的多线程,其实是指依附于多线程上的各种任务。要注意,将任务与线程分离是很有意义的实现,因为线程的创建开销是昂贵的,我们需要妥善管理并使用。


线程中的异常处理

一个线程中的异常如果不在该线程中捕获(run方法),在其他的线程中是无法捕获的,最终抛到前台。

package com.concurrent.newStart.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

class ExceptionThread1 implements Runnable {

	@Override
	public void run() {
		Thread t = Thread.currentThread();
		System.out.println("run() by  " + t);
		// throw new RuntimeException();
		int exception = 1 / 0;
	 }

}

class ExceptionThread2 implements Runnable {

	@Override
	public void run() {
		Thread t = Thread.currentThread();
		System.out.println("run() by  " + t);
		System.out.println("the UncaughtExceptionHandler" + t.getUncaughtExceptionHandler());

		// throw new RuntimeException();
		int exception = 1 / 0;
		System.out.println("after throw exception .............. ");// never
																	// gets here

	}

}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.println("caught  " + e + " e.Message: " + e.getMessage());
	}
}

class HandlerThreadFactory implements ThreadFactory {

	@Override
	public Thread newThread(Runnable r) {
		System.out.println(this + "  creating new Thread by ThreadFactory");
		Thread t = new Thread(r);
		System.out.println("created thread wiht UncaughtExceptionHandler  " + t);
		t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
		System.out.println("the UncaughtExceptionHandler  " + t.getUncaughtExceptionHandler());

		return t;
	}

}

public class CaptureUncaughtException {

	private static void testnWithUncaughtExceptionHandler(Runnable task) {
		ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
		exec.execute(task);
		exec.shutdown();

		System.out.println(task.getClass() + "  --end");
	}

	private static void testnWithOutUncaughtExceptionHandler(Runnable task) {
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(task);
		exec.shutdown();

		System.out.println(task.getClass() + "  --end");
	}

	public static void main(String[] args) throws Exception {

		// 如果所有线程的异常处理器一样,可以为所有线程设置默认的 UncaughtExceptionHandler.
		// 默认的UncaughtExceptionHandler,仅再没有专有的UncaughtExceptionHandler时最后调用 .
		// Thread.setDefaultUncaughtExceptionHandler(new
		// MyUncaughtExceptionHandler());

		testnWithUncaughtExceptionHandler(new ExceptionThread2());

		System.out.println("------------------------------------------------");
		TimeUnit.SECONDS.sleep(1);

		try {
			testnWithOutUncaughtExceptionHandler(new ExceptionThread1());

		} catch (Exception e) {
			System.out.println("can i catch  exception of another thread? e:" + e);// never
		}
	}

}

结论:一个线程中的异常如果发生逃逸,逃逸的异常无法再其他线程中捕获,会一 直抛出到控制台;
当我们使用UncaughtExceptionHandler 可以防止异常逃逸的发生.如果不使用,那务必在run方法中try可能出现的异常


资源共享的问题与解决

package com.tij.concurrency.shareResource;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

abstract class IntGenerator {
	private volatile boolean canceled = false;

	public abstract int next();

	// Allow this to be canceled:
	public void cancel() {
		canceled = true;
	}

	public boolean isCanceled() {
		return canceled;
	}
}

public class EvenGenerator extends IntGenerator {
	private int currentEvenValue = 0;

	public int next() {
		++currentEvenValue; // Danger point here!
		Thread.yield(); //加快线程切换
		++currentEvenValue;
		return currentEvenValue;
	}

	public static void main(String[] args) {
		EvenGenerator sharedResource = new EvenGenerator();
		EvenChecker.test(sharedResource);//
	}
}

class EvenChecker implements Runnable {
	private IntGenerator generator;
	private final int id;

	public EvenChecker(IntGenerator g, int ident) {
		generator = g;
		id = ident;
	}

	public void run() {
		while (!generator.isCanceled()) {
			int val = generator.next();
			System.out.println("val:" + val);
			if (val % 2 != 0) {
				System.out.println(val + " not even!");
				generator.cancel(); // Cancels all EvenCheckers
			}
		}
	}

	// Test any type of IntGenerator:
	public static void test(IntGenerator gp, int count) {
		System.out.println("Press Control-C to exit");
		ExecutorService exec = Executors.newCachedThreadPool();
		for (int i = 0; i < count; i++) {
			System.out.println("count" + i);
			exec.execute(new EvenChecker(gp, i));
		}
		exec.shutdown();
	}

	// Default value for count:
	public static void test(IntGenerator gp) {
		test(gp, 2);
	}
}

// outPut:
// Press Control-C to exit
// count0
// count1
// val:3
// 3 not even!
// val:4

代码中 我们使用了 Thread.yield(); 去加快线程切换。但是 yield()、setPriority()只是给线程调度调度器 建议,未必会有作用,因此我们实际使用中不能依赖它们。

我们定义了一个 EvenGenerator sharedResource = new EvenGenerator();这个对象作为共享资源,在多个线程中进行叠加。我们模拟的场景中:当 线程1 val加1,这时候发生上下文切换(不可控),线程2运行, 线程2中 val加1 (val2)后立刻切换线程1,然后刚才挂起的线程1继续running,加1, 执行判断,此时val3,出现奇数,任务执行完毕。

上述的问题只是一个典型的特例,线程间的切换不可控,导致资源的读写执行情况与预期不符合!

就像小明和小花 并行使用桌上唯一的筷子,小明拿起筷子使用后放下,然后小花使用。ok没问题,这是单线程,小明小花排队拿筷子。当小明拿起筷子后还未返还,小花又要拿筷子,这时候小花当然拿不到,小花会认为桌上就没有筷子。这就是多线程中共享资源出现的问题!小明小花不会排队拿筷子

怎么解决呢?

都是采用 串行访问共享资源的方式解决,通过加锁的方式实现共享资源的多线程访问互斥性。这种机制成为 互斥量 mutex.

接着上文的例子,也就是说当小明拿起筷子还没有放回桌子上时,"不允许"小花伸手拿筷子(让小花等待)。当小明换了筷子后,"允许"小花伸手去取筷子。

线程本地存储

防止在共享资源上产生冲突的第二种方式是根除对变量的共享:
线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。

  • JDK实现:java.lang.ThreadLocal
//验证分线程存储变量 Test
import java.util.concurrent.TimeUnit;

/**
 * 
 * @author zs
 */
public class TestThreadLocal {

	ThreadLocal<String> threadLocal = new ThreadLocal<String>();

	public static void main(String[] args) {
		final TestThreadLocal instance1 = new TestThreadLocal();
		// 线程本地存储:
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				while (!Thread.interrupted()) {
					if (null == instance1.threadLocal.get()) {
						String temp = "" + Thread.currentThread();
						instance1.threadLocal.set(temp);
					}
					System.out.println(
							"--------thread 1  show threadLocal message: -------" + instance1.threadLocal.get());
					sleepThreeSecond();
				}
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				while (!Thread.interrupted()) {
					if (null == instance1.threadLocal.get()) {
						String temp = "" + Thread.currentThread();
						instance1.threadLocal.set(temp);
					}
					System.out.println(
							"--------thread 2  show threadLocal message: -------" + instance1.threadLocal.get());
					sleepThreeSecond();
				}
			}
		}, "t2");
		t1.start();
		t2.start();
		while (true) {
			System.out
					.println("--------other thread   show threadLocal message: -------" + instance1.threadLocal.get());
			sleepThreeSecond();

		}
	}

	public static void sleepThreeSecond() {
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class Accessor implements Runnable {

	private final int id;

	public Accessor(int id) {
		this.id = id;
	}

	@Override
	public void run() {
		while (!Thread.currentThread().isInterrupted()) {
			ThreadLocalVariableHolder.increment();
			System.out.println(this);
			Thread.yield();
		}

	}

	@Override
	public String toString() {
		return "Accessor [id=" + ThreadLocalVariableHolder.get() + "]";
	}

}

public class ThreadLocalVariableHolder {

	// 1.ThreadLocal 通常当作静态域使用
	// 2.对ThreadLocal对象,只能通过get\set方法访问其内容
	// 3.get方法将返回该线程相关联的对象副本,set方法类似
	private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
		// private Random rand = new Random(47);

		protected synchronized Integer initialValue() {
			// return rand.nextInt(10000);
			return 2;

		}
	};

	// 4.访问方法 increment、get都不是synchronized,因为ThreadLocal保证不会出现竞争情况
	public static void increment() {
		value.set(value.get() + 2);
	}

	public static int get() {
		return value.get();
	}

	public static void main(String[] args) throws InterruptedException {

		ExecutorService exec = Executors.newCachedThreadPool();
		for (int i = 0; i < 5; i++) {
			exec.execute(new Accessor(i));
		}
		TimeUnit.SECONDS.sleep(1);
		exec.shutdownNow();
	}
}

注意:使用ThreadLoacl一定要在结束时删除,因为线程池会公用线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值