深入理解Java中的线程

  • Post author:
  • Post category:java



核心内容:

1、Java中线程的概念以及注意事项

2、Java中创建线程的两种方式

3、Java中创建线程两种方式的比较

4、线程同步的问题、线程同步问题的由来、如何解决线程同步的问题


1、Java中线程的概念以及注意事项

线程的概念:

①线程是一个程序里的不同执行路径;

②以前所编写的程序,每个程序都只有一个入口、一个出口以及一个顺序执行的序列,在程序执行过程中的任何指定时刻,都只有一个单独的执行点;

③事实上,在单个程序的内部是可以在同一时刻进行多种运算的,这就是所谓的多线程。

线程的注意事项:

①aa.start()具有两层含义:向下开辟(创建)一个新的线程,并执行本线程所对应的run方法;

②执行一个线程实际上就是执行本线程所对应的run方法中的代码;

③执行完aa.start()之后并不表示aa对象所对应的线程立即得到执行,aa.start()执行完之后只是表明aa对象所对应的线程具有了可以被cpu执行的资格,但由于想抢占cpu执行的线程很多,cpu并不一定会立即去执行aa对象所对应的线程,此时aa对象对应的线程将处于阻塞状态。一个线程对应三种不同的状态:阻塞状态、就绪状态、运行状态

④一个Thread对象能且只能代表一个线程


2、Java中创建线程的两种方式

Java中创建线程有两种方式:

①继承Thread类,并重写run方法

②实现Runnable接口,并实现run方法


实例程序1:继承Thread类,并重写run方法的方式创建线程

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      A aa = new A();
      aa.start(); //aa.start具有两层含义:向下开辟(创建)一个新的线程,并执行本线程所对应的run方法
      while(true)
      {
          System.out.println("Hadoop");
      }  
   }    
}
class A  extends Thread
{
   @Override
   public void run()
   {
      while(true)
      {
          System.out.println("Spark");
      }
   }
}

运行结果:hadoop与spark交替的输出

Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop

实例程序2:实现Runnable接口,并实现其中的run方法的方式创建线程

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      Thread aa = new Thread(new A());
      aa.start(); //aa.start具有两层含义:向下开辟(创建)一个新的线程,并执行本线程所对应的run方法
      while(true)
      {
          System.out.println("Hadoop");
      }  
   }    
}
class A  implements Runnable
{
   public void run()  
   {
      while(true)
      {
          System.out.println("Spark");
      }
   }
}

运行结果:hadoop与spark交替的输出

Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Spark
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop
Hadoop

从上面创建线程的两种方式可以看出,无论通过哪种方式创建线程,必须调用Thread类中的start方法才能开辟一个新的线程。

3、Java中创建线程两种方式的比较

为什么Java要提供两种方法来创建线程呢?它们都有哪些区别?相比而言,哪一种方法更好呢?在给出具体结论之前,我们先用Java中的线程编程实现生活中的几个具体场景:

场景1:假设一个影院有三个售票口,分别用于向儿童、成人和老人售票。影院为每个窗口放有100张电影票,分别是儿童票、成人票和老人票。三个售票口各自售票,互不影响。


①、通过继承Thread类的方式来完成这个程序

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      A t1 = new A();
      A t2 = new A();
      A t3 = new A();
      t1.setName("儿童窗口");
      t2.setName("成人窗口");
      t3.setName("老人窗口");
      //同时开辟三个子线程
      t1.start();
      t2.start();
      t3.start();
   }    
}
class A extends Thread
{
   public int tickets = 100;
   @Override 
   public void run()
   {
      while(true)
      {
          if(tickets > 0)
          {
              System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
              tickets--;
          }
      }
   }
}

