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();
}
}