Java多线程1---线程基础:创建线程三种方式、线程常用方法、线程的生命周期、线程通信等

一、线程理论

1、线程和进程

(1)进程----一个应用程序(软件--QQ)----进程间内存不共享

        线程----进程中的一个执行场景(和不同人聊天)----线程间栈内存和程序计数器不共享,堆内存和方法区共享 

 (2) JVM是一个进程

      方法区和堆内存共享-----一个方法区,一个堆

      栈内存不共享,一个线程一个栈------多个栈

2、单核多核cpu与线程并发之间关系

      单核cpu--在一个时间点上实际上只能处理一件事

              如线程a。b并发执行,实际上是两个线程循环切换执行,速度很快,让我们产生同时进行的错觉

       多核cpu---在一个时间点上可以处理多件事,真正的并发执行

              如4核,可以4个进程并发执行      

二、创建线程的三种方式

(一)无返回值---Thred类和Runnable接口

1、继承Thread类--单继承

class MyThread extends Thread{

    public void run(){
      //该线程要完成的功能
   }
}

2、实现Runnable接口

class MyThread implement Runnable{

    public void run(){
      //该线程要完成的功能
   }
}

  推荐使用: 避免单继承局限性,方便同一个对象被多个线程使用

3、线程的启动---start() 

     start()启动线程,开辟新的栈空间,瞬间结束,线程run方法开始执行。

      启动的线程与目前主线程后续代码并发执行

4、run()与start()区别

  • 直接线程对象调用run方法,不会启动新线程,不会新分配线程栈空间,仍然是单线程自上而下执行,run方法执行结束后,才继续执行主线程剩下代码
  • 线程对象先start线程,会启动一个新线程,开辟新的栈空间,然后系统自动调用run方法,run方法和主线程剩下代码并发执行

(二)有返回值---实现callable接口

1、作用

     可以拿到线程的返回值,即线程的执行结果

2、实现callable接口中没有run方法,其中call()方法相当于run方法,

         call方法有返回值,而run方法没有返回值

3、优缺点:

       优点:可以获取到线程的执行结果

       缺点:效率较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低

4、具体实现过程 

方式一

//线程的定义
class MyCallable implements Callable<String>{
        @Override
        public String call() throws Exception{
            Thread.sleep(2000);
            return " world!";
        }
    }

//线程的调用
 public static void main(String[] args) throws Exception{
     ExecutorService executorService=Executors.newSingleThreadExecutor();
     MyCallable myCallable=new MyCallable(); //new线程对象
     Future<String> result =executorService.submit(myCallable);//启动线程,返回值result接收                                                
     System.out.println("hello "+result.get());//通过get方法获取返回结果
    }

方式二

public static void main(String[] args){
  //1、创建一个“未来任务类”的对象 FutureTask
  //   给 “未来任务类”的对象 传递callable接口实现类的对象【传参】
  //   JUC包下的,属于java的并发包(JDK8的新特性)       
 FutureTask task=new FutureTask(new Callable()){
   public Object call() throws Exception{
    //功能代码
   }
  });
 
   //2、创建线程并【传入task对象】
   Thread t=new Thread (task);
   t.start();//启动t线程

   //3、这里是main方法,通过FutureTask对象获取t线程的执行结果
   Object obj=task.get();
   System.out.println("线程执行结果"+obj);

  //4、这里程序的执行必须等待t线程执行结束返回结果给get()方法,此处主线程和t线程非并发执行   
   System.out.println("线程执行结果"+obj);


}

三、线程的生命周期 

   1、新建状态:new线程对象

   2、 就绪状态:start调用之后(可运行状态),表示当前线程具有抢夺cpu时间片的权利

   3、 运行状态:run方法执行,当占有的cpu时间片用完之后,会重新回到就绪状态继续抢夺

   4、 阻塞状态:遇到阻塞事件后,如sleep中止正在执行的线程,线程会放弃占有的cpu时间片

    5、 死亡状态:程序结束

【注】

  • 同线程在就绪状态运行状态频繁切换-----实现线程并发
  • 单核线程并发实质还是单线程,只是在其他线程还没有运行完之前允许其他进程进行时间片的抢夺
  •  若没有多线程并发,则另一个线程只能等当前线程执行完,然后再开始执行

四、线程常用方法

1、获取当前线程对象----Thread.currentThread()

    main方法中---当前线程就是main方法

    线程类中---谁在执行run方法,当前对象就是谁

2、获取和设置线程名称---线程对象.setName()

3、线程休眠----Thread.sleep(毫秒)

    作用:让当前线程进入阻塞状态,放弃占有的cpu时间片

(1)线程的调用:类名.sleep()

