一线程创建的方法1.1 继承Thread,重写run线程一中涉及1.2 实现Runnable重写runclass MyRunnable implements Runnable{ Override public void run() { while (true) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo2 { public static void main(String[] args) throws InterruptedException { MyRunnable runnable new MyRunnable(); Thread t new Thread(runnable); t.start(); while (true) { System.out.println(hello world); Thread.sleep(1000); } } }第一种写法把线程和线程要执行的任务“耦合在一起了”第二种写法Runnable表示“要执行的任务”Thread表示“线程”解耦合1.3 继承Thread重写run使用匿名内部类Thread t new Thread(){ }此处定义的t并不是指向Thread的实例而是指向了Thread的一个子类这个子类是匿名的没有名字完整代码如下public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread t new Thread(){ Override public void run() { while (true) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; t.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } } }1创建Thread的匿名子类2重写run方法3创建子类的实例赋值给t引用1.4 使用匿名内部类基于Runnablepublic class Demo4 { public static void main(String[] args) { Thread t new Thread(new Runnable() { Override public void run() { } }); } }1,创建一个Runnable的子类2重写run方法3定义出该子类的实例还可以写成以下的形式public class Demo4 { public static void main(String[] args) { Runnable runnable new Runnable() { Override public void run() { System.out.println(hello thread); } }; Thread t new Thread(runnable); t.start(); } }其中Demo4是外部类runnable是匿名内部类匿名内部类必须在定义它的外部类中使用1.5 基于lambda表达式作用是简化匿名内部类lambda表达式本质上是一个匿名函数表示一个“一次性”的使用逻辑Java中通常情况下方法/函数不能脱离类单独存在Thread t new Thread(() - { });完整代码如下public class Demo5 { public static void main(String[] args) throws InterruptedException { Thread t new Thread(() - { while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); while(true){ System.out.println(hello main); Thread.sleep(1000); } } }lambda本质上是一个“函数式接口”继承出来的匿名内部类的实例1.5.1 lambda补充知识点1函数式编程思想1面向对象强调【怎么通过对象做】2函数式强调【做什么不纠结形式】3lambda就是Java里函数式思想的体现2lambda使用前提1必须要有一个接口2接口有且仅有一个抽象方法函数式接口3lambda标准格式三要素参数 箭头 代码块形式参数- {代码块}4省略模式1参数类型可以省略2只有一个参数小括号可以省略3代码只有一行大括号分号return都可以省略二Thread的其他用法Thread()创建线程对象Thread(Runnable target)使用Runnable对象创建线程对象Thread(String name)创建线程对象并命名Thread(Runnable target,String name)使用Runnable对象创建线程对象并命名Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理分好的组即为线程组起不起名字对线程的运行没有任何的影响但是方便进行调试根据不同的功能给线程起不同的名字2.2 Thread的几个常见属性属性方法IDgetId()名称getName()状态getState()优先级getPriority()是否后台线程isDaemon()是否存活isAliver()是否被中断isInterrupted()ID虽然操作系统中对于线程也分配了唯一的id但是此处看到的id是jvm分配的和系统分配的id是不同的内容Java中看不到操作系统分配的线程id前台线程这个线程如果没有执行完此时进程不会结束前台线程会阻止进程结束后台线程也可叫做守护线程这个线程执行完执行不完都不会影响进程的结束代码中创建的线程默认是前台线程包括main也是前台线程JVM自带的线程是后台线程可以用setDaemon来设置线程为后台线程public class Demo6 { public static void main(String[] args) { Thread t new Thread(() - { while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } },给线程起的名字); //默认为前台线程 //一定要在start之前将线程设置为后台线程 t.setDaemon(true); t.start(); } }运行截图如下可以发现只执行了一次进程就结束因为在上述代码中只有一个前台线程即main线程main结束了则整个进程就结束了是否存活未开始执行/执行结束 入口方法则为不存活正在执行 入口方法则为存活是用来描述操作系统内核里是不是有这个线程操作系统的线程执行完毕不代表Thread对象销毁Thread对象的生命周期可以比系统内部的线程更长2.3 启动一个线程-start直接run没有创建新的线程展开start方法可以发现一个声明其中native是本地方法即jvm内部通过c实现的方法针对一个Thread只能调用一个start方法因为Thread对象和操作系统的线程是一一对应的如果能调用多次start就是一对n了2.4 中止/中断一个线程· 在Java中让线程结束只能通过一种方法就是使该线程的入口方法尽快return执行完1直接自己使用标志位进行实现public class Demo7 { private static boolean running true; public static void main(String[] args) { Thread t new Thread(() - { //如果只是一个打印语句的话则这个线程瞬间就执行完毕了就谈不上“中止”其他线程中根本就没来得及操作 while (running) { System.out.println(hello); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(thread结束); }); t.start(); //在main方法中让用户通过进行输入来触发t线程的中止 Scanner scanner new Scanner(System.in); System.out.println(输入任意内容控制t线程结束); scanner.next();//使用这个方法会使得main阻塞会让线程暂停等待用户的输入当用户输入了则马上继续 running false; } }如果将running改为局部变量则不能执行会报错其中effectively final是事实上的final虽然没有通过final修饰但是也没人修改Q为什么要这么设定A1变量生命周期的问题lambda里面访问的变量得是有效的变量即这个变量不能是被释放了的变量如上述代码中running会在main方法执行完毕之后就释放了而main如果结束了此时t线程还可能继续存在所以Java为了规避上述问题就在变量捕获的时候进行了拷贝变量捕获其实是复制了一份副本外面的变量释放不影响里面的使用但是这两个变量是否需要同步也是问题所以规定不可以修改Q如果改成成员变量就不再触发“变量捕获”而是“内部类”访问“外部类”的成员Alambda本质上是一个基于函数式接口的匿名内部类2使用interrupt方法能够把阻塞操作唤醒1.唤醒阻塞方法2.如果线程没有执行到阻塞操作还可以设置内置标志位进行线程结束的判定说明boolean running其实是在Thread对象中被封装了报错的原因为使用isInterrupted的时候t未初始化在lambda表达式中的执行顺序为1定义lambda2把lambda作为Thread构造方法参数3Thread构造方法执行完再初始化t解决方法改为Thread.currentThread作用为获取当前线程对象的引用在哪个线程中调用就能拿到哪个线程对应的对象的引用这个方法是一个静态方法通过类名就可以直接调用public class Demo8 { public static void main(String[] args) { Thread t new Thread(() - { //通过isInterrupted判定线程是否被终止 while(!Thread.currentThread().isInterrupted()){ System.out.println(hello); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(线程t终止); }); t.start(); System.out.println(输入任意内容终止t线程); Scanner scanner new Scanner(System.in); scanner.next(); //终止t线程 t.interrupt(); } }没有interrupt才继续执行里面的Boolean初始是false外面调用interrupt改为true执行后会出现异常是因为try-catch中的catch (InterruptedException e)如果t处于阻塞状态包括sleep当外面通过interrupt方法终止就会使t线程触发interruptedException异常然后由于这个异常没有人catch整个进程就结束了这个代码是idea默认生成的代码实际中的catch类型有1catch中进行重试2记录错误日志3触发监控报警【Q】interrupt底层标志位是false那么while()条件都为false了本该结束了为什么还会跑到catch中去【A】因为sleep的执行时间为1s远超过其他语句的执行时间所以外面的代码在调用interrupt的时候大概率触发sleep操作而当某个异常没人catch会沿着方法的调用栈往上抛没人调用了就要交给JVM处理JVM对于异常的处理方式只有一种即让线程终止优化异常相关的处理代码public class Demo8 { public static void main(String[] args) { Thread t new Thread(() - { //通过isInterrupted判定线程是否被终止 while(!Thread.currentThread().isInterrupted()){ System.out.println(hello); try { Thread.sleep(1000); } catch (InterruptedException e) { //throw new RuntimeException(e); //1立即终止线程 break; //2,稍后处理 //先执行一些其他的操作比如释放资源收尾操作 //然后再break // break; //3,不处理忽略了终止需求 //continue; } } System.out.println(线程t终止); }); t.start(); System.out.println(输入任意内容终止t线程); Scanner scanner new Scanner(System.in); scanner.next(); //终止t线程 t.interrupt(); } }但是第三中会出现问题即在用户输入了之后线程依旧没有终止原因Interrupted会先设置标志位为true然后唤醒sleep当sleep被唤醒的时候又会先清除标志位重新设回false一些补充isInterrupted每个线程对象有一个自己的标志位interrupted整个线程类静态的成员共享2.5 线程等待即等待线程的结束由于线程的调度顺序是随机的所以需要一些方法对线程的顺序来产生干预即线程等待强调线程的“结束顺序”使用后结束的线程等待要先结束的线程比如把某个任务交给某个线程来执行期望这个线程执行完毕之后在主线程汇总结果就可以让主线程等待新线程public class Demo9 { public static int result 0; public static void main(String[] args) { Thread t new Thread(() - { int sum 0; for (int i 0;i1000;i){ sum i; } result sum; }); t.start(); System.out.println(result); } }在这个代码中打印的结果是错误的是因为main和t线程是并发执行的很可能main执行打印的时候t还没有计算完毕解决方法如下1让main线程sleep一下public class Demo9 { public static int result 0; public static void main(String[] args) throws InterruptedException { Thread t new Thread(() - { int sum 0; for (int i 0;i1000;i){ sum i; } result sum; }); t.start(); Thread.sleep(10000); System.out.println(result); } }但是如果计算量比较大的话休眠的时间就不好确定而且执行时间也不固定2线程等待 join() 对于t.join()通常来说“.”是“的”的意思翻译为t的等待但是在这里不一样正确的意思为main等待t因为这个代码是main中调用的所以是main等待t当调用t.join的时候就要看t 线程是否结束1如果t 线程仍然在执行join方法会阻塞等待一直等到t 线程结束2如果 t 线程已经结束了join 方法则不会堵塞直接往下执行public class Demo9 { public static int result 0; public static void main(String[] args) throws InterruptedException { Thread t new Thread(() - { int sum 0; for (int i 0;i1000;i){ sum i; } result sum; }); t.start(); //Thread.sleep(10000); t.join(); System.out.println(result); } }两个线程之间的等待是相互的可以让main等待t 也可以让t 等待main如下public class Demo10 { private static int result 0; public static void main(String[] args) { Thread mainThread Thread.currentThread(); Thread t new Thread(() - { try { mainThread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(计算结束 result); }); t.start(); int sum 0; for(int i 0;i1000;i){ sum i; } result sum; } }2.5.1 join方法方法说明public void join等待线程结束public void joinlong millis等待线程结束最多等millis毫秒public void joinlong millis, int nanos同理但可以更高精度精确到了纳秒实际中一般用第二种死等容易卡住产生bug2.6 获取线程引用方法说明public static Thread currentThread返回当前线程对象的引用这是一种通用的方法如下还有一种特殊的方法:public class Demo11 { public static void main(String[] args) { Thread t new Thread() { Override public void run() { // 可以使用 this 获取到当前线程的引用的. // 此处是继承 Thread 的方式来创建线程的. // this 指向的就是 Thread 对象 System.out.println(this.getName()); System.out.println(Thread.currentThread().getName()); } }; Thread t2 new Thread(new Runnable() { Override public void run() { // 此处 this 指向的是 Runnable 的实例. // System.out.println(this.getName()); // 只能使用 currentThread() System.out.println(Thread.currentThread().getName()); } }); Thread t3 new Thread(() - { // lambda 中根本没有 this // System.out.println(this.getName()); System.out.println(Thread.currentThread().getName()); }); t.start(); } }2.7 休眠当前线程sleep核心就是让线程堵塞不参与调度先描述再组织有就绪队列和阻塞队列当一个线程休眠时就把其从就绪队列中放到阻塞队列休眠结束就放回就绪队列方法说明public static void sleeplong millisthrows InterruptedException休眠当前线程millis毫秒public static void sleeplong millisint nanosthrows InterruptedException可以更高精度的休眠不是立即执行的还有一个调度的时间证明如下public class Demo14 { public static void main(String[] args) throws InterruptedException { // 获取到系统时间戳 (毫秒) long beg System.currentTimeMillis(); Thread.sleep(1000); long end System.currentTimeMillis(); System.out.println(end - beg); } }执行结果为10021000时间到了把PCB放回就绪队列了再到真正继续回到cpu上执行大概花费2ms特殊sleep0写了sleep(0)虽然不休眠但是主动放弃了cpu的执行权就相当于从就绪队列到阻塞队列再快速回到就绪队尾重新排队Thread.yield和sleep0效果是一样的使用场景有些情况下需要对每个线程消耗的cpu资源做限制