java多线程---1.Thread类

一,概念    

    多线程,是拿来干什么的,我们先从计算机整体开始说起来,计算机的组成cpu+内存+硬盘,就是我们只要的硬件设备,在我看来,计算机除了cpu内部最核心的地方是拿来进行计算出来的,其他都是用来进行存放数据的,当然在组合各系统工作中,还需要很多的转换器,也就是接口,计算机之间要进行通信,那么需要进行联网,这个网络信息发送也是一个大功能,这里虽然是java的一个基础,但是先不讨论。

   计算机是拿来进行计算,其他功能都是辅助计算的,我们所谓的计算可能和你理解的1+1简单的计算不一样,这里可以理解为处理业务,一个应用代表一个进程,这里应该反过来说,我们的cpu进行处理,cpu是硬件,硬件的基础上还需要软件,操作系统来进行处理,所以扯到多线程,需要的东西还挺多的。

    进程和线程,线程是进程的小孩子,一个进程拥有很多小孩子,也就是线程,线程可以共享进程的数据,为什么要产生线程?因为我们的cpu很快,而我们的内存,其他硬件很慢,为了提高cpu的使用率,推出了线程,举一个例子:cpu在处理一个a,现在b,c,d都在排队,等待a使用完毕cpu,再使用(这里其实就联系到了操作系统的原理了),如果a需要算一个1+3,那么cpu很快就工作完成了,但是a如果需要磁盘的一个小视频。。。那么磁盘很慢啊,机械盘还需要慢慢的把手臂移动过去,而cpu什么级别的,它就需要等待很久磁盘完成任务,这个时候,cpu就是空闲的,那么就是浪费资源,我们把进程切开,把一个任务一个任务分成很小的部分进行专业的执行,需要等待的时候,直接让出cpu,让cpu忙碌起来,你这个线程需要等待的时候,就让另外一个线程进行工作,这样就大大的提高了cpu的处理效率。而在用户开起来,这些线程是一起工作的,其实是轮流的,只是用户感受不到,这叫并发,而cpu现在的发展,有4核,8核,还有一个核对应两个线程的,也就是可以同时使用多个线程的,这叫做并行

    所以一般情况下,一个应用软件就对应一个进程,而一个进程下面有多个线程,这个时候,线程之间就需要通信,统一合作完成进程交给他们的任务,这个时候问题就来了,线程a,b,c都需要对一个数字w进行操作,比如说存钱和取钱,线程和线程之间,在cpu指中轮流切换的时候,是发现不了对方的,所以大家可能都给w存钱,但是w还没有反应过来,两个操作重叠了。这就是线程安全问题。

    在操作系统中,线程分为用户线程和内核线程,用户线程是在内存中划分出来,又软件自行管理的,而内核线程是由操作系统调度的,一般应用使用的都是用户线程,但是jvm最开始是使用内核线程的模型在内存中开发出来用户线程的,但是后来jvm把线程的管理 直接交给了操作系统了,所以也算是内核线程了。

    java HotSpot JVM后台运行系统线程主要分类:

 二,Thread类

    常用Thread的构造器有两个,一个是Thread(),另外一个是Thread(Runnable target)
线程要执行的任务:run()方法;
线程的启动:调用Thread.start()方法。
两种线程创建的 区别
    1.Thread子类就继承的方式,第二种是接口实现是基于组合的计数,组合相对于继承,耦合性更低,更加灵活
    2.接口实现意味着多个线程可以通向一个Runnable实例,会出错,涉及线程安全。
    3.成本:Thread实例是需要分配栈空间,内核线程等资源的,而普通的Runnable花费成本更低,它可以传递给其他线程进行调用。
线程属性:id,name,daemon(类别),priority(优先级)(优先级是给线程调度器使用的,让它决定哪些线程优先执行)
守护线程:后台运行的线程,daemon属性为true就代表是一个守护线程,例如:垃圾回回收,内存管理都是守护线程,不管它执行是否完成,jvm关闭,守护线程就关闭,非守护线程不存在了,那么jvm就关闭。始终是被动的,不考虑你这个线程的任务。
线程组:ThreadGroup是为了管理线程方便出现的,每个线程组包含子线程Or子线程组,所以它是树状的,它的好处就是可以对线程进行批量操作(这个和后面讲 的东西好像都没有什么联系,了解一下)
  线程的生命周期
    1.新建(new Thread):new出来一个Thread的时候,并没有有进行start()
    2.就绪(runnable):线程已经被启动,执行了start()方法,但是还没有分到cpu时间片。
    3.运行(running);活到了cpu的使用权。
    4.赌塞(blocked):处于某种原因让正在运行的线程让出cpu并暂停自己的执行 如:sleep(),wait(),
    5.死亡(dead):线程执行完毕,或者线程被杀死,线程无法再进入就绪状态:例如stop()或者run()方法结束。
