黑马程序员-Java基础总结07——多线程

时间:2023-02-20 13:15:39

多线程

-------android培训java培训、期待与您交流!----------

内容:    线程(Thread与Runnable)、同步(synchronized)与锁、

           新版锁机制Lock 与 ReentrantLock、守护线程、线程不同状态(运行、睡眠、终止)

1、进程:是一个正在执行中的程序。

每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

 

线程:就是进程中的一个独立的控制单元;线程在控制着进程的执行。

【比喻:  进程为一个修公路工程,线程为施工队,默认一条施工队修完整个工程,

多线程就是“伪”多个施工队施工公路不同段线路(多核处理才是真有多施工队)】

 

PS: Java的虚拟机(jvm)执行时除执行main的主线程,还有执行Java垃圾回收机制的线程

 

2、线程的创建方式

第一种:继承Thread

1、定义类继承Thread复写Thread类中的run方法;

    (复写目的:将自定义代码存储在run方法,让线程运行此代码。)

2建立子类对象的同时,线程也被创建(但还未启动);

3调用线程的start方法开启线程

   该方法两个作用:启动线程,调用run方法。

格式:class PrimeThreadextends Thread  {           

public void run( ) {    线程要运行的代码;   }   }

 

创建并启动线程:PrimeThread p = new PrimeThread();

                  p.start();

 第二种:实现Runnable接口

  1. 定义类实现Runnable接口复写(覆盖)接口中的public void run( )方法        (同理:将线程要运行的代码存放在该run方法中。)
  2. 通过Thread建立线程对象,并将实现Runnable接口子类对象作为实际参数传递给Thread类的构造函数
  3. Thread类对象调用start方法开启线程并调用了Runnable接口子类的run方法。

 

格式:class PrimeThreadimplements Runnable {

          public void run( ) {   线程要运行的代码;   }        }

 

创建并启动线程:  PrimeThread p = new PrimeThread();

                 newThread(p).start();

     Thread t1 = new Thread(p);    t1.start();   [多个线程共享一个类的数据]

       Thread t2 = new Thread(p);    t2.start();

 

思考:为什么要给Thread类的构造函数传递Runnable的子类对象?

答:     因为自定义的run方法所属的对象是Runnable接口的子类对象,

所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

 

实现方式和继承方式的区别

继承Thread:线程代码存放Thread子类run方法中,子类对象调用start( )方法

实现Runnable:线程代码存在接口的子类run方法,子类对象作为参数传递给的Thread类对象调用start( )方法。

实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。

 

 

3、线程的不同状态:

a、创建  —— 创建继承Thread的子类的对象

b、运行 —— 通过start()方法创建,既有运行资格又有执行权;

c、中断 (运行过程中)

   |——临时状态(阻塞、等待cpu执行) —— 具有运行资格但没有执行权;

|——冻结(sleepwaitnotify—— 没有运行资格(暂时放弃执行资格)

d、消亡(死亡)—— 程序中run方法结束或通过stop()方法强行关闭,在内存中释放;

 

 

每个线程都有自己默认的名称:Thread-编号该编号从0开始。

获取名称:staticThread  currentThread( ) 获取当前线程对象;(通用)

【标准通用方式;而this虽然也可实现该功能,但仅适用于继承Thread的类。】

String  getName() 获取线程名称;

设置线程名称: setName或者创建构造函数时赋值:Thread(Runnable target,String name)。

 

Thread.currentThread()this语句的区别:

Thread.currentThread()   :Thread类中方法,获取当前线程的对象

this                   :仅存在于方法的方法体中,获取当前引用的对象

this的三种情况:调用同类的成员(同功能起因)、存在于构造函数第一行、区分同名变量)

 

结论: 继承Thread获得当前线程对象可以用Thread.currentThread()this

实现Runnable接口要获取当前线程对象只能使用Thread.currentThread()

【此为简单说明及结论,详细分析研究可见 CurrentThread.java 中的模拟与分析】

 

4、线程安全问题:

导致安全问题的出现的原因:

1、多个线程访问出现延迟。

2、线程随机性  

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

 

