认识线程
线程的基本概念
1.进程与线程的关系
首先我们要明确,线程是轻量级进程(创建销毁的开销更小)
进程是包含线程的(一个或多个),我们在Windows任务管理器看不到进程内部的线程,需要借助一些其他的调试工具(VS的调试器/Windbg...)
线程是CPU上调度执行的基本单位,进程是操作系统资源(包括CPU,内存,磁盘资源(文件描述符表))分配的基本单位,进程内部管辖的多个线程之间会共享上述的内存资源、硬盘资源、网络带宽
我们在销毁线程的时候是不会释放资源的
一个进程内部的线程之间,会容易相互影响的
线程在CPU上执行的一系列过程和"进程调度"是一样的
2.JAVA中如何使用线程
线程是操作系统提供的概念,操作系统提供了一些操作线程的API(应用程序编程接口)(Application Programming Interface)
这里注意原生api是C语言的,不同操作系统的api是不一样的
JAVA对其进行了封装,也就是Thread类,这也意味着我们的JAVA代码在不同操作系统上都是可以正常使用的
创建第一个线程
1.继承Thread,重写run
创建线程要使用Thread类,Thread是属于java.Lang的,默认import
Thread本身有一个run方法(相当于线程的入口)
我们要写一个MyThread 来继承Thread 重写run来创建线程
t.start()相当于我们创建一个新的线程,通俗来讲就是多了个执行流来干活
我们可以利用Thread.sleep( 单位为ms)来让线程休眠,也就是让当前线程暂时放弃CPU,休息一会后再执行
注意在使用这一方法的时候,会有异常

这时我们的处理方法只能是Try-Catch,无法throws(父类无)
class MyThread extends Thread{
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello thread");
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t=new MyThread();
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
2.实现RUNNABLE,重写run
这里的本质是还是用Thread来创建线程,只不过是用了RUNNABLE接口(而不是直接重写Thread run 方法),从而会达到解耦合的目的
耦合:两个代码关联关系越大,耦合就越大
解耦合:让执行任务方式和线程这个概念的关联性降低 这样的话后期变更代码会变得更加简单
我们在写代码的时候追求高内聚低耦合
class MyRunnable implements Runnable{
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello thread");
}
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new MyRunnable();
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
3.使用匿名内部类来创建线程

这看似简单的一行代码实际上看来三件事:1.创建了一个Thread子类,匿名内部类
2.{}里面可以编写子类的定义代码,子类有哪些属性,要有哪些方法重写父类的方法
3.创建了一个匿名内部类的实例,并且将其赋值给t;
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(){
@Override
public void run() {
while(true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
这样的我们就可以少定义一些类了,如果某个代码是一次性的,就可以使用匿名内部类的方法
4.使用匿名内部类并实现RUNNABLE来创建线程
使用RUNNABLE,任务和线程是分开的,降低了耦合
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new Runnable() {
@Override
public void run() {
while(true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
5.针对三和四进行改进,引用lambda函数(这也是我们推荐的做法)
lambda函数本质是一个匿名函数,最主要的用途是作为"回调函数"
ps:回调函数
1.c语言中函数指针的应用场景就有回调函数 2.JAVA数据结构中,优先级队列指定排序方式
JAVA中方法必须要寄托于类存在,"函数式接口"()->{}
其实这一方法的本质是创建了一个匿名的函数式接口的子类,并且创建出了对应的实例,并且重写了里面的方法(编译器做的事)
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
Thread类的其他属性及常见方法
Thread的构造方法
|-----------------------------------------------|---------------------------------------|
| 方法 | 说明 |
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 可以给线程起名字,线程的名字不会影响线程的执行,名字的意义是方便程序员调试 |
| Thread(Runnable target,String name) | 使用Runnable对象创建线程对象并命名 |
| (了解)Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
我们可以在jconsole上看到线程,可以通过给线程命名来更好的调试
Thread的几个常见属性
1.
|-----|------------------------------------|
| 属性 | 获取方法 |
| ID | getID() JAVA中给线程分配ID,标识线程的身份,类似pid |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
2.isDaemon()
isDaemon() 是否是守护线程
守护线程这里==后台线程
那么什么是后台线程,什么是前台线程呢?
main线程虽然结束了,但t1、t2仍然在运行,我们可以认为t1、t2是前台线程,也就是说线程的存在能影响进程是否继续存在,这样的线程叫做"前台线程";
像JVM自带的线程就属于"后台线程",也就是说不会影响进程的存在的线程就是"后台线程"
而isDaemon()就是用来判断是不是后台线程的方法
我们可以通过setDaemon来修改线程的属性
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.setDaemon(true);
t.start();
for (int i = 0; i < 3; i++) {
System.out.println("hello main");
}
System.out.println("main 结束");
}
}
3.isAlive()
是否存活
这里我们要明白,前台线程和后台线程都是有多个的,一个前台线程的结束并不一定会影响是否存活,只有当前台线程全部结束时,进程才会结束
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("hello t");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
while(true){
System.out.println(t.isAlive());
Thread.sleep(1000);
}
}
}
4.isInterrupted()
是否被打断
如何开始一个线程
线程.start()
public class Demo9 {
public static void main(String[] args) {
Thread t=new Thread(()->{
while(true) {
System.out.println("hello t");
}
});
t.start();
}
}
如何中断一个线程?
目前常见的两种方法:1.通过共享的标记来进行统治 2.通过调用isInterrupted()方法来通知
实例一 :使用自定义的变量来作为标志位
public class Demo10 {
private static boolean isFinished=false;
public static void main(String[] args) throws InterruptedException {
//boolean isFinished=false;
Thread t=new Thread(()->{
while(!isFinished){
System.out.println("hello,thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("thread 结束");
});
t.start();
Thread.sleep(3000);
isFinished=true;
}
}
实例二:使用Thread对象的isInterrupted()方法来通知线程结束
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//throw new RuntimeException(e);
//线程掀桌了
break;
}
}
System.out.println("t线程结束");
});
t.start();
Thread.sleep(3000);
System.out.println("main线程尝试终止线程");
t.interrupt();
}
}
如果没有break可以结束吗,理论上是可以的,但实际上这段代码把sleep唤醒了,这种情况下,就把isInterrupted到标志位设置回false了
这样的设定让决定权交给被终止的线程本身
5.等待一个线程--join()
多个线程之间的调度是并发执行的,随机调度的,但这并不是我们想要看到的,join能够要求多个线程之间结束的先后顺序
public class Demo12 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
t.join();
Thread.sleep(2000);
System.out.println("主线程结束");
}
}
在主线程中调用t.join就是让主线程等到t线程先结束,也就是说main线程会出现"阻塞等待"
只要t不结束,主线程的join就会一直等待下去,这样的做法并不合理,更加合理的做法应该是给join设置一个时间,这样的话就不会盲目的等待
6.线程调用
public static Thread currentThread()
哪个线程调用这个方法就会返回哪个线程(有点类似this)
7.休眠当前线程
sleep,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间
sleep(0) 意味着让当前的线程立即放弃CPU资源,等待操作系统重新调度