一、线程实现的方式
1、实现Runnable接口并编写run()方法
2、继承Thread类并覆盖run()方法
3、前两者都不返回任何值,如果你希望任务在完成时能返回一个值,那么就需要实现Callable接口.
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ThreadTest { public static void main(String[] args) { try { Future<String> result = getDataFromRemote(); System.out.println(result.get()); } catch(InterruptedException | ExecutionException e) { e.printStackTrace(); } } public static Future<String> getDataFromRemote() { ExecutorService service = Executors.newCachedThreadPool(); return service.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("getDataFromRemote is ing..."); try { Thread.sleep(2000); } catch(InterruptedException e) { e.printStackTrace(); } return "ok"; } }); } }
//output
getDataFromRemote is ing...
ok
二、一些概念简介:
Thread.yield():对线程调度器的一种建议,它在声明:“我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机”。
Thread.sleep():休眠,不释放对CPU的占用;Object对象的wait()方法:释放CPU
getPriority():优先级
后台线程(daemon):也叫守护线程,是指在程序运行的时候在后台提供一些通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。当所有非后台线程结束,程序被终止,同时进程中的所有后台线程也会被杀死。典型的就是 垃圾回收。
要将一个线程设置为后台线程,则调用setDaemon(true)即可、
t.join():等待一段时间直到线程t结束,原线程才继续执行
捕获异常:由于线程的本质特性,使得不能捕获从线程逃逸的异常。一旦异常逃出任务的run()方法后,就会向外传播到控制台。除非采取特殊的方式捕获这些异常。
package threadpool; public class ExceptionThread1 implements Runnable { @Override public void run() { throw new RuntimeException(); } public static void main(String[] args) { try { Thread thread = new Thread(new ExceptionThread1()); thread.start(); } catch(Exception e) { } } } //output Exception in thread "Thread-0" java.lang.RuntimeException at threadpool.ExceptionThread1.run(ExceptionThread1.java:9) at java.lang.Thread.run(Unknown Source)
为了解决这个问题,java se5之后的新街口Thread.UncaughtExceptionHandler可以解决这个问题。
package threadpool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; public class ExceptionThread implements Runnable { @Override public void run() { Thread t = Thread.currentThread(); System.out.println("run() by " + t); System.out.println("eh = " + t.getUncaughtExceptionHandler()); throw new RuntimeException(); } public static void main(String[] args) { try { ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory()); exec.execute(new ExceptionThread()); } catch(Exception e) { e.printStackTrace(); } } } class HandlerThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { System.out.println(this + " creating new Thread"); Thread t = new Thread(r); System.out.println(" created " + t); t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); System.out.println("eh = " + t.getUncaughtExceptionHandler()); return t; } } class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("caught " + e); } }
//output
threadpool.HandlerThreadFactory@16acdd1 creating new Thread
created Thread[Thread-0,5,main]
eh = threadpool.MyUncaughtExceptionHandler@facf0b
run() by Thread[Thread-0,5,main]
eh = threadpool.MyUncaughtExceptionHandler@facf0b
threadpool.HandlerThreadFactory@16acdd1 creating new Thread
created Thread[Thread-1,5,main]
eh = threadpool.MyUncaughtExceptionHandler@10721b0
caught java.lang.RuntimeException
三、线程同步的方式
所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁产生的四个条件:
1、Synchronized
所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任何synchronzised方法的时候,此对象都被加锁,这时候该对象的其他synchronzised方法只有等到前一个方法调用完毕并释放锁之后才能被调用。所以,对于某个特定对象来说,其所有synchronzised方法共享同一个锁。这可以被用来防止多个任务同时访问被编码为对象内存。
针对每个类,也有一个锁。所以synchronized static 方法可以在类的范围内防止对static数据的并发访问。
2、使用显示的Lock对象
Java SE5的java.util.concurrent类库还包含有定义在java.util.concurrent.locks中的显示 互斥机制。Lock对象必须被显示地创建、锁定和释放。因此,它与内建的锁形式相比,代码缺乏优雅性。但是,对于解决某些类型的问题来说,它更加灵活。
class EvenGenerator extends IntGenerator { private int currentEvenValue = 0; private Lock lock = new ReentrantLock(); @Override public int next() { lock.lock(); try { ++currentEvenValue; Thread.yield(); ++currentEvenValue; return currentEvenValue; } finally { lock.unlock(); } } }
当使用Lock对象时,对lock()的调用,必须放置在finally子句中带有unlock()的try-finally语句中、
return必须在try子句中,以确保unlock()不会过早发生。
如果使用synchronized,某些事务失败了,就会抛出一个异常。而又无法去做任何清理工作,以维护系统使其处于良好状态。
ReentrantLock允许你尝试获取但最终未获取锁,这样如果其他人已经获取了这个锁,那你就可以决定离开去执行其他一些事情,而不是等待直至这个锁被释放。
lock.tryLock();
lock.tryLock(2, TimeUnit.SECONDS);
四、线程本地存储--根除对变量的共享
线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。
ThreadLocal类实现,通常当作静态域存储。在创建ThreadLocal时,你只能通过get()和set()方法来访问该对象的内容。实质就是每个单独的线程都被分配了自己的存储,因为它们
每个都需要跟踪自己的计数值,从而实现了线程间的数据隔离。