5、同步(synchronized 同步代码块、同步函数

同步代码块格式:

synchronized(对象){ 

  需要同步的代码;

}

同步可以解决安全问题的根本原因就在那个对象上;该对象如同的功能。

 

如何明确需要同步的代码?

1、明确哪些代码是多线程运行代码;  

2、明确共享数据;

3、明确多线程运行代码中哪些语句时操纵共享数据的。(需要同步的代码)

 

同步的前提:

1、同步需要两个或者两个以上的线程。

2、多个线程使用的是同一个锁。

只有同时满足这两个条件,才能称其为同步,缺一不可。

同步的弊端:

当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。       (根据情况可用双重判断(其他)减少对锁的判断。)

 

同步函数格式:    函数上加上synchronized修饰符即可(同步函数使用的锁是this)。

 

同步的应用之单例设计模式:懒汉式(双重判断和同步)[延时加载]

             懒汉式使用的同步锁为该类所属的字节码文件对象.class

注意与之相对的是饿汉式,在实际开发中使用较多,可见之前的“4、面向对象”。】

 

同步中的锁(对象):        

   1Object类的对象(需要创建对象,仅适用于同一类将该对象唯一化  

2this (代表的对象):适用于同步函数;

3、字节码文件对象(类名. class,适用于多数,因为字节码文件class唯一。

 [同个包中不能有同名class,调用不同包时则需要添加包名标识全名称]

   使用锁为字节码文件对象的有懒汉式单例设计模式静态同步函数等】

 

同步中的线程等待、唤醒机制:(属于Object类)

(同步对象) .wait( );        : 线程进入等待状态;

           .notify( );     唤醒单个同类对象的线程;

       .notifyAll( ); 唤醒同个同步对象的全部线程。

 

注意:不要同时有两个线程以上在使用同步嵌套同步,因为每个同步对应不同的锁,当持有一个锁而无法获得另一个被其他线程持有锁时,两个线程都处于阻塞状态,从而造成死锁

             (为解决该问题,JDK1.5提供新的机制方法,后面会写到。)

 

思考1wait( )notify( )notifyAll( )用来操作线程为什么定义在了Object类中?

      1,因为这些方法只能存在于同步中;

      2,使用该方法时必须要标识所属的同步的锁

      3,而锁可以是任意对象(要求唯一),所以任意对象调用的方法一定定义Object中。

思考2wait( )sleep( )有什么区别?

      wait( );      :释放cpu执行权,释放锁。(需要被notifynotifyAll方法唤醒)

      sleep( );       :释放cpu执行权,不释放锁。(该方法要指定时间,时间结束则自动唤醒)

 

JDK1.5多线程升级解决方法机制之新版锁机制:

             (属于:java.util.concurrent.locks.*;

将同步Synchronized替换成现实Lock操作。

Object中的waitnotify notifyAll,替换了Condition对象。

该对象可以Lock进行获取;可实现了本方只唤醒对方操作。

 

Lock:   替代了Synchronized(同步)

创建: Lock alock = newReentrantLock( );       

         ReentrantLock alock = newReentrantLock( );

(创建互斥锁,还有读写锁ReentrantReadWriteLock

             alock . lock();                获取锁;

            alock . unlock();            释放锁;(此步骤为必须执行,要求定义在finally代码块

      (锁名. Lock类方法,使用于需要同步的代码开头之前和结尾之后)

 

Condition:替代了Object类中的waitnotifynotifyAll方法;

             [特殊之处,可一个Lock对应多个Condition]

创建: Condition con = lock . newCondition();         [Lock同时使用,lockLock锁名]

      . await();            :指定锁的线程进入等待状态;

      . signal();            :同个锁单个线程唤醒

      . signalAll(); :同个锁所有线程唤醒

 

 

6、停止线程:

  1. 定义循环结束标记:(避免出现死循环)                                    因为线程运行代码一般都是循环,只要控制住循环即可。
  2. 使用interrupt(中断)方法。

作用:结束线程的冻结状态,使线程回到运行状态中来。

格式: 线程名. interrupt(); :中断指定线程冻结状态,恢复到正常状态(执行资格)

 

适用:特殊情况,当线程处于冻结状态时,其读取不到标记,那么线程就无法结束。

注:stop方法已经过时不再使用,因其容易造成bug,存在不安全性。

 

线程类的其他方法:

1、线程名.setPriority(int num) :     更改线程的优先级;

例: 线程名.setPriority(Thread.MAX_PRIORITY); 将该线程优先级更改为*10级;

(static int)  Min_PRIORITY  :线程可以具有的最低优先级。(该名称为常量类型)

    NORM_PRIOITY  :分配给线程的默认优先级。

 

2、守护线程:        (必须在线程开启start前调用)

线程名. setDaemon(boolean b);  是否将该线程标记为守护线程;

(默认为false,即用户线程,主线程main即为用户线程。)

注意:当用户(前台)线程全部结束,剩下正在运行的均为守护线程,则程序终止,JVM结束。

(也可看做后台线程,前台线程完成结束,后台线程也随之结束,程序结束)

 

3、线程名.join();           等待该线程停止;

   B.join(); :当正在运行的A线程执行到B线程join()方法时,A线程就会进入等待状态,B线程加入到Cpu执行权争夺,直到B线程执行结束A线程才恢复执行资格

应用: 可用来临时加入线程执行

抛出:throwInterruptedException 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态被清除。

(即B线程等待状态被中断trythrows此异常后,B线程将结束中断状态

 

4、修改线程状态:

线程类(Thread) . yield(); :暂停当前正在执行的线程对象,并执行其他线程。

 

5、线程对象.toString();  : 返回线程信息(线程名称、线程优先级和线程组

  (可在类ThreadGroup   线程组调用方法修改线程组名或其他相关信息)

 

其他提示:

匿名内部类创建线程的格式:

1、继承Thread类:newThread( ){     run(){   运行代码;  }    }.start();

2、实现Runnable类:

Runnable r = newRunnable( ) {  public void run(){  }    };  

newThread(r).start( );                 

【因为Runnable是接口,且仅有run方法,所以不能直接调用start方法。】