JAVAEE-2

回顾

首先回顾一下线程和进程,进程是系统分配资源的基本单位,线程是调度执行的基本单位,一个进程可以包含多个线程,每一个线程都是一个单独的执行流,都有各自单独的任务,线程之间会相互影响,而进程不会,我们把线程称为轻量级进程,让同一个进程的多个线程去共享一份系统资源(创建线程,能够在一定程度上减少申请资源和销毁线程时释放资源带来的开销);

每个线程都是一份单独的执行流,都可以单独的参与CPU的调度。共享一份系统资源(文件描述符表和内存指针)。

操作系统,提供了一些操作线程的API,用c或c++实现,通过JDK封装成java风格的api。下面我们讲讲如何使用这些API。

我们将Thread类称为线程,而里面的run方法是线程执行的入口。每个线程跑起来都会去执行一定的逻辑。

所谓线程,就是在操作操作系统的内核,操作系统最核心的部分。

操作系统的内核态:有一些程序,需要针对一些系统提供的软硬件资源进行操作,这些操作需要调用系统提供的API,进一步在内核中完成这样的操作。

接下来我们讲线程是如何创建的

class Mythread extends Thread{//我们自己创建的线程需要继承类Thread,重写里面的run方法,我们可以把run方法当作线程执行的入口。
pubic void run(){
while(true){
System.out.println("hello thread!");
}
}

}
public class Main(){

public static void main(){

Thread t=new Mythread();//通过这个操作来创建线程
t.start();//通过start方法来进行该线程的启动
while(true){
System.out.println("hello Main!");

}
}


}

两个线程,一个是Main方法,一个是自己创建的t线程,两个各自执行各自的,互不干扰。

********************************************************

这里有一个点需要注意,当有多个线程的时候,这些线程执行的先后顺序,是不确定的。

因为在操作系统内核中,有一个“调度器模块”。这个模块的实现方式,是一种类似于“随机调度”的效果。

随机调度

1.一个线程,什么时候被调度到CPU上执行,时机是不确定的。

2.一个线程,什么时候从cpu上下来,让其他线程被调度,时机是不确定的。

我们称为“抢占式先机”,为后面多线程的线程安全问题埋下伏笔。

另外关于谁先执行的问题,主线程,调用start方法之后,就立刻往下打印了。于此同时,内核需要通过刚才线程的API构建出线程来,并且去执行run方法。

一般来说,是主线程执行的更快,因为创建线程本身也有一定的开销,不能说为0,在第一轮打印,创建线程开销本身影响下,导致hello Thread!比hello Main!略慢一筹。

进程创建线程第一线程是最大的,剩下的开销都比较小,但都不是0;

线程创建的五种方式:

1.继承类Thread来创建线程

class MyThread extends Thread{

@Override
public void run(){
while(true)
{
System.out.println("hello Thread!");
}
}


}
public class Main{

public static void main(String[]args){

Thread t=new MyThread();
t.start();
while(true)
{
System.out.println("hello Main!");
}
}

}

2.使用Runnable接口来创建线程

public class Main{
public class MyThread implents Runnable{


public void run(){


while(true)
{
try{Thread.sleep(1000);
}
catch(InterruptedException e)
{throw new RuntimeException(e);
}
System.out.println("hello Thread!");
}
}


}


public static void main(String[]args)


{
Thread t=new Thread(new MyThread());
t.start();
   while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

}
}

Runnable接口可以抽象出一段可以实现的代码

3.使用匿名内部类来创建线程

public class Main{

public static void main(String[]args){
Thread t=new Thread(){
public void run()
{
   while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

}


};
t.start();
        while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
}

}

4.使用接口匿名

public class Main{

public static void main(String[]args){
Thread t=new Thread(new Runnable(){

public void run()
{
   while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

}


});
t.start();
        while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
}

}

5.最常用的,lamab表达式来创建

public class Main{

public static void main(String[]args){
Thread t=new Thread(()->{
   while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }




});
t.start();
        while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
}

}

Thread的创建方法

Thread();
Thread(String name);//创建带名字的线程
Thread(Runnable target);使用RunNbale来创建线程
Thread(Runnable target,String name);
Thread(ThreadGroup group,Runnable target);ThreadGroup为线程组,可以分组管理

Thread的相应属性

Thread t=new Thread();//创建线程
t.getId();//获取线程ID;
t.getName();//获取线程名称
t.getState();//获取线程状态
t.getPriority();//获取优先级
t.isDaemon();//是否为后台进程
t.isAlive();//判断线程是否存货
t.isInterrupted();//判断线程是否阻塞
public class ThreadDemo7 {
    public static void main(String[]args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("hello Thread!");

            }
        },"这是我的线程");
        t.start();
        Thread.sleep(1000);
        System.out.println(t.getName());
    }
}

运行结果如上图所示

咱们在代码创建线程的时候,默认是前台进程,前台进程会阻止进程结束,只要前台进程没执行完,进程就不会结合,即使Main线程已经执行完了.

  t.setDaemon(true);//注意,setDaemon要放在start之前,不然会报错
        t.start();

之后的运行结果.

在使用setDaemon(true)之后,true是后台,不会阻止进程结束.只要前台进程一结束,后台进程无论结束没结束都必须跟随前台进程结束.

Thread创建出来的线程是有生命周期的

public class Threaddemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println("start之前的存活"+t.isAlive());
        t.start();
        System.out.println("start之后的存活"+t.isAlive());
        Thread.sleep(2000);
        System.out.println("2000ms之后的存活"+t.isAlive());

    }
}

