黑马程序员——java基础--多线程

时间:2021-03-06 00:43:22
------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


1、多线程概述

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个控制单元。

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

一个进程中至少有一个线程。
2、如何在自定义的代码中,自定义一个线程呢?

Java中提供了对线程这类事物的描述,就是Thread类。
   1)创建线程的第一种方式:继承Thread类。

 A、定义类继承Thread。

 B、覆写Thread类中的run方法。

    目的:将自定义代码存储在run方法中,让线程运行。

 C、调用线程的start方法。

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

多线程有一个特性:随机性。多线程都获取cpu执行权,但是在某一刻,只能有一个程序运行,在快速切换,互相抢夺cpu执行权。
  2)为什么要覆盖run方法?

Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,也就是说Thread类中的run方法用于存储线程要执行的代码。
  3)创建线程的第二种方式:实现Runnable接口

开发时用这种方法多。

步骤:

A、定义类实现Runnable接口;

B、覆盖Runnable接口中的run方法;

C、通过Thread类建立线程对象;

D、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;

E、调用Thread类中的start方法开启线程并调用Runnable接口子类中的run方法;
  4)实现方式和继承方式区别:

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

继承Thread:线程代码存放在Thread子类run方法中;

实现Runnable:线程代码存放在接口子类的run方法中。
3、多线程的安全问题

一个线程对多条语句还没执行完,若另一个线程参与进来执行,会导致共享数据的错误。
  1)解决办法:同步代码块

synchronized(对象)

      需要被同步的代码;

    这个对象就如同一把锁。

同步前提:

    A、必须要有两个或者两个以上的线程;

    B、必须是多个线程是哟个同一个“锁”;

好处:解决了多线程安全问题。

弊端:多个线程需要判断,较为消耗资源。
  2)同步函数

A、在函数上加上synchronized修饰即可,函数需要被对象调用,函数都有一个所属对象引用,所以同步函数的锁是this(非静态)。

B、静态同步函数的锁是class对象。因为静态方法中不可以定义this,静态进内存时没有本类对象,但是一定有该方法所在类的字节码文件对象。
  3)如何寻找多线程中的安全问题

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

B、明确共享数据;

C、明确多线程运行代码中哪些语句是操作共享数据的。
  4)死锁

当同步中嵌套同步时,就有可能出现死锁现象。

class Test implements Runnable

{

   private boolean flag ;

   Test(boolean flag)

   {             

       this.flag = flag;

   }

   public void run()

   {

       if(flag)

       {

          synchronized(Lock.a)

           {

              System.out.println("iflocka");

              synchronized(Lock.b)

              {  

                 System.out.println("iflockb");

              }

          }

       }

       else

       {  

          synchronized(Lock.b)

          {

              System.out.println("elselockb");

              synchronized(Lock.a)

              {

                 System.out.println("elselocka");

              }//a锁中有b,b锁中有a。

          }     

       }

   }

}

class Lock

{

   static Object a = newObject();

   static Object b = newObject();//静态,可以通过类调用。如:Lock.b

}

class DeadLockTest

{

   public static voidmain(String[] args)

   {

       Thread t1 = newThread(new Test(true));

       Thread t2 = newThread(new Test(false));//创建两个线程及子类对象作实际参数传递给Thread构造函数。

       t1.start();

       t2.start();

   }

}
4、线程间通信

1)wait()与sleep()区别

    wait():释放资源,释放锁;

    sleep():释放资源,不释放锁。

2)线程间通讯:

   其实就是多个线程在操作同一个资源,但是操作的动作不同。

3)等待唤醒机制

  A、wait();notify();notifyAll();

   都使用在同步中。因为要对持有监视器(锁)的线程操作,所以要使用在同步中,只有同步才具有锁。

  B、为什么这些操作线程的方法要定义在Object类中呢?

   因为这些方法在操作同步中线程时,都必须要标识他们所操作线程中的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。即等待和唤醒必须是同一个锁。不可以对不同锁中的线程进行唤醒,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。

  C、为什么定义notifyAll?

   因为需要唤醒对方线程,只用notify容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
5、JDK升级版(1.5版本以后)

提供了多线程升级解决方案,将同步synchronized替换成现实Lock操作,将Object中的wait,notify,notifyAll替换成condition对象,该对象可以Lock锁进行获取,实现了本方只唤醒对象操作。
6、停止线程

只有一种方法,那就是run方法结束。

开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。处于冻结状态的线程要强制恢复要运行状态,才可以操作标记让其结束,可通过Thread类interruput()方法恢复。
7、守护线程(setDaemon(Boolean on))

将线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,虚拟机退出。记住:该方法爱启动线程前调用。