(2)静态方法与对象无关,只能用类名进行调用,且在哪调用,哪个线程休眠

        如,在main方法中调用终止主线程,在自定义线程中调用终止自定义线程   

4、唤醒睡眠---线程对象.interrupt() 

 (1) interrupt()中断方法

  •     interrupt()不能中断在运行中的线程,它只能改变中断状态,线程仍然继续运行
  •     interrupt()的作用主要用来唤醒阻塞中的线程
  •      中断线程主要用 标记+return方法

(2)相关方法

  • interrupt(),在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号——线程中断状态已被设置。至于那个线程何去何从,由具体的代码实现决定。
  • isInterrupted(),用来判断当前线程的中断状态(true or false)。
  • interrupted()是个Thread的static方法,用来恢复中断状态

   参考:Java中interrupt的使用 - 无名码者 - 博客园

5、终止线程---stop()

一个线程本来运行结束需要10s,需要在5秒后终止该线程,即只让线程执行5秒

(1)强行终止线程的执行---stop()

    缺点:容易丢失数据,

       这种方式是直接杀死线程,线程没有保存的数据容易丢失

(2)合理终止线程的执行---打标记 flag=true,当flag=flase--->return;

       在return前有想要保存的数据可以加save语句进行保存。

6、守护线程----线程对象.setDaemon(ture)

     作用:调用守护线程的用户线程结束,守护线程随之结束,即使守护线程是死循环需一直运行

               即,守护线程的生死不由其本身的运行状态决定,由其用户线程决定

               如,t是守护线程,main方法中调用并启动该线程,此时main方法是其用户线程

                      当main方法执行结束后,t线程也随之结束,不管run方法是否执行完毕

7、设置线程调度优先级---Thread.currentThread().setPriiority(1)

     优先级设为1

8、线程让位----Thread.yied()

      在哪个线程中调用该函数,哪个线程等待,不执行(静态类,类名.调用)

9、线程合并----t.jion()

     谁调用jion函数,谁先执行,其他线程等待(线程对象.调用)

10、线程等待----对象.wait()

   作用:   让当前线程等待并释放占有的共享对象锁

11、唤醒线程-----对象.notify()

   作用:-唤醒线程,但不释放共享对象之前占有的锁

   notifyAll()---唤醒该对象上等待的所有线程

【注】sleep()和wait()区别

     1、sleep来自Thread类----线程对象的方法【Thread.sleep()】----让线程休眠进入阻塞状态,

                                         放弃占有的cpu时间片,但不释放锁

          wait来自object类----Java对象的方法【对象.wait()】-----让本线程等待唤醒再执行,

                                      释放占有的锁

    2、sleep任何地方都可以,在哪个地方调用,对应哪个线程类休眠

         wait只能在同步代码块中使用,共享变量调用并等待

    3、sleep需要捕获

         wait不需要

12、计时器 ---Timer

(1)创建定时器对象

       Timer timer =new Timer();

(2)指定定时任务

        timer.schedule(要执行的任务类对象,开始执行任务时间,间隔多久执行一次)

(3)编写需要执行的任务类---extend TimerTask

//main方法
//1、创建定时器对象
Timer timer =new Timer();

//2、指定定时任务
SimpleDateFormat sdf=new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
Date firstTime=sdf.parse("2021-09-19 19:40:00");
timer.schedule(new LogTimerTask(),firstTime,1000*60*60*24*365);


//编写定时任务类---编写具体功能
class LogTimerTask extends TimerTask{
 
  public void run(){
    要定时执行的任务
  }
}

四、线程通信---生产者消费者模式

1、线程通信方式

(1)缓冲区

(2)信号灯法

2、实现线程通信方法

3、消费者生产者

  (1)利用缓冲区解决:管程法

    缓冲区空,消费者wait(),生产者生产,notify()唤醒消费者

    缓冲区满,生产者wait(),消费者消费,notify()唤醒生产者

(2)信号灯法--flag

    根据flag真假来决定某一个线程进行,真代表一个,假代表一个

五、线程调度


 1、 协同式:线程的执行时间由线程本身来控制,线程任务执行完成之后主动通知系统切换到另一个线程去执行。(不推荐)
       优点:实现简单,线程切换操作对线程本身是可知的,不存在线程同步问题。
       缺点:线程执行时间不可控制,如果线程长时间执行不让出CPU执行时间可能导致系统崩溃。

 2、  抢占式:每个线程的执行时间有操作系统来分配,操作系统给每个线程分配执行的时间片,抢到时间片的线程执行,

       时间片用完之后重新抢占执行时间,线程的切换不由线程本身来决定(Java使用的线程调度方式就是抢占式调度)。
                优点:线程执行时间可控制,不会因为一个线程阻塞问题导致系统崩溃。

  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值