与线程的第一次见面

1、线程
2、Java中线程的创建
3、线程生命周期及线程状态之间的转换
4、线程的常用方法
1、那,什么是线程呢?

闲谈:
学习Java到现在,作为一个身份是一丢丢的程序员的我来说,写过了许多叫做程序的东西。我们写好一段代码,然后运行,然后得到预期的结果。
我想,对于线程陌生,那么进程就会是一个稍微贴切的概念。
对于平时用的QQ啊、微信啊、微博啊、网易云音乐,都可以称为一个进程,对于实现它们的那些代码,则叫做程序。
进程,是系统资源分配和处理机调度的一个独立单位。
那么,线程,则可以称为轻量级的线程。

比如说,你打开了咪咕音乐,这是一个进程。对于它来说,你可以在听歌的时候去下载歌曲,还可以去读歌曲下的评论,或者去做一些别的事,对于咪咕音乐这个进程来说,你的这些操作就可以称为一个个线程。
之所以引入线程,是因为进程的创建与撤销的花销比较大,在不追求安全和稳定的情况下,那么我们可以只启动线程也能完活。
说简单点,现在电脑的性能越来越好了,为了对得起电脑的内存利用率和系统吞吐量,都在追求高并发,无疑,多线程是实现这样需求的一种很好的思路。

对于线程来说,其实它本身并不拥有系统资源,它只需要有一点必不可少的、能保证独立运行的资源,即允许多个线程共享该进程所拥有的资源。
那么对应到Java里来说,
方法区、堆:当前所有线程之间所共享
每个线程都有自己的程序计数器、栈(Java栈和本地方法栈)

2、Java中创建一个线程的三种方式

首先,无论是什么方法,都要重写一个run()方法。
而启动线程,则是调用start()方法

1)继承Thread类,重写run()方法

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello world");
    }
}

调用 new MyThread.start()方法启动一个线程
2)实现Runnable接口,重写run()方法

class MyThread1 implements Runnable{
    @Override
    public void run() {
        System.out.println("implments Runnable to create a thread");
    }
}

仍然要调用start()方法启动一个线程
3)匿名线程(通过匿名类实现)

new Thread(){
	public void run(){
	
	}
}.start();

Thread thread = new Thread(){
	public void run(){
	
	}
};
thread.start();

不是调用run()方法去启动线程,因为Java是这么搞的,你把你想以多个线程展现的东西写在重写的run()里面,然后调用start()方法,如果进展顺利,就会调用一个start0()方法,这是一个Java的本地方法,这个方法用来启动线程,同时会将当前的线程加入到线程组中,线程就会进入Runnable状态。
源码:

public synchronized void start() {
        /*
        threadStatus,默认值 0,第一次启动后,就不是0了,所以说,一个线程只能被启动一次
        当启动一个线程后,第二次去启动它的时候,就会失败
        */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);//加入到一个线程组,进入到就绪状态
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                //如果start0()失败,就把这个进程加入到这里面
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
3、线程的生命周期及线程状态之间的转换

又到了我灵魂画手展现威力的时刻了!!!哈哈哈!
Java中进程的生命周期与操作系统的中略有区别
Java:
1)New状态:创建一个线程对象
2)Runnabled状态:就绪状态,只要获得系统资源,就有执行的可能。
3)Running状态:执行状态
4)Blocked状态:阻塞状态,等待监视器锁/调用Object.wait方法
5)Waiting状态:同阻塞状态,Object.wait/thread.join/LockSupport.park()
6)Timed_Waiting状态:限时等待
7)Terminated状态:终止状态
相信学过操作系统的同学都有能力画出来,对于线程状态的转换也不难:⬇
在这里插入图片描述
完美(此处应有手势)。

操作系统:
New:新建状态
Runnabled:就绪状态
Blocked:阻塞状态
Running:执行状态
Dead:终止状态

4、线程的一些常用方法

1)不用多说,当然是启动线程,
start()方法。
展示一下:
在这里插入图片描述
在这里插入图片描述
这个输出顺序的不确定性,就证明了这三个线程几乎是同时进行的。

