多线程
································································
一、线程
1.什么是线程
一个线程是一个执行流,每个线程都可以按顺序执行自己的代码,多个线程"同时"执行自己的代码。
2.为什么要有线程
创建销毁进程的开销大,尤其是频繁操作,引入线程,创建销毁开销更加小更加快
3.进程与线程的关系与区别
1.进程包含线程 ,每个进程都有一个或者多个线程
2.进程与进程之间不共享资源 ,进程是操作系统资源分配的基本单位,而线程与线程之间共享资源 ,所以当一个进程崩溃不会影响其他进程 ,但是一个线程挂了可能其他线程(整个进程)都崩溃 (这就是线程安全相关问题及时捕获可能不会崩溃)
3.线程是CPU上调度执行的最小单位 ,如果一个线程包含多个进程,此时多个进程各自去cpu上调度执行
4.一个进程中的线程共用一个文件描述符表和内存指针,但是每个线程都有自己的调度相关的(状态、优先级、上下文、记账信息),一个进程并不是越多越好,过多线程产生的调度开销也很大,会拖慢程序的性能
4.创建线程
对于线程,操作系统提供一些api(应用程序编程接口)给程序员使用,操作系统提供的原生线程api是c语言的,而且不同操作系统的线程api不同,java对上述内容进行统一封装,如Thread类(标准库中提供的类)。
方法1:继承Thread类创建一个线程类
java
class Mythread extends Thread{
@Override
public void run() {
while(true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class demo1 {
public static void main(String[] args) {
Mythread mythread=new Mythread();
//start方法创建一个线程,多一个执行流,像是多一个人干活
mythread.start();
while (true) {
try {
//sleep静态方法让当前线程暂时放弃CPU,过1000毫秒后继续执行
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello main");
}
}
}

运行结果显示有时候先输出main有时先输出thread,这是因为多个线程的调度顺序是随机的,无法预测(唯一可以做的就是设置优先级,但是对于操作系统也不一定严格执行。
方法2:实现Runnable接口
java
class MyRunnable implements Runnable {
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class demo2 {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
Thread thread=new Thread(runnable);
thread.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
方法3:使用内部类操作方法1
java
public class demo3 {
public static void main(String[] args) {
Thread thread=new Thread(){
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
thread.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
对于匿名内部类:
Thread thread=new Thread(){};
1.创建一个Thread的子类,子类匿名
2.{}中写子类的属性,方法
3.创建匿名内部类的实例,并且把实例赋值给thread
方法4:使用内部类操作方法2
java
public class demo4 {
public static void main(String[] args) {
Runnable runnable=new Runnable(){
@Override
public void run() {
while (true){
System.out.println("hello thread");
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" +
"");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
方法5:使用lambda表达式创建Runnable子类对象
java
public class demo5 {
public static void main(String[] args) {
Thread thread=new Thread(()->{
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t1");
thread.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
lambda("匿名函数")(主要需要方法但必须依托类存在使用"函数式接口"简化代码)
()-> 创建一个匿名的函数式接口的子类,并且创建出对应实例重写方法
- lambda访问外部变量的限制
如果把在lambda中使用的变量,定义成局部变量是否可以?
如果要把变量定义成局部变量,则这个变量不能修改,或者直接设置成final 。这是由于变量捕获 ,因为lambda是回调函数,系统创建线程后才会执行,可能当main主线程执行完局部变量都销毁了,而lambda还需要使用,所以java会把变量拷贝一份到lambda里面 ,这样即使外面变量销毁也不影响lambda里面使用,而拷贝也就意味着变量不能进行修改 。如果这个局部变量是个引用类型,那么这个引用本身不可以修改 ,不能指向其它对象,而对象本体是可以修改的,因为对象本体是JVM垃圾回收机制管理,不会随着main函数结束而销毁。
如果是成员变量呢?
将变量改为成员变量,就不会报错,因为此时不再是"变量捕获"语法,而是"内部类访问外部类的成员 "语法。
lambda本质是一个函数式接口,相当于一个内部类,isFinished就是外部类的成员,内部类本身就可以访问外部类的成员,成员变量生命周期是由垃圾回收GC管理的,所以lambda不用担心销毁,就不必拷贝,就可以修改了。
5.线程构造方法与属性
Java Thread 常见构造方法
| 构造方法签名 | 方法说明 | 核心参数解释 |
|---|---|---|
Thread() (方法1) |
创建一个新的线程对象,线程名称为默认格式(如Thread-0),无目标任务,需重写run()方法 |
无参数 |
Thread(Runnable target)(方法2) |
创建一个新线程,将Runnable接口的实现类作为线程执行的目标任务 |
target:实现了Runnable接口的对象,线程启动后会执行该对象的run()方法 |
Thread(String name) |
创建指定名称的线程对象,便于调试和日志排查,无目标任务 | name:自定义的线程名称(如"业务处理线程-1") |
Thread(Runnable target, String name) (方法5) |
创建指定目标任务且指定名称的线程(最常用的构造方法之一) | target:线程执行的任务;name:线程自定义名称 |
Thread(ThreadGroup group, Runnable target)(了解) |
将线程归属到指定的线程组,并指定执行任务 | group:线程所属的ThreadGroup(用于批量管理线程);target:执行任务 |
常见属性
| 属性(获取方法) | 数据类型 | 属性说明 |
|---|---|---|
getId() |
long |
获取线程的唯一ID |
getName() / setName(String name) |
String |
获取/设置线程名称 |
getPriority() / setPriority(int newPriority) |
int |
获取/设置线程优先级 |
getState() |
Thread.State |
获取线程状态 |
isDaemon() / setDaemon(boolean on) |
boolean |
判断/设置是否为守护线程 |
isAlive() |
boolean |
判断线程是否存活 |
isInterrupted() |
boolean |
判断线程是否被中断 |
interrupted()(静态方法) |
boolean |
判断当前线程是否被中断 |
重点属性
1)后台线程
| isDaemon() / setDaemon(boolean on) | boolean |
判断/设置是否为守护线程(后台进程)
前台进程例如我们自己创建的线程包括main主线程,但我们可以通过setDaemon方法将它改为后台线程 ,后台进程例如JVM自带的具有特殊功能的线程,跟随整个进程持续执行。JVM会在一个进程的所有前台线程结束后才结束运行,而后台线程对结束无影响
2)是否存活(简单理解为run方法运行结束)
| isAlive() | boolean | 判断线程是否存活 |
java中创建的Thread对象与系统的线程一一对应,但是两者的生命周期并不相同,可能Thread对象依然存在但是对应的系统的线程已经销毁 ,

上面for循环执行完对象thread可能依然存在
3)启动一个线程start方法
每个Thread对象,start是一次性的 ,进行调用系统的api,每次创建新线程,都得创建一个新的Thread对象。
4)中断一个线程isInterrupted()
中断即线程停止不会再恢复

lambda的定义在new Tread之前,所以不能直接在里面使用,但是java的Thread对象中提供了现成的变量,我们可以使用Thread.currentThread()静态方法,哪个线程中调用,获取的就是哪个线程的Thread引用


