package thread;
class MyThread extends Thread{
@Override
//这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的
public void run(){
System.out.println("hello world");
}
}
//Thread 类来自java.lang这个包 不需要import就可以直接用
public class Demo1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();//此处不仅仅要创建出类 还需要调用它让它执行起来
myThread.start();
//此处start就是在创建线程 这个操作就会在底层调用操作系统提供的"创建线程"API同时就会在操作系统内核里创建出对应的pcb结构并且加入到对应的链表中
//此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
}
}
Thread 类来自java.lang这个包 不需要import就可以直接用
这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的
此处start就是在创建线程 这个操作就会在底层调用操作系统提供的"创建线程"API同时就会在操作系统内核里创建出对应的pcb结构并且加入到对应的链表中
此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
当我们这样写的时候
run只是上面的入口方法(普通方法) 并没有调用系统api 也没有创建出真正的线程来
当点击运行程序的时候就会先创建出一个java进程这个进程中就包含了至少一个线程 这个线程也叫做主线程 也就是负责执行main方法的线程
当我们调整代码之后 在main方法中有一个while循环 在线程中run也有一个while循环
使用start的方式执行 此时这俩循环都在执行
两个线程分别执行之间的循环 这两个线程都能参与cpu的调度中以并发式执行
如果将代码改为
它只创建了一个线程 只有当run方法执行完成之后才会执行下面的代码 所以输出结果只有 hello world
总结
我们可以使用 jconsole查看线程
可以在jdk的路径下寻找
然后我们启动java程序
选择
能够列出系统上正在运行的所有的java进程
线程的调用栈
线程的调用栈 方法之间的调用关系
尤其是 当程序卡死了 查看一下 这里每个线程的调用栈 就可以知道大概那个代码出现卡死情况
Tread.sleep
Tread.sleep 时间是毫秒
运行结果并非是严格的交替
这两线程在进行sleep之后就会进入阻塞状态
当时间到之后 系统就会唤醒这两线程 回复线程的调度
谁先调度 可以认为是"随机"的
这样"随机"的调度过程 称位"抢占式执行"
Java中 通过Thread 类创建线程的方式还要很多种写法
class MyRunnable implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
while (true){
System.out.println("hellw main");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
Thread 这里是直接把要完成的工作放到了Thread的run方法中
Runnable 这里 则分开了 要把完成的工作放到Runnable 中再让Runnable 和Thread配合
public class Deom3 {
public static void main(String[] args) {
Thread t = new Thread(){
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
while (true){
System.out.println("hello main");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public class Demo {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println("hello world");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
});//对于的是Thread构造方法的结束 new Runnable整个一段都是构造Thread的参数
}
}
public class Demo4 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true){
System.out.println("hello world");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
thread.start();
}
}
lambda 表达式 本质上就是一个"匿名函数"
这样的匿名函数主要可以用来回调函数来使用
回调函数 不需要程序员直接主动调用而是在合适的时机 自动的被调用
此处的这个回调函数 就是在线程创建成功之后才真正执行
Java 本身不允许函数脱离 类/对象 来使用 lambda相当于特殊情况
“函数式接口”
类似于lambda 这样的写法 本质上没有新增新的语言特性 而是把以往的实现的功能 换了一种更简洁的方式来编写
关于线程的创建 并没有这五种写法
基于Callable
基于线程池
方法 | 说明 |
---|---|
Thread | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
【了解】Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
示例:
public class Demo5 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"myThread");
thread.start();
}
}
我们打开jconsole就可以看到我们命名的线程
这里并没有看到main线程是因为
一开始main线程执行遇到myThread线程执行lambda的死循环一直执行下去 然后当main执行完了 只剩下一个死循环继续执行
线程是通过start创建的 一个线程的入口方式执行完毕 对于主线程 是main 对于其他线程是run/lambda
示例2:
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
属性 | 获取方法 |
---|---|
ID | getId()JVM给的身份标识 |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况,下面我们会进一步说明
优先级高的线程理论上来说更容易被调度到
关于后台线程(守护线程),需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。后台线程不影响进程结束 前台线程会影响进程结束 如果前台线程没执行完 进程是不会结束的 一个进程中所有前台都执行完了 退出此时后台线程仍然没执行完也会随着进程一起退出 创建的线程默认是前台线程
示例:
package thread;
public class Demo7 {
public static void main(String[] args) {
Thread t = new Thread(()->{
while (true){
System.out.println("hello world");
}
});
//设置成后台线程
t.setDaemon(true);
t.start();
try {
Thread.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
start方法
在系统中 真正创建出线程
8. 创建出PCB
9. 把pcb加入对应的链表中(系统内核完成)
终止一个线程
一个线程的run方法执行完毕 就算终止了(结束)
此处的终止线程就是想办法让run能尽快执行完毕
正常情况下 不会出现 run 没执行完 线程突然就没了
10. 程序员手动设置标志位
package thread;
public class Demo8 {
public static boolean isQuit = false;
//设置标志位
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while (!isQuit) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//主线程这里执行一些逻辑后 让thread线程结束
thread.start();
Thread.sleep(3000);
//修改标志位
isQuit = true;
System.out.println("把t线程终止");
}
}
lambda变量捕获问题
当我们将 标识符写入main函数中时会报错 这是lambda变量捕获的问题
lambda 表达式 执行的时机时更靠后的
这就导致 后续真正执行lambda的时候局部变量isQuit就可能已经被销毁了
然后这时再让lambda去访问一个已经被销毁的变量明显时不合适的
lambda引入了"变量捕获"这样的机制
lambda内部看起来时直接访问外部的变量 其实本质上时把外部的变量 给复制了一份 到lambda里面 这样就可以解决生命周期的问题了
但是 变量捕获有限制
要求捕获的变量得是final (不能进行修改)
如果想让这个变量进行修改 就不能使用变量捕获了
写作成员变量就不是触发变量捕获的逻辑 而是"内部类访问外部类成员"
我们可以使用Thread类 给我们提供好了现成的标志位 不用自己手动去设置标志
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
//Thread.currentThread 就是t
//但是lambda 表达式是在构造t之前就定义好的 编译器看到lambda 里的thread会认为是一个没有初始化的对象
while (!Thread.currentThread().isInterrupted()){
System.out.println("hellp thread");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
}
Thread.currentThread 就是thread
但是lambda 表达式是在构造t之前就定义好的 编译器看到lambda 里的thread会认为是一个没有初始化的对象
获取到当前线程的对象 Thread提供了静态方法 currentThread在那个线程调用这个方法 就能获取到那个线程的引用
isInterrupted() true:结束线程
false 线程继续
thread.interrupt();将其设置为true
我们运行后会出现报错
thread线程正在sleep 然后被interrupt给唤醒(手动设置标识位是没办法唤醒的)
一个线程可能正在正常运行 也可能是在sleep
因此线程正在sleep过程中 其他线程调用interrupt方法中 就会强制使sleep抛出一个异常sleep就立即被唤醒了(假设你设定sleep(1000)虽然此处才过去10ms没到1000ms也会被立刻唤醒)
但是sleep在被唤醒的同时 会自动清除前面设置的标志位!
此时 如果我们先继续让线程结束 就直接在catch中加上break
如果不想看见异常信息可以把打印代码报错的注释掉
public class Deom10 {
public static void main(String[] args) {
Thread b = new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println("hello b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("b结束了");
});
Thread a = new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("hello a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
b.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("a结束了");
});
a.start();
b.start();
}
}