java学习笔记多线程学习总结(上)

时间:2023-02-24 12:33:59


一、进程的简述

     

1、进程:

是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。Java中主线程要运行的代码在main中存储,自定义线程存在run中。

2、线程:

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

Java  jvm启动时会有一个进程java.exe。该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中该线程称之为主线程。

3、扩展:

其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回事机制的线程。

4、多线程存在的意义:

使程序的部分产生同时运行效果,提高效率。


二、创建进程

1、如何在自定义的代码中,自定义一个线程呢?

   继承Thread类,重写run()方法。创建步骤:

  1、定义类继承Thread

  2、复写Thread类中的run方法(目的是:将自定义代码存储在run方法,让线程运行)

  3、调用线程的start方法,该方法有2个作用:启动线程、调用run方法。

  eg:class Demo extends Thread      // 定义一个线程类

     {

        public void run()    // 重写run方法

        {

           for(int x=0;x<60;x++)    // 循环的目的是让线程多运行一会。

               System.out.println(“demo run--”+x);

        }

     }

 class ThreadDemo

  {

    public static void main(String[] args)

     {

        Demo d = new Demo();    // 建立好一个对象其实就是实例创建一个线程

        d.start();          // 线程启动

        for(int x=0;x<60;x++)

           System.out.println(“Hello world!..”+x);    // 主线程

     }

  }

2、实现Runnable接口,覆写run方法。详细步骤:

      1、定义类实现Runable接口。

      2、覆盖Runable接口中的run方法。将线程要运行的代码放在该run方法中。

      3、通过Thread类建立线程对象。

      4、将Runable接口的子类对象作为实际参数传递给Thread类的构造函数(为什么要将Runable 接口的子类对象传            递给Thread的构造函数)因为,自定义的run方法所属的对象是Runable接口的子类对象,所以要让线程去指              定指定对象的run方法。就必须明确该run方法所属对象。

      5、调用Thread类的start方法,开启线程,并调用Runable接口子类的run方法。

eg:classTicket implements Runnable  // (extendsThread)   // 定义类实现Runnable接口用于创建线程

{

  private void run()     // 实现run方法

   {

      while(true)

      {

          if(ticket>0)        // 票数大于0时出票

          { System.out.println(Thread.currentThread().getName()+”…sale:”+tick--);  }

      }

   }

}

class TicketDemo

{

  public static void main(String[] args)

   {

     Ticket t = new Ticket();     

     Thread t1 = new Thread(t);      // 创建两个个线程,参数是实现接口的类对象。

     Thread t2 = new Thread(t2);

     t1.start();         //  启动线程

     t2.start();

   }

}

3、实现方式和继承方式的区别?

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

      2、继承Thread:线程代码存放Thread子类run方法中,实现Runable,线程代码存在接口的子类的run方法。

注:发现程序运行结果每一次都不同,以newi多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)cpu做着快速的切换,以达到看上去是同时运行的效果。我们可以形象地把多线程的运行认为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性,谁抢到谁执行。至于执行多长时间,Cpu说的算。



三、线程的四种常见状态:

1、New(新创建)

2、Runnable(可运行)

3、Blocked(被阻塞)

4、Waiting(等待)

5、Timed waiting(计时等待)

6、Terminated(被终止)


四、线程的方法介绍

1、staticThread currentThread();   返回当前正在执行的线程对象。静态方法

2、getName();  获取线程名称。

3、设置线程名称:setName或者构造函数。调用父类构造super(name);


五、线程的同步

多线程当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式:同步代码块。

同步代码块的格式:Synchronized(对象){   需要被同步的代码   }

说明:那些语句要操作共享数据,那些语句就需要同步。

eg:class Ticket implements Runnable

{

  private int tick = 100;

  Object obj = new Object();

  public void run()

   {

     while(true)

     {  

          Synchronized(obj)  

         {

             if(tick>0)

             {

                  try{Thread.sleep(10);}    // 停止10秒看效果

                  catch(Exception e)

                  {    }

                 System.out.println(Thread.currentThread().getName()+”..sale:”+tick--);

             }

         }

     }

   }

}

同步代码块的对象参数相当于锁,持有锁的线程可以在同步中执行,没有持有锁的线程即便获取cpu的执行权,也进不去,因为没有获取锁。

同步的前提:

      1、必须要有2个或2个以上的线程。

      2、多个(必须)多个线程使用同一个锁(必须保证同步中只能有一个线程在运行)

同步代码块的好处:解决了多线程的安全问题。

同步代码块的弊端:多个线程需要判断锁,较为消耗资源。

如何找出同步问题:

  1、明确哪些代码是多线程运行代码(run中代码及run中调用代码)

  2、明确共享数据。

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

同步有2种表现形式:同步代码块和同步函数。

同步函数封装的定要是需要同步的语句(例如while循环不能放)

同步代码块封装代码,唯一的区别是他带着同步性,若让函数具备同步性,就可以。


六、同步锁

同步函数用的是哪一个锁?

函数需要被对象调用,那么函数都有一个所属对象引用就是this,所以同步函数的使用锁是this。证明实例如下:

class Ticket implements Runnable

   {

     private int tick = 1000;

      Booleanflag = true;

     public void run()

     {

        if(flag)

        {

           while(true)

           {

               Synchronized(this)

              {

                  if(tick>0)

                  {

                     try{   Thread.sleep(10);   }catch(Exception e){   }

                  }

                  else

                     while(true)

                        (this.)show();

              }

             

              System.out.println(Thread.currentThread().getName()+”…sale:”+tick--);

           }

        }

     }

   }

  class ThisLockDemo

   {

     public static void main(String[] args)

     {  

         Ticket t = new Ticket();  

         Thread t1 = new Thread(t);

         Thread t2 = new Thread(t);

         t1.start();             //  一线程开始用同步代码块

         t.flag = false;             //  二线程开始用同步函数

         t2.start();

     }

   }

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证发现不是this,因为静态方法中也不可以定义this。

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。就是类名.class。该对象的类型是class(可以通过getClass()方法获取)。静态的同步方法使用的锁是该方法所在类的字节码文件对象,类名.class。且此对象在内存中是唯一的。


七、死锁

死锁,同步嵌套同步。在编写程序过程中要尽量避免死索。

创建一个死锁程序的例子:

class Test implements Runnable

{

  private Boolean flag;

  Test(Boolean flag)

  {   this.flag = flag;   }

  public void run()

   {

     if(flag)

     {  

         while(true)

         {

             Synchronized(MyLock.locka)

             {

                  System.out.println(“if locka”);

                  Synchronized(MyLock.Lockb)

                  {   System.out.println(“if lockb”);   }

             }

         }

     }

     else

     {

         Synchronized(MyLock.lockb)

         {

             System.out.println(“else lockb”);

             Synchronized(MyLock.locka)

             {

                  System.out.println(“elselocka”);

             }

         }

     }

   }

}

class MyLock

{

  static Object locka = new Object();

  static Object lockb = new Object();

}

class DeadLockTest

{

  public static void main(String[] args)

   {

     Thread t1 = new Thread(new Test(true));    Thread t2 = new Thread(new Test(false));

     t1.start();      t2.start();

   }

}