JavaEE——Thread类

JavaEE传送门

JavaEE

JavaEE——进程调度

JavaEE——进程与线程的关系



Thread类的基本用法

1.创建线程

#继承Thread类

1)继承Thread类来创建一个线程类

class MyThread extends Thread {
    //重写父类Thread中的run方法,run里面的逻辑,就是这个线程需要执行的工作
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}

2)创建MyThread类的实例,调用start方法启动线程

public class Demo1 {
    public static void main(String[] args) {
        MyThread t = new MyThread();//创建一个实例,并不会再系统中真的创建一个线程
        t.start();//线程开始运行,调用start方法的时候才真正创建出一个新的线程 
        System.out.println("hello main");
    }
}

# 注意事项 #

  1. 运行一次Java程序,就是启动了一个进程
  2. 一个进程里至少会有一个线程,默认的线程,正是main方法所在的线程(也叫做主线程)
  3. main主线程,和MyThread创建出来的新线程,是一个"并发执行"的关系(“并发= 并发 + 并行”)
  4. 操作系统调度线程的时候,是一个"随机"的过程,main线程和和MyThread创建出来的新线程谁先打印是随机的

一个多线程程序

class MyThread extends Thread {
    @Override
    public void run() {
        while(true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);//sleep表示"休眠",让线程阻塞1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行该程序,我们可以看到打印的顺序是随机的,是不可预测的

20200811130123_5074f

使用jconsole命令观察线程

找到jconsole.exe,双击打开

选择本地进程,选择刚刚写的Demo1,点击链接,选择不安全的链接

点击线程,我们可以看到左下角有许多线程


#实现Runnable接口

1)实现Runnable接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}

2)创建Thread类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数, 并调用 start 方法.

public class Demo2 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();

        System.out.println("hello main");
    }
}

那么这样写的好处是什么呢?

将任务提取出来, 目的是为了解耦合.

上面一种继承Thread写法, 就把线程要完成的工作和线程本身, 耦合在一起了, 假设未来要对这个代码进行调整(不用多线程了), 代码改动就会比较大.

如果想搞多个线程, 都干一样的活, 这时也是更合适使用Runnable的.

20200811130123_5074f

对比上面两种方法

继承Thread类实现Runnable接口
工作和线程本身耦合在一起低耦合
直接使用 this 就表示当前线程对象的引用this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()

#匿名内部类创建Thread子类对象

public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        };

        t.start();
    }
}

#匿名内部类创建 Runnable 子类对象

public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        });
        t.start();
    }
}

#lambda 表达式创建 Runnable 子类对象

public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello thread");
        });

        t.start();
    }
}

2.Thread的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
  • ID是Java中给Thread对象安排的身份标识, 身份标识可以有多个, 在不同的环境下, 使用不同的标识(比如说一个线程在JVM中有一个id, 在操作系统的线程API中有一个id, 在内核PCB中还有一个id)

  • 名称是在构造方法中, 指定的name, 在各种调试工具中用到(比如说jconsole)

  • getState()获取线程的状态(下一篇博客会讲解)

  • getPriority()获取线程优先级

  • isDaemon()判断该线程是否是守护线程(后台线程), 我们默认创建的线程是 "前台线程" (前台线程会阻止进程退出, 如果main运行完了, 前台线程还没完, 进程不会退出). 如果是后台线程, 后台线程不阻止进程退出(如果main等其他前台线程执行完了, 即使后台线程没执行完,进程也会退出)

    我们可以用setDaemo将默认进程设置成后台线程, 设置操作需要在start之前, 如果线程启动了, 就改不了

    t.setDaemon(true);
    
  • 是否存活,简单的理解,即为 run 方法是否运行结束了

  • 线程中断的问题, 下面进一步说明

public class Demo7 {
    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);
                }
            }
        },"我的Thread");

        t.start();

        System.out.println(t.getId());//ID
        System.out.println(t.getName());//名称
        System.out.println(t.getPriority());//优先级
        System.out.println(t.getState());//状态
        System.out.println(t.isDaemon());//是否后台线程
        System.out.println(t.isAlive());//是否存活
        System.out.println(t.isInterrupted());//是否被中断
    }
}

2.启动程序-start()

调用 start 方法, 才真的在操作系统的底层创建出一个线程.

如果我们不调用start方法, 只调用run方法

class MyThread extends Thread {
    @Override
    public void run() {
        while(true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.run();
        //t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果展示, 我们的运行结果只有"hello thread", 并没有"hello main", 只调用run方法并没有创建线程, 他只是在main这个线程里调用run方法.


3.线程中断

run方法执行完, 线程就结束了. 那我们有什么办法可以让线程提前结束呢?

通过线程中断的方法来进行, 其本质仍是让run方法尽快结束, 而不是run执行一半, 强制结束.

1)自己定义一个标志位, 作为线程结束的标志

public class Test {
	private static boolean isQuit = false;
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!isQuit) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("t线程执行完了");
        });

        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isQuit = true;
        System.out.println("设置让t线程结束!");
    }
}

2)使用标准库中自带的标志位, 在主线程中,通过t.interrupt中断线程

public class Demo8 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!Thread.currentThread().isInterrupted()) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("t线程执行完了");
        });

        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();//中断线程,设置标志位为true
        System.out.println("设置让t线程结束!");
    }
}

运行该程序, 我们可以看到触发了异常, 但线程仍在运行

在这里插入图片描述
那么这是为什么呢?

interrupt 方法的行为, 有两种情况:

  1. t 线程在运行状态.会设置标志位为true
  2. t 线程在阻塞状态(比如说sleep), 不会设置标志位, 而是触发一个InterruptedException, 这个异常会把sleep提前唤醒

由于代码中只是打印了日志, 并没有结束循环, 因此线程还是在继续执行, 所以我们需要由线程自身的代码来判定处理.

Thread t = new Thread(() -> {
	while(!Thread.currentThread().isInterrupted()) {
       System.out.println("hello thread");
       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();

           1.立即结束进程
           break;

           //2.不做理会,线程继续执行

           //3.线程稍后处理
           //Thread.sleep(1000);
           //break;
       }
   }
    System.out.println("t线程执行完了");
});

4.线程等待-join()

线程之间的调度顺序, 是不确定的. 我们可以通过一些特殊手段, 来对线程的执行顺序做出干预.

join方法, 可以控制线程之间的结束顺序

在main中调用t.join方法, 让main阻塞等待, 等到 t 执行完了, main才继续执行

public class Test {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        System.out.println("main线程 join之前");
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main线程 join之后");
    }
}

运行结果展示

# 注意 # 在调用 jion 之前, 若 t 线程已经结束了, 此时 join 不需要阻塞等待

20200811130123_5074f

join的几种版本

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理, 但更高精度

5.休眠线程-sleep()

在操作系统组织这些 线程的 PCB的时候, 是会有多个链表的.

A线程调用了 sleep .

则这个PCB 就会被移动到另外的 “阻塞队列” 中.

当A线程, sleep 的时间到了, 就会被移动到之前的就绪队列

# 注意 # 移回就绪队列, 不代表立即就能够上CPU执行. 还得看系统啥时候调度这个线程.(sleep(1000), 不一定是只休眠了1000, 一般要略多于1000)


6. 获取当前线程引用

public static Thread currentThread();//返回当前线程对象的引用

🌷(( ◞•̀д•́)◞⚔◟(•̀д•́◟ ))🌷

以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!

在这里插入图片描述
加粗样式

这里是Gujiu吖!!感谢你看到这里🌬
祝今天的你也
开心满怀,笑容常在。
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值