2)sleep()方法,让线程睡一会儿。
sleep(long millis),单位是毫秒,想多睡一会儿的话就稍有不便。
所以这里有一个使用更方便的枚举类,TimeUnit

TimeUnit.NANOSECONDS.sleep(纳秒);
TimeUnit.MICROSECONDS.sleep(微秒);
TimeUnit.MILLISECONDS.sleep(毫秒);
TimeUnit.SECONDS.sleep(忘记了);
TimeUnit.MINUTES.sleep(不认识);
TimeUnit.HOURS.sleep(???);
TimeUnit.DAYS.sleep(--);
TimeUnit.MONTH.sleep(月!!!);//哦,不好意思,没有这个

展示一下:
A B C

System.out.println(Thread.currentThread().getName());//主线程
        Thread thread1 = new Thread("A"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread2 = new Thread("B"){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);//睡一秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread3 = new Thread("C"){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);//睡2秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
        thread1.start();
        thread2.start();
        thread3.start();

在这里插入图片描述
C A B

try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());//主线程
        Thread thread1 = new Thread("A"){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread2 = new Thread("B"){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread3 = new Thread("C"){
            @Override
            public void run() {
//                    TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName());
            }
        };
        thread1.start();
        thread2.start();
        thread3.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());//主线程

在这里插入图片描述

另一个让线程飞一会儿的方法,yield()方法
它的使用跟sleep方法差不多,有一点点的区别如下:
①sleep只会导致当前线程暂停,不会放弃cpu时间片
②yield放弃cpu一次时间片这个过程是不一定,如果cpu没有忽略这个提示,那才会导致线程切换
③yield方法会使得线程从Running切换到Runnable状态
④sleep,使用interrupt会捕获到中断信号,yield不会

3)线程中断
interrupt - 由Thread的一个对象调用,如果线程处于这个状态,将中断状态位置为true
那么,sleep()/join/wait()则会使得当前的线程进入阻塞状态->中断状态
线程处于阻塞状态时不能被打断,如果被打断就会抛出一个InterruptedException
isInterrupted - Thread.xxx 检测中断状态位是否为true

interrupted - Thread.xxx 判断当前线程是否被中断
如果当前线程被打断,第一次调用interrupted会返回true,然后立即擦出当前inteerrupt标识,
第二次包括以后调用都返回的是false

4)获取当前线程
public static Thread currentThread() 返回当前执行线程的引用
也就相当于得到当前线程的一个对象(总觉得这句话是多余的。。。)

5)join方法
线程A调用join方法,会使得线程A调用join方法时所在的线程B进入到等待队列,
此时必须等到线程A执行完或者到达给定的时间, 线程B才可以去执行
展示一下:

Thread thread1 = new Thread("A"){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread2 = new Thread("B"){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread3 = new Thread("C"){
            @Override
            public void run() {
//                    TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName());
            }
        };
        thread1.start();
        thread2.start();
        thread3.start();
//        try {
//            thread1.join();
//            thread2.join();
//            thread3.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println(Thread.currentThread().getName());//主线程

此时输出“main”在整个程序的最后一行,却是控制台第一个先输出的
在这里插入图片描述
在这里插入图片描述
6)守护线程
如果在主程序中存在非守护线程,会导致main线程结束,而子线程依旧在执行,JVM进程也将不会退出。
守护线程的意思就是,当主线程结束,它的守护线程也就随之而亡。
这么说也不太对,当线程B成为了A的守护线程,只要A结束,B也就跟着结束了,反之B结束,一般情况下,A的结束与否和B无关;当B不是A的守护线程,只要它们两个没得关系,who care who?
thread1.setDaemon(true);
true代表将它设为守护线程,false代表不把它设为当前线程的守护线程(人类迷惑行为大赏…),而且这段代码要写在start方法前面。
show time:
新启一个线程,里面写一个循环,输出0 ~ 9
在这里插入图片描述
搞搞这两行代码

thread1.setDaemon(true);
thread1.start();

在这里插入图片描述
这个例子不算很好,知道意思就行了。
迷惑行为:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值