进程是OS分配资源的最进本的单位,线程是执行调度的最基本单位。分配资源最重要的是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己的独立空间)。
JVM线程与操作系统的线程是一一对应的,在JVM中启动一个线程,会交给操作系统启动一个线程。
纤程:用户太的线程,线程中的线程,切换和调度不需要经过OS。优势:占有资源很少,可以启动很多个(10W+)。目前支持向纤程的语言:GO、Python(lib)。Java目前不支持纤程。
//方法一:继承Thread static class ThreadExtends extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":T1"); } } //方法二:实现Runnable接口 static class ThreadImplRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":T2"); } } // 方式3 实现Callble接口(线程执行带返回值的) class MyRun implements Callable<String>{ @Override public String call() throws Exception { return "success"; } } @Test public void testCall(){ MyRun t1 = new MyRun(); try { String call = t1.call(); System.out.println(call); } catch (Exception e) { e.printStackTrace(); } } //方式4 使用线程池创建(带返回值) public void threadPool(){ ExecutorService pool = Executors.newCachedThreadPool(); pool.execute(()->{ System.out.println("hello ThreadPool"); }); Future<String> future = pool.submit(() -> { return "success"; }); try { //阻塞 String msg = future.get(); System.out.println(msg); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } //方式5 使用FutureTask创建(带返回值) @Test public void testFutureTask(){ FutureTask<String> task = new FutureTask<>(()->{ return "success"; }); new Thread(task).start(); try { //阻塞 String msg = task.get(); System.out.println(msg); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
线程启动使用start()方法。为什么不能用run()方法?start()会启动新线程执行(不能重复start),run()直接当前线程启动,不会使用新线程执行。
//错误演示:使用run启动线程 public static void main(String[] args) { ThreadExtends thread1 = new ThreadExtends(); new Thread(thread1).run(); System.out.println(Thread.currentThread().getName() + ":Main"); } //输出: // main:T1 // main:Main //结论:使用run()方法启动,执行线程为main线程,并非是thread1线程,并没有使用多线程执行 //正确使用start()启动线程 public static void main(String[] args) { ThreadExtends thread1 = new ThreadExtends(); new Thread(thread1).start(); System.out.println(Thread.currentThread().getName() + ":Main"); } //输出: // main:Main // Thread-1:T1 //结论:使用start方法启动线程,会开启多个线程执行,从而达到多线程执行的目的。 //jdk8使用Lambda表达式启动线程,实际也是通过实现Runnable接口 new Thread(()->{ System.out.println(Thread.currentThread().getName() + ":T3"); }).start();
@Test public void testJoin() throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println("T1执行"); System.out.println("T1结束"); }); Thread t2 = new Thread(() -> { try { System.out.println("T2执行"); t1.join(); System.out.println("T2结束"); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t3 = new Thread(() -> { try { System.out.println("T3执行"); t2.join(); System.out.println("T3结束"); } catch (InterruptedException e) { e.printStackTrace(); } }); t3.start(); t1.start(); t2.start(); /** * 执行结果: * T3执行 * T2执行 * T1执行 * T1结束 * T2结束 * T3结束 * 结论:无论谁先抢到执行,都需要等T1执行结束后T2才能执行结束,最后T3执行结束。 */ }
wait(),notify(), notifyAll()必须拥有monitor,属于Object类,和Condition类似。
notify唤醒:
Condition是个接口,基本的方法就是await()和signal()方法;Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()。调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。Condition中的**await()对应Object的wait();Condition中的signal()对应Object的notify();Condition中的signalAll()**对应Object的notifyAll()。
/** * 实现一个固定容量同步容器,拥有put和get方法,以及getCount方法. * 能够支持2个生产者线程以及10个消费者线程的阻塞调用 */ public class WaitAndNotilfy { List list = new ArrayList(); //容器最大容量 private final static int MAX = 10; private static int COUNT = 0; Lock lock = new ReentrantLock(); //生产者 Condition producer = lock.newCondition(); //消费者 Condition consumer = lock.newCondition(); public Object get() { Object o = null; try { lock.lock(); while (list.size() == 0) { System.err.println("消费者暂停"); consumer.await(); } o = list.get(0); list.remove(0); COUNT--; //通知生产者生产 producer.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return o; } public void put(Object o) { try { lock.lock(); while (list.size() == MAX) { System.err.println("生产者暂停"); producer.await(); } list.add(o); ++COUNT; //通知生产者生成 consumer.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public int getCount() { return COUNT; } public static void main(String[] args) { WaitAndNotilfy c = new WaitAndNotilfy(); for (int i = 0; i < 20; i++) { new Thread(() -> { for (int j = 1; j <= 5; j++) { c.put(j); System.out.println("生产者生产了:" + j + ",目前容量:" + c.getCount()); } }).start(); } for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 1; j <= 5; j++) { Object o = c.get(); System.out.println("消费者消费了:" + o + ",目前容量:" + c.getCount()); } }).start(); } } }
通常情况下,我们不会手动停止一个线程,而是允许线程运行到结束,然后让它自然停止。但是依然会有许多特殊的情况需要我们提前停止线程,比如:用户突然关闭程序,或程序运行出错重启等。在这种情况下,即将停止的线程在很多业务场景下仍然很有价值。尤其是我们想写一个健壮性很好,能够安全应对各种场景的程序时,正确停止线程就显得格外重要。但是Java 并没有提供简单易用,能够直接安全停止线程的能力。
@Test public void testInterrupted() throws InterruptedException { Thread t = new Thread(()->{ while (true){ if(Thread.currentThread().isInterrupted()){ System.out.println("Thread is interrupted!"); System.out.println(Thread.currentThread().isInterrupted()); break; } } }); t.start(); Thread.sleep(1000); t.interrupt(); } /** * 输出: * Thread is interrupted! * true * 结论:isInterrupted()查询不重置标志位 */ @Test public void testInterrupted2() throws InterruptedException { Thread t = new Thread(()->{ while (true){ if(Thread.interrupted()){ System.out.println("Thread is interrupted!"); System.out.println(Thread.interrupted()); break; } } }); t.start(); Thread.sleep(1000); t.interrupt(); //当前线程 System.out.println("main:"+t.isInterrupted()); } /** * 输出: * main:false * Thread is interrupted! * false * 结论:Thread.interrupted() 会重置标志位 */
注意:interrupted不能中断正在竞争锁的线程。如果允许打断,在线程加锁时Lock的lockInterruptibly()加锁,当interrupted对该线程设置标志位的时候,该线程会保InterruptedException异常。
对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt 仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。
** 使用interrupt中断线程** @Test public void testInterruptNormal() throws InterruptedException { Thread t = new Thread(()->{ while (!Thread.interrupted()){ } System.out.println("t end"); }); t.start(); Thread.sleep(500); t.interrupt(); }
一旦调用某个线程的 interrupt() 之后,这个线程的中断标记位就会被设置成 true。每个线程都有这样的标记位,当线程执行时,应该定期检查这个标记位,如果标记位被设置成true,就说明有程序想终止该线程。
public class StopThread implements Runnable { @Override public void run() { int count = 0; while (!Thread.currentThread().isInterrupted() && count < 1000) { System.out.println(count++); } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new StopThread()); thread.start(); Thread.sleep(5); thread.interrupt(); } } // 运行结果,输出没有到1000会被main线程中断 当线程处于sheep或await状态时,被其他线程interrupt,会报java.lang.InterruptedException public class StopThread implements Runnable { @Override public void run() { int count = 0; try { while (!Thread.currentThread().isInterrupted() && count < 1000) { if(count==10){ Thread.sleep(10); } } }catch (InterruptedException e){ e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new StopThread()); thread.start(); Thread.sleep(5); thread.interrupt(); } } //运行结果:输出10个数后报异常
查看Runnable接口可以发现,run()方法的返回是void,且未声明为抛出任何已检查的异常,而咱们实现并重写这个方法,自然也不能返回值,也不能抛出异常,因为在对应的Interface / Superclass中没有声明它。
Runnable为什么设计成这样?
如果run()方法可以返回值,或者可以抛出异常,也无济于事,因为我们并没办法在外层捕获并处理,这是因为调用run()方法的类(比如Thread类和线程池)也不是我们编写的。所以如果我们真的想达到这个目的,可以看看下面的补救措施:
@FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }