小小的回顾
上一篇博客中,阿涛大概领着大家认识了一下进程.进程就是任务管理器中一个个打开的程序,比如我们现在打开了一个腾讯QQ,我们既需要用它来聊天,也需要用它来刷空间,显然这是两个不同的逻辑,我们需要通过不同的代码来实现这个逻辑.一个QQ是有好多好多的功能的,如果我们就按照代码的书写顺序来运行,那么极有可能同一时间能过实现的功能是极其有限的,此时此刻老祖宗留下的至理名言起到了关键作用:人多力量大!
初识线程
我们可以把进程想象成一个个工厂,那么线程就是工厂里面的一个个流水线,为了提高工厂的效率,我们就需要在条件允许的情况下设置流水线.我们现在的电脑都是多核心多线程的,核心越多线程越多,电脑并发编程的能力越强.
刚才说到的,进程也能做到线程能做到的事情,那么我们为什么不多搞几个线程呢?
其实线程本身不叫线程,线程的真名是"轻量级进程",只不过我们程序猿在学习工作的时候需要进行区分而已,"轻量级"主要体现在除了第一次创建线程我们需要有一定的开销,由于个个线程是共用一块地址空间的,所以之后的创建线程就有现成的地方了,同理,调用和销毁进程的开销是要大于调用和销毁线程的!
还是类比工厂和流水线,要建厂的开销一定比在厂里面增加一条流水线要来的大吧!!
创建线程
其实只要我们psvm了一下,我们就创建好了一个main线程,所以一般一个java程序一定至少会有一个线程.
继承Thread类
static class MyThread extends Thread{
@Override//继承Thread类并且需要重写里面的run方法,可以把run方法看成是Thread里面的main方法
public void run(){
System.out.println("hi,t");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();//创建好MyThread实例只是做好了准备工作,此时程序里面并没有真正创建好一个线程
//就好比跑步比赛的预备,各就各位
//只有在这里调用了start()方法,线程才是真正地创建了出来
System.out.println("hi,main");
}
构造方法中传入Runnable实例
static class MyRunnable implements Runnable {
@Override//创建MyRunnable类实现Runnable接口,这里的重写人家是会强制你去做的
public void run() {
System.out.println("hi,t");
}
}
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);//在构造方法里面传入MyRunnable实例
t.start();//还是需要调用start方法的
System.out.println("hi,main");
}
匿名内部类创建Thread子类
public static void main(String[] args) {
Thread t = new Thread(){//这种写法相当于是创建了一个一次性的Thread类的子类,并且重写其中的run方法
//匿名内部类主要就是应用于一次性使用的场景下,没有类名但是能且只能使用这一次
@Override
public void run(){
System.out.println("hi,t");
}
};
t.start();
System.out.println("hi,main");
}
匿名内部类实现Runnable接口实例
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("hi,t");
});
t.start();
System.out.println("hi,main");
}
这个基本上就是最简洁的版本了,以后也是使用的比较多的一个版本.
线程休眠
休眠就是睡觉,睡觉就是sleep,休眠模式下面的线程是没有办法申请CPU资源的,当我们不希望线程快速上CPU时就会搞这一套:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
System.out.println("hi,t");
});
t.start();
Thread.sleep(100);//sleep后面加上一个休眠的时间,到点了线程就会起床干事情
System.out.println("hi,main");
}
之前我们说过,一般来说t线程是要在main线程之后才跑起来的,但是此时我们加上了Thread.sleep() ,一切就不一样了,在我们看来100ms可能就是白驹过隙,我打字的功夫就不知道过去多少个100ms了,但是在计算机看来100ms就是沧海桑田,等待这一会的功夫已经跑了N行代码了,所以在main线程休眠的这段时间里面,y线程跑起来并执行了里面的run方法
线程中断
想要中断一个进程,就是要让进程里面的run方法跑完,就以循环为例子:
public static boolean flag = false;//如果这里不是一个成员变量,好像是不可以在lambda表达式里面使用的,所以要设置一个成员变量
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(!flag){
System.out.println("hi,t");
}
System.out.println("线程结束");//如果循环结束了,跑到了这一行,只要打印完这一行那么线程也就结束了
});
t.start();
Thread.sleep(100);//不加这个的话main线程执行的比t线程快,是不会有打印内容的
flag = true;
System.out.println("main结束");
}
从结果上来看,确实达到了我们的预期效果,只不过这里我们是使用了外部变量来控制线程的,其实我们的Thread中是有内置的标志位的:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(!currentThread().isInterrupted()){
System.out.println("hi,t");
}
System.out.println("t结束");
});
t.start();
Thread.sleep(10);
t.isInterrupted();
System.out.println("main结束");
}
当我们需要interrupt一个正在sleep或者其他阻塞状态下的线程是,就会通过抛出异常的方式唤醒线程,有没有一点你永远没办法叫醒一个装睡的人的意思呢?
线程等待
public static int num = 0;
//main是一个静态方法,非静态的属性方法是没法在静态函数中使用的
//lambda表达式中是不可以使用普通变量的,所以我们把这里的变量给调整成成员变量
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 5; i++) {
num++;
}
});
t.start();
System.out.println(num);
}
按照我们的预设,应该是在t线程里面完成了自增操作啊,为什么最后我们显示出来的还是0嘞?
我们之前应该说过,因为创建线程是需要一定的系统开销的,所以main线程一般情况是是要比其他线程提前执行的,所以我们在sout的时候,num还是0.要是想要达到我们的预期,就需要让main线程等待t线程执行完,这时候,就涉及到了线程的等待,这里我们先讲解一个join()方法,后期我们再讲解别的方法:
那么我们在main线程中调用了一个t.join()这就是让main线程等待t线程执行完然后再执行,这下子我们就得到了自己想要的.
获取线程实例
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(Thread.currentThread());
});
t.start();
System.out.println(Thread.currentThread());
}
我们可以直接通过currentThread()这个静态方法来获取到当前线程的实例,在t线程中拿到的就是t线程,在main线程中拿到的就是main线程的实例:
看样子Thread类是重写了toString()方法,同时我们可以看到实例方法还是有不少的,在学习的过程中可以随时使用.
好了,那么今天关于线程的学习就到这里了,希望我的这篇博客可以帮助到大家.
百年大道,你我共勉!