建一个门户网站,网站建设的工具是,无锡网站建设推广公司,wordpress中文版去广告文章目录 1. 线程的创建1.1 方法1#xff1a; 直接使用Thread1.2 方法2#xff1a;使用Runnable配合Thread1.3 方法3#xff1a;FutureTask配合Thread 2. 线程运行2.1 原理2.2 常见方法2.2.1 start与run2.2.2 sleep与yield2.2.3 join2.2.4 interrupt 3. 主线程和守护线程4. … 文章目录 1. 线程的创建1.1 方法1 直接使用Thread1.2 方法2使用Runnable配合Thread1.3 方法3FutureTask配合Thread 2. 线程运行2.1 原理2.2 常见方法2.2.1 start与run2.2.2 sleep与yield2.2.3 join2.2.4 interrupt 3. 主线程和守护线程4. 线程状态4.1 五种状态4.2 六种状态 1. 线程的创建
1.1 方法1 直接使用Thread
Thread t new Thread(){Overridepublic void run(){// 要执行的任务}
};
// 启动线程
t.start();1.2 方法2使用Runnable配合Thread
把 [线程] 和 [任务] 要执行的代码分开
Thread代表线程Runnable可执行的任务线程需要执行的代码
Runnable runnable new Runnable() {public void run(){// 要执行的任务}
}
// 创建线程对象
Thread t new Thread( runnable );
// 启动线程
t.start(); java8以后可以使用lambda精简代码
Runnable r () - {log.debug(running);
};
Thread t new Thread(r, t2);
t.start();小结
方法1是把线程和任务合并在了一起方法2是把线程和任务分开了。用Runnable更容易把线程池等高级API配合。用Runnable让任务类脱离了Thread继承体系更灵活。
1.3 方法3FutureTask配合Thread
FutureTask能够接收Callable类型的参数用来处理有返回结果的情况。
FutureTaskInteger task new FutureTask(new CallableInteger(){Overridepublic Integer call() throws Exception {log.debug(running);Thread.sleep(1000);return 100;}
});Thread t new Thread(task);
t.start();log.debug({}, task.get());
2. 线程运行
2.1 原理
栈与栈帧 JVM由堆、栈、方法区所组成其中栈内存给线程使用每个线程启动后哦虚拟机就会为其分配一块栈内存。
每个栈由多个栈帧Frame组成对应着每次方法调用时所占用的内存。每个线程只能有一个活动栈帧对应着当前正在执行的那个方法。
线程上下文切换Thread Context Switch 因为以下一些原因导致cpu不再执行当前的线程转而执行另一个线程的代码
线程cpu时间片用完垃圾回收有更高优先级的线程需要运行线程自己调用了sleep、yield、wait、join、park、sychronized、lock等方法程序
当Context Switch 发生时需要由操作系统保存当前线程的状态并恢复另一个线程的状态Java中对应的概念就是程序计数器它的作用是记住下一条jvm指令的执行地址是线程私有的。
状态包括程序计数器、虚拟机栈中每个栈帧的信息如局部变量、操作数栈、返回地址等。Context Switch频繁发生会影响性能。
多线程
2.2 常见方法
2.2.1 start与run
启动线程必须要调用start()不然对性能没影响。start()不能多次调用
public class Test4{public static void main(String[] args){Thread t1 new Thread(t1) {Overridepublic void run() {log.debug(running....);}};// t1.run();t1.start();}
}2.2.2 sleep与yield
sleep
调用sleep会让当前线程从Running进入Timed Waiting状态。其它线程可以使用interrupt方法打断正在睡眠的线程这时sleep方法会抛出InterruptedException。睡眠结束后的线程未必立刻得到执行。建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性。
yield
调用yield会让当前线程从Running进入Runnable状态然后调度执行其它同优先级的线程。如果这时没有同优先级的线程那么不能保证让当前线程暂停的效果。具体的实现依赖于操作系统的任务调度器。
线程优先级
线程优先级会提示调度器优先调度该线程但仅仅是一个提示调度器可以忽略它。如果cpu比较忙那么优先级高的线程会获得更多的时间片但cpu闲时优先级几乎没作用。
sleep实现 在没有利用cpu来计算时不要让while(true)空转浪费cpu这时可以用yield或sleep来让出cpu的使用权给其他程序。
while(true){try{Thread.sleep(50);} catch (InterruptedException e){e.printStackTrace();}
}可以用wait 或 条件变量达到类似的效果。不同的是后两种都需要加锁并且需要相应的唤醒操作一般适用于要进行同步的场景。sleep适用于无需锁同步的场景。
2.2.3 join
等待线程运行结束
t1.start();
t1.join();应用之同步案例1 以调用方角度来讲如果
需要等待结果返回才能继续运行就是同步不需要等待结果返回就能继续运行就是异步
2.2.4 interrupt
打断sleepwaitjoin的线程 eg打断sleep的线程会清空打断状态。
private static void test1() throws InterruptedException {Thread t1 new Thread(() -{sleep(1);}, t1);t1.start();sleep(0.5);t1.interrupt();log.debug(打断状态{}, t1.isInterrupted());
}eg打断正常运行的线程不会清空打断状态。
两阶段终止模式 在一个线程T1中如何“优雅”终止T2即给T2一个善后的机会。
错误思路如下
使用线程对象stop()方法停止线程 stop方法会真正杀死线程如果这时线程锁住了共享资源那么当它被杀死后就再也没有机会释放锁其他线程将永远无法获取锁。 使用System.exit(int)方法停止线程 目的仅是停止一个线程但这种做法会让整个程序都停止。 public class Test3{public static void main(String[] args){TwoPhaseTermination tpt new TwoPhaseTermination();tpt.start(); Thread.sleep(3500);tpt.stop();}
}class TwoPhaseTermination{private Thread monitor;// 启动监控线程public void start(){monitor new Thread(() - {while(true){Thread current Thread.currentThread();if(current.isInterrupted()){log.debug(料理后事);break;}try{Thread.sleep(1000); // 情况1log.debug(执行监控记录); // 情况2} catch(InterruptedExcetion e){e.printStackTrace();// 重新设置打断标记current.interrupt();}}}); }// 停止监控线程public void stop(){monitor.intterupt();}
}打断park线程不会清空打断状态。 如果打断标记已经是true则park会失效。
不推荐的方法
stop() 停止线程运行suspend() 挂起(暂停)线程运行resume() 恢复线程运行
3. 主线程和守护线程
默认情况下Java进程需要等待所有线程都运行结束才会结束。有一种特殊的线程叫做守护线程只要其它非守护线程运行结束了即使守护线程的代码没有执行完也会强行结束。
log.debug(开始运行...);
Thread t1 new Thread(() - {log.debug(开始运行...);sleep(2);log.debug(运行结束...);
}, daemon);
t1.setDaemon(true);
t1.start();sleep(1);
log.debug(运行结束...);4. 线程状态
4.1 五种状态
这是从操作系统层面来描述的
初始状态 仅仅在语言层面创建了线程对象还未与操作系统线程关联。**可运行状态**指该线程已经被创建与操作系统线程关联可以由CPU调度执行运行状态 指获取了CPU时间片运行中的状态。 当CPU时间片用完会从 运行状态 切换至 可运行状态 会导致线程的上下文切换 阻塞状态 如果调用了阻塞API如BIO读写文件这时该线程实际不会用到CPU会导致线程上下文切换进入阻塞状态。等BIO操作完毕会由操作系统唤醒阻塞的线程转换至可运行状态。与可运行状态的区别是对阻塞状态的线程来说只要它们一直不唤醒调度器就一直不回考虑调度它们。 **终止状态**表示线程已经执行完毕生命周期已经技术不会再转换为其它状态。
4.2 六种状态
这是从Java API层面来描述的。 根据Thread.State 枚举分为六种状态。
NEW 线程刚被创建但是还没有调用start()方法。RUNNABLE当调用了start()方法之后注意Java API层面的RUNNABLE状态涵盖了操作系统层面的可运行状态、运行状态和阻塞状态由于BIO导致的线程阻塞在Java里无法区分仍然认为是可运行。BLOCKED、WAITING、TIMED_WAITING都是Java API层面对阻塞状态的细分后面会在状态转换一节详述。TERMINATED当线程代码运行结束。