运行结果,可以使用isAlive函数来进行查看线程是否哦存活.

当线程执行完之后,内核中的线程就会释放了(pcb中的内核就会释放了).

*******************在这里要注意一点,start方法只能执行一次*************

中断线程

要想中断一个线程,必须要让他的代码逻辑符合中断的一个机制,就需要你main线程的配合,不然这是没有办法进行终止的,我们可以来设置标志位来中断线程,比如:

public class ThreadDemo9 {
    private static boolean isQuit=false;
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(!isQuit){
                System.out.println("我是一个线程,正在工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程工作执行完毕");

        });
        t.start();
        Thread.sleep(3000);
        isQuit=true;

    }
}

这是运行结果.

设置标志位来中断线程

public class ThreadDemo10 {
    private static boolean isQuit=false;
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("我是一个线程,正在工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
            }
            System.out.println("线程工作执行完毕");

        });
        t.start();
Thread.sleep(3000);
        System.out.println("让线程结束");
        t.interrupt();


    }
}

这里我们使用Thread.curreThread()来获取当前实例

这里我们可以看出打印了异常,并且线程并没有被终止.

为什么呢?

在这里,我们通过interrupt方法设置了标志位,但确将sleep函数提前唤醒了,这使得发生了异常,sleep函数自动清除还原了标志位.因此,循环并没有并中断(这里要注意,之后或许并不只有一种让标志位重塑),

提前唤醒,会做两件事

1.抛出Inter若特点Exception(紧接着就会被catch捕捉到)

2.清除Thread对象的isInterrupted标志位,通过interrupt方法已经将标志位设置为true了,但是sleep唤醒,又将标志位设回false了,所以循环会继续进行执行.

要想让线程结束,只需在catcj里加上break就行.

这里的sleep清除是有一些逻辑在里面的,是为了给我们操作空间,你到底是想让线程沉睡,还是让他唤醒,这里有歧义,所以sleep会将标志位还原,之后怎么设置,我们可以在catch里面加上自己的一些逻辑.

1)让线程立即结束,加上break;

2)不让线程结束,继续执行,不加break;

3)让线程执行一些逻辑之后,再结束,写一些其他代码,再break;

t.interrupt();让线程结束

Thread.currentThread().isInterrupted();判断标志位是否为true.

对于一个服务器程序来说,稳定性是最重要的.

无法保证服务器一直不出问题,这些所谓的问题就会在java代码中,以异常的形式体现出来.

可以通过catch语句,对这些语句进行处理

实际开发中的catch做的代码

1)可能会尝试自动恢复

能自动恢复就尽量自动恢复,比如出现了一个网络通信相关的异常,就可以在catch中尝试重连网络.

2)记录日志(异常信息记录到文件里面)

有些情况,并非很严重的问题,只需要把这个问题记录下来,(并不需要立即解决),等之后有空了再去解决.

3)发出报警

针对一些比较严重的问题,包括但不限于发短信邮件,打电话

4)也有少数的正常的业务逻辑,会依赖到catch,比如文件操作中有的方法,就是要通过catch结束循环

(非常规用法)

Java中,线程的终止是一种"软性操作".必须要对应的线程配合,才能把终止执行下去.

相比之下,系统原生的API其实提供了强制了终止线程的操作,无论你线程是否配合,无论线程执行到哪个代码,都能强行把这个线程给干掉.

java的api并没有提供这样的操作.

这种操作,其实是弊大于利,如果强行干掉一个实验,很有可能线程执行到一半,就可能出现一些残留的临时性质的"错误"的数据.

例子:加入这个线程正在执行写文件的操作,写文件的数据有一定的格式要求(写一个图片文件)

如果图片写到一半,线程嘎了.图片文件就无法正确打开了.

lambda表达式/匿名内部类是可以访问到外面定义的局部变量的(变量捕获语法规则)

lambda要求捕获的临时变量是final类型或者事实final(即要求没有修改)

当写成成员变量的时候就可以,因为内部类本来就可以访问外部类成员.

这里不能让局部变量修改是因为生命周期可能不一样.

以局部变量boolean isQuit为例

java在变量捕获中,是在lambda表达式在自己的栈帧中创建一个新的isQuit,并把外面的值拷贝过来,

为了避免isQuit的值不同步,java干脆就不让你修改isQUit的值.

如何等待线程结束

t.join();

package thread;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            int n=5;
            for(int i=0;i<n;i++)
            {
                System.out.println("我是一个线程,正在工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        t.join();//该操作让线程结束
        System.out.println("这是主线程, 期望这个日志在 t 结束后打印");
    }
}

可以看到执行的一个结果

多个线程的执行顺序是不确定的(抢占式执行,随机调度)

虽然程序底层的调度是无序的,但是可以在程序中,调用一些api来影响到线程执行的顺序.

join就是一种方式

影响线程执行的先后顺序.

在main线程中调用t.join()就是让main线程等待t线程结束.

执行join的时候,就看t是否在正常运行

如果t在运行中,那么main就会阻塞(main线程暂时不会参与到cpu的调度中)

如果t运行结束,main线程就会从阻塞中恢复过来,并且往下执行~

(阻塞,让这两个线程结束的先后时间,产生了先后关系)

*********************

一个典型情况就是使用多个线程并发进行一系列的计算

用一份线程阻塞等待上述计算线程,等到所有的线程都计算完了,最终这个线程汇总结果.

线程执行顺序是不确定的,线程执行的任务的时间也是不可预估的

如果单个线程,无法发挥出多核cpu的优势

如果是多个线程,势必要有一个线程进行汇总结果.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值