②、通过实现Runnable接口的方式来完成这个程序

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      Thread t1 = new Thread(new A());
      Thread t2 = new Thread(new A());
      Thread t3 = new Thread(new A());
      t1.setName("儿童窗口");
      t2.setName("成人窗口");
      t3.setName("老人窗口");
      //同时开辟三个子线程
      t1.start();
      t2.start();
      t3.start();
   }    
}
class A  implements Runnable
{
   public int tickets = 100;
   public void run()
   {
      while(true)
      {
          if(tickets > 0)
          {
              System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
              tickets--;
          }
      }
   }
}

运行结果(部分截取):

老人窗口正在售第:53张票
成人窗口正在售第:12张票
老人窗口正在售第:52张票
成人窗口正在售第:11张票
老人窗口正在售第:51张票
成人窗口正在售第:10张票
老人窗口正在售第:50张票
成人窗口正在售第:9张票
老人窗口正在售第:49张票
成人窗口正在售第:8张票
老人窗口正在售第:48张票
成人窗口正在售第:7张票
老人窗口正在售第:47张票
成人窗口正在售第:6张票
老人窗口正在售第:46张票
成人窗口正在售第:5张票
老人窗口正在售第:45张票
成人窗口正在售第:4张票
老人窗口正在售第:44张票
成人窗口正在售第:3张票

在上面的这个实例场景中, 不同的售票口相当于不同的线程,不同种类的票相当于不同的资源。


可以总结出:若多个线程处理的是不同的资源,两种创建线程的方式均可。

场景2:有3个售票口,共同售出电影院的1000张票,3个售票口相当于3个线程,处理的是相同的资源(1000张电影票)

①、通过继承Thread类的方式来完成这个程序

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      A t1 = new A();
      A t2 = new A();
      A t3 = new A();

      t1.setName("窗口1");
      t1.start();
      t2.setName("窗口2");
      t2.start();
      t3.setName("窗口3");
      t3.start();

   }    
}
class A  extends Thread
{
   public static  int tickets = 1000; //静态的属性和方法属于类本身的,由操作系统只分配一块内存空间
   public static  String str = "Java";
   public void run()
   {
      while(true)
      {
          synchronized(str) //创建同步代码块
          {
              if(tickets > 0)
              {
                  System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
                  tickets--;
              }
          }
      }
   }
}

运行结果:(部分截图)

窗口1正在售第:202张票
窗口1正在售第:201张票
窗口1正在售第:200张票
窗口1正在售第:199张票
窗口2正在售第:198张票
窗口2正在售第:197张票
窗口2正在售第:196张票
窗口2正在售第:195张票
窗口2正在售第:194张票
窗口2正在售第:193张票

②、通过实现Runnable接口的方式来完成这个程序

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      A target = new A(); //三个售票口处理的是相同的资源,所以我们new出一个对象
      Thread t1 = new Thread(target);
      Thread t2 = new Thread(target);
      Thread t3 = new Thread(target);

      t1.setName("窗口1");
      t1.start();
      t2.setName("窗口2");
      t2.start();
      t3.setName("窗口3");
      t3.start();

   }    
}
class A  implements Runnable
{
   public int tickets = 1000;  
   public void run()
   {
      while(true)
      {
          synchronized(this) //创建同步代码块
          {
              if(tickets > 0)
              {
                  System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
                  tickets--;
              }
          }
      }
   }
}

运行结果:(部分截图)

窗口2正在售第:966张票
窗口2正在售第:965张票
窗口2正在售第:964张票
窗口2正在售第:963张票
窗口2正在售第:962张票
窗口2正在售第:961张票
窗口1正在售第:960张票
窗口1正在售第:959张票
窗口1正在售第:958张票
窗口1正在售第:957张票
窗口1正在售第:956张票
窗口1正在售第:955张票
窗口1正在售第:954张票


可以总结出:若多个线程处理的是相同的资源,最好通过实现Runnable接口的方式创建线程,不然的话通过继承Thread的方式显得过于繁琐。

4、Java中线程同步的问题,线程同步问题的由来,如何解决线程同步的问题/td>

什么是线程同步?

所谓线程同步就是多个线程在处理相同资源的时候,保证共享数据的数据一致性和变化一致性


线程同步问题的由来?

导致线程同步的原因共有两个:

①多个线程彼此之间处理的是相同的资源(比如3个窗口共同售出1000张票)

②多个线程彼此之间在处理相同关键步骤的时候,在这些关键的步骤没有执行完毕的时候,CPU会切换到另外一个线程去执行这些关键的步骤,导致共享数据的一致性出现问题

实例程序:(线程同步出错)

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      A target = new A(); //三个售票口处理的是相同的资源,所以我们new出一个对象
      Thread t1 = new Thread(target);
      Thread t2 = new Thread(target);
      Thread t3 = new Thread(target);

      t1.setName("窗口1");
      t1.start();
      t2.setName("窗口2");
      t2.start();
      t3.setName("窗口3");
      t3.start();

   }    
}
class A  extends Thread
{
   public int  tickets = 1000; 
   public void run()
   {
      while(true)
      {  
          if(tickets > 0)
          {
              System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
              tickets--;
          }
      }
   }
}

运行结果:(部分截取)

窗口2正在售第:10张票
窗口2正在售第:9张票
窗口2正在售第:8张票
窗口2正在售第:7张票
窗口2正在售第:6张票
窗口2正在售第:5张票
窗口2正在售第:4张票
窗口2正在售第:3张票
窗口2正在售第:2张票
窗口2正在售第:1张票
窗口1正在售第:113张票
窗口3正在售第:111张票

从上面的程序可以看出,3个线程在处理相同资源的时候(1000张票)的时候,共享数据的一致性出现了问题,其原因在于:

 if(tickets > 0)
 {
    System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
    tickets--;
 }

这个代码块并不是一个不可分割的整体。


在Java当中,如何解决线程同步的问题?

在Java当中是通过synchronized语法机制来解决线程同步的问题的,通过synchronized语法机制,保证这些关键的步骤在被某一个线程访问或者执行的时候,其余线程不能在执行这些关键的步骤(尽管CPU仍在多个线程之间来回切换),直到这个线程将这些关键的步骤执行完毕,其余线程才能执行这些关键的步骤;

Java中的synchronized语法机制类似于数据库中的事务性处理机制。

实例程序:通过synchronized语法机制来解决上面的线程同步问题

public class App1
{  
   public static void main(String[] args) throws InterruptedException
   {
      A target = new A(); //三个售票口处理的是相同的资源,所以我们new出一个对象
      Thread t1 = new Thread(target);
      Thread t2 = new Thread(target);
      Thread t3 = new Thread(target);

      t1.setName("窗口1");
      t1.start();
      t2.setName("窗口2");
      t2.start();
      t3.setName("窗口3");
      t3.start();

   }    
}
class A  extends Thread
{
   public int  tickets = 1000; 
   public void run()
   {
      while(true)
      {
          synchronized(this) //创建同步代码块
          {
              if(tickets > 0)
              {
                  System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
                  tickets--;
              }
          }
      }
   }
}

运行结果:(截取部分)

窗口1正在售第:48张票
窗口1正在售第:47张票
窗口1正在售第:46张票
窗口1正在售第:45张票
窗口1正在售第:44张票
窗口1正在售第:43张票
窗口3正在售第:42张票
窗口3正在售第:41张票
窗口3正在售第:40张票
窗口3正在售第:39张票
窗口3正在售第:38张票
窗口3正在售第:37张票
窗口3正在售第:36张票

在上面的程序中,通过Java中的synchronized语法机制,保证了下面的代码块变成了一个不可分割的整体。

  synchronized(this) //创建同步代码块
  {
        if(tickets > 0)
        {
            System.out.println(Thread.currentThread().getName()+"正在售第:"+tickets+"张票");
            tickets--;
        }
  }

对于上面的讲解,如有问题,欢迎留言指正!



版权声明:本文为a2011480169原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。