但是java的线程,它使用的又是另外一套术语,虽然jvm的线程已经变成了内核线程,但是它的前身还是用户线程,所以有点区别。
java线程状态:六个
    1.new新建:还没有启动。
    2.runnable可运行:复合状态:ready,running(也就是就绪+运行)
    3.blocked阻塞:碰到锁之类的,或者Io.
    4.waiting等待:wait(),join(),park(),让线程变成等待状态,waiting->runnable可以是:notify(),LockSupport.unpark(Object).
    5.timed_waiting计时等待:它和waiting相似,只不过时间到了,自动变成runnable状态。
    6.terminated终止:与死亡状态一样,要嘛是任务完成,要嘛就是抛出异常。
(如果没有东西去接收这个异常,异常就抛给方法的调用者,这样一层一层的往上抛,直到Main方法,都没有异常的处理,则线程终止。这个涉及栈帧)
为什么要把两个线程的状态术语区别来讲,因为大部分口头传述是使用的传统线程的属于,而在线程安全里面的方法调用之间的状态转换,使用的是java线程的术语,怕搞不懂,所以这里需要把两个区分开。!
原子操作:操作不可分割,在别的线程看来,你这个操作,要嘛是完成了,要嘛就是还没有进行,比如读,写操作。
可见性:一个线程对某个共享变量操作之后,其他线程都可以看到这个更新值,那么就是可见的。
上下文切换:为什么在但cpu上,也可以进行平发,就是因为时间片乱换,一个线程占用cpu一个时间片,那么cpu是如何知道是哪一个cpu的呢?上下文!
    上下文包括:通用寄存器的内容和程序计数器的内容,切出的时候,它把这两个内容保存到内存中,切入的时候,cpu又从内存中读取加载。
    哪些操作会发送上下文切换:sleep,wait,yield,join,LockSupport.park,io操作,等待锁。垃圾回收
    上下文切换的开销:主要是处理器时间开销,线程调度器进行选择的开销,从内存中读取资源到缓存中的开销。

1.isAlive()方法
public class MyThread extends Thread{
      public void run() {
            System.out.println("run= "+this.isAlive());
      }
      public static void main(String []args) {
            MyThread h=new MyThread();
            System.out.println("begin= "+h.isAlive());
            h.start();
            System.out.println("end= "+h.isAlive());
            
      }
}

结果:
2.sleep()方法
    它的作用是在指定的毫秒数内让当前“正在执行的线程”休眠,它的特性,它指定指定时间休眠的时候,是不会释放锁的。
    sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程(也就是说,让出了cpu的),但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
public class MyThread extends Thread{
      public void run() {
            try {
                  System.out.println("begin=  "+System.currentTimeMillis());
                  Thread.sleep(5000);
                  System.out.println("end=  "+System.currentTimeMillis());
            } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }
      }
      public static void main(String []args) {
            MyThread h=new MyThread();
            h.start();
      }
}

结果:
3.yield()方法
    属于一种启发式的方法,它会提醒调度器我愿意放弃当前的cpu资源,如果cpu资源不紧张,则会忽略这个提醒。放弃的时间不确定,有可能刚刚放弃,马上又获得了时间片。
    调用yield()会让线程从running切换为runnable状态,
public class MyThread extends Thread{
      public void run() {
            long beantime=System.currentTimeMillis();
            int count=0;
            for(int i=0;i<=500000;i++) {
                  //Thread.yield();
                  count=count+(i+1);
            }
            long endtime=System.currentTimeMillis();
            System.out.println("花费时间:"+(endtime-beantime));
      }
      public static void main(String []args) {
            MyThread h=new MyThread();
            h.start();
      }
}

