一.JAVA中Thread类创建线程的方式
1.1 创建一个类,通过继承(class)Thread,重写run方法
package thread;
class MyThread extends Thread{
@Override
public void run() {
while(true) {
System.out.println("hello world");
try {
//这里只能try catch,不能throws
//此处是方法重写,对于父类的run方法;来说,就没有throw xxx 异常这样的设定
//在重写的时候,也就不能throws异常了
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread= new MyThread();
myThread.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
1.2 创建一个类,实现(实现interface)Runnable,重写run方法
package thread;
class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//使用Runna的方式创建线程
public class Demo2 {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
Thread t=new Thread(runnable);
//将runnab作为构造方法的参数传递到Thread的构造方法里
t.start();
while(true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.3 继承Thread,重写run,属于匿名内部类
package thread;
//通过匿名内部类,创建线程
public class Demo3 {
public static void main(String[] args) {
Thread t= new Thread(){
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
while(true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
针对上述代码中run()这个构造方法的理解:
1)创建一个子类,这个子类继承自Thread,但是这个子类,是没有名字的(匿名),另一方面,这个类的创建,是在Demo3这个类里面
2)在子类中重写了run这个方法
3)创建了该子类的实例,并且使用了t这个引用来指向
1.4 实现Runnable,重写run,属于匿名内部类
匿名内部类,指优先级队列,指定比较规则
package thread;
public class Demo4 {
public static void main(String[] args) {
Thread t= new Thread(new Runnable(){
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
}
}
new Runnable(),此处是创建Runnable的子类,通过构造方法传给Thread
1.5 使用lambda表达式
package thread;
//使用lambda表达式
public class Demo5 {
public static void main(String[] args) {
Thread t= new Thread(() ->{
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
lambda表达式,本质上就是一个"匿名函数"
这样的匿名函数,主要就可以用来回调函数来使用
经常会用到回调函数的场景:
1.服务器开发:服务器收到一个请求,触发一个对应的回调函数
2.图形界面开发:用户的某个操作,触发一个对应的回调
二. Thread类及常见方法
2.1 Thread类和常见构造方法
方法 | 说明 |
1 Thread | 创建线程对象 |
2 Thread(Runnable target) | 使用Runnable对象创建线程对象 |
3 Thread(String name) | 创建线程对象,并命名 |
4 Thread(Runnable target,String name) | 使用Runnable对象创建线程和对象 |
5 Thread(ThreadGroup,Runnable target) | 线程可以用来分组管理,分好的组即为线程组,了解即可 |
- 例1:Thread t1=new Thread();
- 例2:Thread t2=new Thread(new MyRunnable());
- 例3:Thread t3=new Thread("这是我的名字");
- 例4:Thread t4=new Thread(new MyRunnable(),"这是我的名字");
2.1 方法3的实例
package thread;
public class Demo6 {
public static void main(String[] args) {
Thread t= new Thread(() ->{
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"myThread");
t.start();
}
}
2.2 Thread的几个常见属性
属性 | 获取方式 |
1 ID | getId() |
2 名称 | getName() |
3 状态 | getState() |
4 优先级 | getPriority() |
5 是否后台进程 | isDaemon() |
6 是否存活 | isAlive() |
7 是否被中断 | isInterupted() |
2.2 属性1
线程的标识(在JVM这里给线程设定的标识)
类似于一个人可以有多个名字,一个线程也可以有好多个名字
- JVM有一个身份标识
- pthread库(系统给程序员提供的操作线程的api,也有一个线程的身份标识)
- 内核里,针对线程的pcb还有身份标识
以上三点都是相互独立的
2.2 属性3和4
java中的线程状态和操作系统中有一定从差异的
设置/获取优先级,作用不是很大
线程的调度,主要还是系统内核来负责
系统调度的速度实在是太快了
2.2 属性5
后台线程(守护线程),后台线程不影响进程结束
前台线程,前台线程会影响到进程结束,如果前台没执行完,进程是不会结束的
一个进程中所有的前台线程都执行完,退出了,此时即使存在后台线程仍然没执行完,也会随着进程一起结束
创建的线程,默认是前台线程,通过setDaemon显示的设置成后台
package thread;
public class Demo7 {
public static void main(String[] args) {
Thread t=new Thread(() ->{
while(true){
System.out.println("hello thread");
}
});
//设置成后台
t.setDaemon(true);
t.start();
//使进程展现3秒钟,可以让进程结束得慢一点
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.2 属性6
Thread对象,对应的线程(系统内核中)是否存活
Thread对象的生命周期,并不是和系统中的线程完全一致
一般就是Thread对象,先创建好,手动调用start,内核才真正创建出现线程
消亡的时候,可能是thread对象,先结束了生命周期(没有指向这个对象),也可能是thread对象还在,内核中的线程把run执行完了,就结束了
2.3 启动一个线程-start()
在系统中,真正创建出线程(1和2在系统内核完成)
- 创建出pcb
- 把pcb加入到对应链接
package thread;
public class Demo8 {
public static boolean isQuit=false;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(() ->{
while(!isQuit){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//主线程这里执行一些其他逻辑之后,要让线程结束
Thread.sleep(3000);
//这个代码就是在修改前面的设定的标志位
isQuit=true;
System.out.println("把t线程终止");
}
}
1)程序员手动设置标志位,通过这个手动设置的标志位,来让run尽快结束
问题:为社么isQuit写作成员变量,就能被访问到,写作局部变量就会报错,是否有别的方法让局部变量不报错
lambda可以捕获到外面的变量
既然lambda表达式,执行时机是更靠后的
这就导致后续真正执行lambda的时候,局部变量isQuit是否已经被销毁
这种情况是客观存在的,让lambda去访问一个已经被销毁的变量很明显是不合适的
lambda引入了“变量捕获”的机制
lambda内部看起来是在直接访问外部的变量,其实本质上是把内部的变量复制了一份,到lambda里面(这样可以解决生命周期的问题)
但是,变量捕获这里有个限制
要求捕获的是final(至少看起来是final)
将boolea isQuit =false;修改为final boolea isQuit =false;则不会报错
package thread;
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
final boolean isQuit=false;
Thread t=new Thread(() ->{
while(!isQuit){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(3000);
System.out.println("把t线程终止");
}
}
2)直接Thread类,给我们提供好了现成的标志位,不用手动去设置标志位
package thread;
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(() -> {
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
t.start();
Thread.sleep(3000);
//把上述的标志位设置为true
t.interrupt();
}
}
break
表示遇到错误直接跳出循环
Thread.currentThread()
获取到当前线程对象(Thread)
Thread提供了静态方法,currentThread
在哪个线程调用这个方法,就能够获取到哪个线程的引用
isInterrupted
Thread对象内部提供了一个标志位(boolea)
true:线程应该要结束 false:线程不必结束
2.4 等待一个线程 .join()
多个线程是并发的,具体的执行过程 ,都是由操作系统进行调度的
操作系统的调度线程的过程,是“随机的”,无法确定线程执行的先后顺序
上述随机性,程序员不太喜欢,更喜欢的是“确定”的东西
等待线程,就是一种规划 线程结束 顺序的手段
举个例子:
A、B两个线程,希望B先结束,A后结束,此时就可以让A线程中调用 B.join() 的方法
此时,B线程还未执行完,A线程就会进入“阻塞”状态,就相当于给B留下了执行的时间,B执行完毕后,A再从阻塞状态中恢复过来,并且继续往后执行
如果A执行到B.join()的时候,B已经执行完了,A就不必阻塞了,直接往下执行就行了
阻塞:让代码暂时不继续执行了(该线程暂时不去cpu上参与调度)
sleep也能让线程阻塞,阻塞是有时间限制的
join的阻塞,则是“死等”、“不见不散”
package thread;
public class Demo10 {
public static void main(String[] args) {
Thread b=new Thread(() ->{
for(int i=0;i<5;i++){
System.out.println("hello b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("b结束了");
});
Thread a=new Thread(() ->{
for(int i=0;i<3;i++){
System.out.println("hello a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("a结束了");
});
b.start();
a.start();
}
}
a先结束,b后结束
package thread;
public class Demo10 {
public static void main(String[] args) {
Thread b=new Thread(() ->{
for(int i=0;i<5;i++){
System.out.println("hello b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("b结束了");
});
Thread a=new Thread(() ->{
for(int i=0;i<3;i++){
System.out.println("hello a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
//如果此时还未执行完毕,b.jion就会产生阻塞的情况
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a结束了");
});
b.start();
a.start();
}
}
加上b.join()时,则是b先结束,a后结束