多线程的基本知识

1.了解什么是线程(what)

   要知道线程,首先要知道什么是进程?

   进程:正在执行的(进行的)程序,需要操作系统为其分配独立的内存地址空间。

   线程:为了提高效率(目的),虚拟的把进程资源分配给线程,让在小的时间段内产生多个代码片段在运行.这些线程可以

           共享同一块内存和系统资源等.特点:独立运行,但不占用系统资源.

 

 2.如何创建线程(How)

    1.继承类java.lang.Thread

    2.实现接口java.lang.Runnable

 

    Thread类代表线程类,它的两个最主要的方法是:
    run()——包含线程运行时所执行的代码 
    start()——用于启动线程,运行run()中的代码片段

 

   常见开发格式:

   class ThreadTest extends Thread{

         public void run(){

               while(true){

                    //代码片段

                  ....

                   //退出条件

                   if(条件){

                      break;

              }

          }             

      } 

   }

 

 3.线程的状态

    新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存
    就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于

                   可运行池 中,等待获得CPU的使用权
    运行状态:处于这个状态的线程占用CPU,执行程序的代码
    阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。
    阻塞状态分为三种情况:
    1、 位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象

          的等待池中
    2、 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程

          占用,JVM就会把这个线程放到这个对象的琐池中。
    3、 其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法,或者发出了I/O请求时,就会进入这个

          状态中。

    死亡状态:当线程退出了run()方法,就进入了死亡状态,该线程结束了生命周期。 或者正常退出 或者遇到异常退出
                   Thread类的isAlive()方法判断一个线程是否活着,当线程处于死亡状态或者新建状态时,该方法返回false,在其余

                   的状态下,该方法返回true.

 4.线程的调度

    线程调度模型:分时调度模型和抢占式调度模型,JVM采用抢占式调度模型。
   所谓的多线程的并发运行,其实是指宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。
  (线程的调度不是跨平台,它不仅取决于java虚拟机,它还依赖于操作系统) 

 

   如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一
   1、 调整各个线程的优先级

         Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。
         如果希望程序能够移值到各个操作系统中,应该确保在设置线程的优先级时,只使用

           MAX_PRIORITY、

           NORM_PRIORITY、  

           MIN_PRIORITY

          这3个优先级。

   2、 让处于运行状态的线程调用Thread.sleep()方法

          当线程在运行中执行了sleep()方法时,它就会放弃CPU,转到阻塞状态。


   3、 让处于运行状态的线程调用Thread.yield()方法

          当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其它线程处于就绪状态,

          那么yield()方法将把当前运行的线程放到运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,

          则yield()方法什么也不做。


   4、 让处于运行状态的线程调用另一个线程的join()方法 

           当前运行的线程可以调用另一个线程的 join()方法,当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才恢复运行。

 

  

    补充:sleep()和yield()

    sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程,

     两者的区别在于:
         1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;

              yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。

         2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;

              当线程执行了yield()方法后,将转到就绪状态。 
         3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常 
         4、sleep()方法比yield()方法具有更好的移植性

 

 5.线程的同步

    什么是线程的同步?了解为什么会出现线程的同步?

    多个线程在一段代码之间,可能出现异常情况,如:

    public void run(){

        if(i<10){

           System.out.println(i++);

       }

    }

    当i=9;如果线程1执行完判断条件后切换到线程2,线程2判断条件后执行后续的代码后i=10,这个时候线程切换到线程1,因为线程1已经

    判断过,所以执行后续代码这样就会出现异常,回打印出10.

    所以要保证上述代码的原子性。

    1.原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,

                   一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。

   

    2.解决办法:java为了解决这个问题,引进同步机制

                      具体的做法是在代表原子操作的程序代码前加上synchronized(Object)标记,这样的代码被称为同步代码块。

                      同类,代码块可以进行同步控制,这样把这个代码块放在一个方法中,就是同步方法了.

      引入一个新的概念:同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。

  

    3.当一个线程试图访问带有synchronized(Object)标记的代码块时,必须获得 Object的对象的锁,

       在以下的两种情况下,本线程有着不同的命运。
       1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。

             锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,

             并且转到就绪状态。
       2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。
             (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 如在执行同步代码块时,

             遇到异常而导致线程终止,锁会被释放,还有就是在执行完代码后,也会释放锁;在执行代码块时,

             执行了锁所属对象的wait()方法,这个线程会释放对象锁, 进入对象的等待池中)

   

    4.线程同步的特征:
       1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的

              同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一

              个对象的同步代码块时,因为要获得对象的同步锁而互相牵制) 
        2、 每个对象都有唯一的同步锁
        3、 在静态方法前面可以使用synchronized修饰符。 
        4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行

              Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
        5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,

               除非用synchronized修饰

      
     5.释放对象的锁:
       1、 执行完同步代码块就会释放对象的锁
       2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
       3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。 

 

 6.死锁

    什么叫死锁?为什么会发生死锁?
    当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。

    JVM不监测也不试图避免这种情   况,因此保证不发生死锁就成了程序员的责任。 

 

    如何避免死锁
    一个通用的经验法则是:当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。 

 7.线程通信 
    Java.lang.Object类中提供了两个用于线程通信的方法 

    1、 wait():执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒 

    2、 notify():执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,

          把它转到对象的锁池中。 



  

  

        

 



 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值