不使用yield()方法,执行时间:3
使用yield()方式,执行时间:238(这个不是固定的,会有波动)因为yield到底有没有效果是随机的。
4.interrupt()方法:常用的方法,
    如下方法的调用会让当前线程进入阻塞状态:
    wait();   sleep();    join()  interruptibleChannel的io操作    Selector的wakeup方法  。。。
上面的方法都会让线程进入阻塞状态,而当另外一个线程调用被阻塞线程的interrupt()方法,则会打断这种阻塞,因此称为: 中断方法,它是中断的阻塞状态,而不是中断运行状态。打断一个线程不等于该线程的声明周期结束了
    一旦线程在阻塞的情况下被打断,则会抛出interruptedException异常,这个异常如同一个signal信号通知线程,被打断了,
public class MyThread extends Thread{
      public void run() {
            try {
                  System.out.println(Thread.currentThread().getName()+"  begin");
                  Thread.sleep(3000);
                  System.out.println(Thread.currentThread().getName()+"  end");
                  return ;
            } catch (InterruptedException e) {
                  System.out.println(Thread.currentThread().getName()+"  被中断了");
            }
            System.out.println("我异常退出了。。");
      }
      public static void main(String []args) throws InterruptedException  {
            MyThread h=new MyThread();
            h.start();
            Thread.sleep(1000);
            h.interrupt();
      }
}

结果: 这里没有执行 end操作。而是从抓到异常后面执行的。
这里让我想起来了慕课的多线程视频教学,里面提到了退出标识符,
    使用中断标识符作为退出标识符的操作:
Thread thread=new Thread(new Runnable() {
    public void run() {
       //如果当前线程被中断则退出循环
       while (!Thread.currentThread().isInterrupted())
           System.out.println(Thread.currentThread()+"Hello");
   }
});

注意点:isInterrupted()是不擦除 标识符的,而interrupted()是要擦除中断标识符的。
5.join()方法
    常用重要的方法。与sleep一样是可中断的方法,并且擦除interrupt表示。api提供了三种join()方法。
    它的功能是什么:join线程a,则b进入等待,等待a完成之后,再执行b。(最常用的就是子线程执行完了之后,main线程再执行)
    
public class MyThread extends Thread{
      public void run() {
            try {
                  System.out.println(System.currentTimeMillis()+"  begin");
                  Thread.sleep(3000);
                  System.out.println(System.currentTimeMillis()+" end");
            } catch (InterruptedException e) {
            }
      }
      public static void main(String []args) throws InterruptedException  {
            MyThread h=new MyThread();
            h.start();
      //    h.join();
            System.out.println(System.currentTimeMillis()+" main end");
      }
}

结果: 有join时:      无join时:
6.线程如何关闭
线程的自动关闭存在两种情况:1.线程任务完成,生命周期正常结束。2.抛出异常退出。
代码处理让线程退出呢?
    1.stop(),它可以停止一个线程,但是不安全,因为是强制退出,无法做一些处理(比如流的关闭等等)
    2.中断标识符退出,在一些循环线程中,比如监听等等功能,如何让线程退出的,它自己是无法退出的,需要一个标识符,当这个标识符显示意思为退出时,那么代码的执行就走退出路线进而退出。
    3.自己设定一个退出标识符,原理是2方法是一样的。
7.线程如何暂停
    暂停与关闭之间的区别就是,暂停之后,还能启动。
    suspend()与resume()
public class MyThread extends Thread{
      private long i=0;
      public long getI() {
            return i;
      }
      public void run() {
            while(true) {
                  i++;
            }
      }
      public static void main(String []args) throws InterruptedException  {
            MyThread h=new MyThread();
            h.start();
            Thread.sleep(2000);//让子线程先跑一段。
            h.suspend();// 暂停子线程,看一下i的值是多少
            System.out.println("暂停: "+System.currentTimeMillis()+" i=  "+h.getI());
            Thread.sleep(2000); //再让子线程跑,看它跑得动吗?
            System.out.println("暂停: "+System.currentTimeMillis()+" i=  "+h.getI());
            //唤醒
            h.resume();
            System.out.println("唤醒: "+System.currentTimeMillis()+" i=  "+h.getI());
            Thread.sleep(2000); //再让子线程跑,看它跑得动吗?
            System.out.println("唤醒: "+System.currentTimeMillis()+" i=  "+h.getI());
            h.stop();
      }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值