概念
进程
进程是指一个正在运行的程序。
线程
线程是一条执行路径
主线程
主线程是值程序刚开始运行时自带的一条线程。
子线程
除主线程外的所有线程都成为子线程
多线程
多条线程一起执行,成为多线程并发
线程的理解
从宏观的角度来说是多条线程同时执行
从微观的角度来说是多条线程在交替执行
线程的组成
cpu时间片
cpu时间片是指一个线程在抢到执行权之后执行的时间。
内存
多个线程之间共享堆内存,但是每条线程之间的栈相互独立,即每条线程都有一个独立的栈。
逻辑
逻辑是指认为指定的程序的执行顺序。
线程的创建
创建Thread子类
方法1步骤:
- 创建一个类
- 继承Thread类
- 重写run方法
方法2步骤:
使用匿名内部类直接创建Thread的子类对象,并重写run方法。
实现Runnable接口
方法1:创建一个类并实现接口Runnable,重写run方法,创建这个线程的礼让 类的对象。
方法2:使用匿名内部类直接创建Runnable对象,并重写run方法。创建Thread对象并传入线程任务对象runnable
总结:如果使用第一种方案,由于在Java中,类是单继承的,所以在继承了Thread之后将不能再继承其他的类,这样会导致我们的代码扩展性变差。使用第二种方案,可以将创建线程对象和线程任务分开,而且实现接口也不会让我们的类不能再继承其他类或实现其他接口,让我们的代码变的更加灵活。
线程方法
线程启动
线程调用可以使用对象名.start(),调用start方法,这个方法的作用是新开辟一片空间成为新线程的栈,这个方法在执行的瞬间就会结束。
start方法在执行结束之后会调用线程对象中的run方法,执行run方法中的代码。
设置线程名称
对象名.setName(String name);
每一个线程都有对应的线程名,这个方法可以修改线程的名字,如果在创建线程对象之后不修改这个线程的名字,这个线程将会使用默认名字。
获取线程名称
对象名.getName();
返回值是String类型的值,是作用是获取线程的名字。
获取当前线程对象
static currentThread();
这个方法是静态方法,作用是获取调取该方法的线程对象,使用第二种方案创建线程任务对象,并分别传递给多个线程对象,使得多个线程同时操作一个线程任务,可以在runnable 中调用这个方法获取每次执行这个任务对象的线程对象。
线程的休眠
两种方式,限时休眠和不限时休眠。限时休眠是指休眠特定的时间,时间结束之后将会重新参与cpu时间片的抢夺。不限时休眠是指休眠将会一致持续下去,直至达成某个条件。
sleep(int m);使当前线程休眠m毫秒(还有一个两参的sleep,第一个参数仍为ms,第二个参数为纳秒)。
wait();不限时休眠
在线程休眠期间不参与cpu时间片的抢夺。
线程的优先级
线程是由优先级的,最低为1,最高为10,默认为5,更高优先级的线程更容易抢夺cpu时间片,但是这个差别不大。
线程对象.setPriority(int i); 设置线程优先级
线程对象.getPriority(); 获取线程优先级
线程的礼让
Thread.yield();执行此处代码将会释放抢夺到的cpu时间片,并重新抢夺cpu时间片。
线程的合并
join();
线程对象.join();
将其他线程合并到当前线程中,并且剩余代码将会一直等待到合并过来的线程执行完毕之后再执行,所以join也会起到不限时休眠的作用。
注意:这里的合并并不是指线程之间的栈合并了,而是值线程之间执行时间片的协调,是名义上的合并。
守护线程
在这里要抛出再抛出一个概念——前台线程。
我们每次创建的线程默认为前台线程,如果当前进程中仍有一个前台线程存活,那这个进程占用的内存将不会被回收。直至所有前台线程执行结束之后将会回收这片内存空间。
守护线程的存活与否不会影响到进程的结束与否。
setDaemon(true);
将当前线程对象修改为守护线程。
线程销毁
有两个方法,但是这两个方法销毁进程的方案都是抛出异常,直接是线程崩溃,这样用风险太大,就不介绍了,别用。
实在想控制线程销毁与否,可以给线程对象加一个boolean型的属性,每次执行代码判断线程后面的代码是否需要执行,在其他线程满足某个条件之后修改这个属性值,让它结束运行。如:
public void run(){
for(int i = 0;i < 10 && !isEnd;i++){
System.out.println(i);
}
}
从上面的代码可以看到,我们加一个属性isEnd,给它初始值是false,这个代码将不会受影响,可以一直执行,但如果在它执行到一半的时候,我们将isEnd修改为true,这个循环的条件表达式结果就会变成false,循环结束,run内的代码全部结束,就代表这个线程执行完毕,进入等待销毁的状态。
线程的声明周期
1.创建线程
创建线程对象,但是并没有对这个线程对象做任何的操作。
2.就绪状态
创建好线程对象之后可以调用start方法让线程对象进入到就绪状态,在这个状态之后将会参与到cpu时间片抢夺,如果抢到了cpu时间片,将会进入下一个状态:执行状态。
3.执行状态
线程抢到cpu时间片之后将会开始执行run中的代码,但是每次执行都是有固定时间的,时长就是cpu时间规定的时间,如果时间到了,代码还没有结束,将会再次回到就绪状态参与时间片的抢夺,抢到之后将会继续上次代码执行的位置开始执行。如果所有代码执行结束之后将会进入到下一个状态——死亡状态。
4.死亡状态
死亡状态,又叫销毁状态,当代码线程中的代码执行结束之后将会进入这个状态,在这个状态下线程将不会再参与时间片的抢夺,也不会在启用,等待gc的回收。由于这个状态是等待回收,所以这个状态下的线程仍旧会占用内存。
上述流程是指一个线程正常的执行过程,如果线程在执行过程中遇到了阻塞,将会进入阻塞状态。
3.5 阻塞状态
阻塞状态,又叫睡眠状态,进入这个状态的线程将会释放当前剩余的时间片,等待唤醒,有可能是阻塞时间结束之后自动唤醒,也有可能的满足某个条件之后被唤醒。进入这个状态的方式例如:睡眠,scanner键入数据等。唤醒之后将会回到就绪状态,再次参与时间片的抢夺。
所以最终的流程就是这样的。