您的位置:首页 > 编程语言 > Java开发

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

2016-02-18 01:07 330 查看

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 (val==2)后立刻切换线程1,然后刚才挂起的线程1继续running,加1, 执行判断,此时val==3,出现奇数,任务执行完毕。

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

就像小明和小花 并行使用桌上唯一的筷子,小明拿起筷子使用后放下,然后小花使用。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();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: