Java多线程编程

Java提供内置的多线程支持,线程是进程中控制流的最小单位,可并发执行任务。多线程比多进程更节省资源,通过继承Thread类或实现Runnable接口创建线程。线程状态包括新建、就绪、运行、阻塞和死亡。线程同步、通信和死锁是多线程编程的重要概念,合理使用能提高程序效率。
摘要由CSDN通过智能技术生成

Java多线程编程

与其他编程语言相比,Java 给多线程(multithreaded)编程提供了内置的支持。 一条线程(thread)指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

 

多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。在基于线程(thread-based) 的多任务处理环境中,线程是最小的执行单位。

 

这里定义和线程相关的另一个术语 ——进程(process):一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。基于进程(process-based) 的多任务处理中,进程(process)本质上是一个执行的程序。因此,程序是调度程序所分派的最小代码单位。

【进程的特点

独立性:进程是系统中独立存在的实体,拥有自己的独立资源和私有空间。在没有经过进程本身允许的情况下,不能直接访问其他进程。

动态性:进程与程序的区别在于,前者是一个正在系统中活动的指令,而后者仅仅是一个静态的指令集合

并发性:多个进程可以在单个处理器上并发执行,而不受影响。

并发性和并行性的区别:

并行性:在同一时刻,有多条指令在多个处理器上同时执行(多个CPU)

并发性:在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果(单核)。

并发(Concurrent)是一种能并行运行多个程序或并行运行一个程序中多个部分的能力。如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善。现代的PC都有多个CPU或一个CPU中有多个核。是否能合理运用多核的能力将成为一个大规模应用程序的关键。从JAVA1.5(JAVA5)起,在java.util.concurrent中提供了改进的并发库。本文不介绍Java并发编程,对此感兴趣的读者可参考:Java并发编程基础  https://ifeve.com/java-concurrency-cookbook/

 

多线程程序比多进程程序需要更少的管理费用,多线程帮助你写出CPU最大利用率的高效程序,空闲时间保持最低。进程是重量级的任务,需要分配它们自己独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。当Java程序使用多进程任务处理环境时,多进程程序不受Java的控制,而多线程则受Java控制。

java中的线程是什么

操作系统中可以有多个进程(process),一个进程可以有多个线程(process)。线程是一个轻量级的子进程,是最小的处理单元,它使用共享内存区域。

一次只能有一个线程在一个进程中运行。线程调度程序主要使用抢占式或时间切片调度来调度线程。

抢占式调度与时间分片的区别在抢占式调度下,优先级最高的任务一直执行,直到它进入等待或死亡状态或更高优先级的任务出现。 在时间切片下,任务执行预定义的一段时间,然后重新进入就绪任务池。 然后,调度程序根据优先级和其他因素确定接下来应执行的任务。

 

多线程编程有两种方式:

☆使用继承java.lang.Thread类的方法来创建线程类,多条线程之间无法共享线程的实例变量

☆实现java.lang.Runnable接口创建线程类

 

两者的特点

采用实现继承方式的多线程:

编程相对简单;

多条线程之间无法共享线程类实例变量;

继承Thread类之后就不能继承其他的父类;

采用实现接口的方式的多线程:

线程类只是实现了Runnable接口,还可以继承其他类;

在这种方式下,可以多个线程共享同一个对象,非常适合多个相同线程来处理同一份资源的情况;

编程稍稍复杂一点。

 

线程的生命周期:新建(NEW)、就绪(Runnable)、运行(Running)、阻塞(Blockde)、死亡(Dead)。

当程序使用new关键字创建一个线程后,该线程就处于新建状态;当线程对象调用了start()方法之后,该线程就处于就绪状态;如果处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程就处于运行状态;但是它不可能一直处于运行状态,可能会在等待资源时被中断进入阻塞(blocked)状态;线程运行结束后就会进入到死亡(Dead也称为terminate)在此阶段JVM销毁线程对象并且释放JVM分配的资源)状态。

 

wait和sleep的区别

执行wait方法时,当前线程会释放监视器的所有权,即是释当前所拥有的资源,让给其他线程使用。而且在调用wait方法之后需要使用notify或者notifyAll方法、中断或者在等待时间大于时间参数才能唤醒。wait必须在同步快内才能使用,sleep方法的没有这个限制。

sleep方法不会失去任何监视器的所有权,即是sleep的睡眠其实是线程在执行计数,也就是说线程是不会释放任何所拥有的资源。

 

线程进入阻塞状态的情况:

线程调用了sleep方法主动放弃所占有的资源;

线程调用了一个阻塞式IO方法,在该方法返回的时候被阻塞;

线程尝试获取一个同步监听器,但是被其他线程占有;

在等待wait某个通知;

调用线程suspend方法挂起,不推荐使用容易造成死锁。

 

线程进入就绪状态的情况:

调用sleep方法的线程经过了指定时间;

线程调用的阻塞式IO方法已经返回;

线程成功获取试图取得同步监听器;

线程在等待某个通知,其他线程发出一个通知;

处于挂起状态的线程调用了resume恢复方法;

进入阻塞状态的线程在获得执行机会后重新进入就绪状态,而不是运行状态。

 

Thread类的使用

Thread类提供了在线程上创建和执行操作的构造函数和方法。Thread类扩展了Object类并实现了Runnable接口。

 

常用的Thread类构造函数

  • Thread()
  • Thread(String name)
  • Thread(Runnable r)

 

Thread类的常用方法:

  • public void run(): 用于执行线程的操作。
  • public void start(): 开始执行线程,JVM调用线程上的run()方法。
  • public void sleep(long miliseconds): 使当前正在执行的线程休眠(暂时停止执行)达指定的毫秒数。
  • public void join(): 等待线程死亡。
  • public void join(long miliseconds): 按指定的毫秒数等待线程死亡。
  • public int getPriority(): 返回线程的优先级。
  • public int setPriority(int priority): 更改线程的优先级。
  • public String getName(): 返回线程的名称。
  • public void setName(String name): 更改线程的名称。
  • public int getId():返回线程的编号(ID)。
  • public Thread.State getState(): 返回线程的状态。
  • public boolean isAlive(): 测试线程是否处于活动状态。
  • public void yield(): 使当前正在执行的线程对象暂时暂停并允许其他线程执行。
  • public void suspend(): 用于挂起线程(depricated)。
  • public void resume(): 用于恢复挂起的线程(depricated)。
  • public void stop(): 用于停止线程(depricated)。
  • public boolean isDaemon(): 测试该线程是否为守护进程线程。
  • public void setDaemon(boolean b): 将线程标记为守护进程或用户线程。
  • public void interrupt(): 中断线程。
  • public boolean isInterrupted(): 测试线程是否被中断。
  • public static boolean interrupted(): 测试当前线程是否已被中断。

 

Thread类提供了更改和获取线程名称的方法。默认情况下,每个线程都有一个名称,即thread-0,thread-1, ...等。 可以使用setName()方法更改线程的名称。 setName()和getName()方法的语法如下:

public String getName() : 用于返回线程的名称。

public void setName(String name): 用于更改线程的名称。

 

命名线程的示例

class TestMultiNamingA extends Thread {
    public void run() {
        System.out.println("running...");
    }


    public static void main(String args[]) {
        TestMultiNamingA t1 = new TestMultiNamingA();
        System.out.println("Name of t1:" + t1.getName());
  
        t1.start();
 
        t1.setName("AppT");

        System.out.println("After changing name of t1:" + t1.getName());
    }
}

运行之,显示如下:

Name of t1:Thread-0

After changing name of t1:AppT

running...

 

 

例1、通过继承Thread类来创建并启动多线程。

继承类必须重写run()方法,该方法是新线程的入口点。它也必须调用start()方法才能执行。

// 通过继承 Thread 创建线程

class NewThread extends Thread {

   NewThread() {

      // 创建第二个新线程

      super("Demo Thread");

      System.out.println("Child thread: " + this);

      start(); // 开始线程

   }

 

   // 第二个线程入口

   public void run() {

      try {

         for(int i = 5; i > 0; i--) {

            System.out.println("Child Thread: " + i);

             // 让线程休眠一会

            Thread.sleep(50);

         }

      } catch (InterruptedException e) {

         System.out.println("Child interrupted.");

      }

      System.out.println("Exiting child thread.");

   }

}

 

public class ExtendThread {

   public static void main(String args[]) {

      new NewThread(); // 创建一个新线程

      try {

         for(int i = 5; i > 0; i--) {

            System.out.println("Main Thread: " + i);

            Thread.sleep(100);

         }

      } catch (InterruptedException e) {

         System.out.println("Main thread interrupted.");

      }

      System.out.println("Main thread exiting.");

   }

}

运行之,显示如下:

Child thread: Thread[Demo Thread,5,main]

Main Thread: 5

Child Thread: 5

Child Thread: 4

Main Thread: 4

Child Thread: 3

Child Thread: 2

Main Thread: 3

Child Thread: 1

Exiting child thread.

Main Thread: 2

Main Thread: 1

Main thread exiting.

 

 

Runnable接口的使用

通过实现Runnable接口来创建线程

创建一个线程,最简单的方法是创建一个实现Runnable接口的类。Runnable接口只有一个run()方法,用于执行线程的操作。

为了实现Runnable,一个类只需要执行一个方法调用run(),声明如下:

publicvoid run()

你可以重写该方法,重要的是理解的run()可以调用其他方法,使用其他类,并声明变量,就像主线程一样。

在创建一个实现Runnable接口的类之后,你可以在类中实例化一个线程对象。

Thread定义了几个构造方法,下面的这个是我们经常使用的:

Thread(Runnable threadOb,String threadName);

这里,threadOb 是一个实现Runnable 接口的类的实例,并且 threadName指定新线程的名字。

新线程创建之后,你调用它的start()方法它才会运行。

void start();

 

例2、通过实现Runnable接口来创建线程

// 创建一个新的线程

class NewThread implements Runnable {

   Thread t;

   NewThread() {

      // 创建第二个新线程

      t = new Thread(this, "Demo Thread");

      System.out.println("Child thread: " + t);

      t.start(); // 开始线程

   }

 

   // 第二个线程入口

   public void run() {

      try {

         for(int i = 5; i > 0; i--) {

            System.out.println("Child Thread: " + i);

            // 暂停线程

            Thread.sleep(50);

         }

     } catch (InterruptedException e) {

         System.out.println("Child interrupted.");

     }

     System.out.println("Exiting child thread.");

   }

}

 

public class RunnableThreadDemo {

   public static void main(String args[]) {

      new NewThread(); // 创建一个新线程

      try {

         for(int i = 5; i > 0; i--) {

           System.out.println("Main Thread: " + i);

           Thread.sleep(100);

         }

      } catch (InterruptedException e) {

         System.out.println("Main thread interrupted.");

      }

      System.out.println("Main thread exiting.");

   }

}

运行之,显示如下:

Child thread: Thread[Demo Thread,5,main]

Main Thread: 5

Child Thread: 5

Child Thread: 4

Child Thread: 3

Main Thread: 4

Child Thread: 2

Main Thread: 3

Child Thread: 1

Exiting child thread.

Main Thread: 2

Main Thread: 1

Main thread exiting.

 

 

在多线程编程时,还需要了解以下几个概念:

线程同步

线程间通信

线程死锁

线程控制:挂起、停止和恢复

通过对多线程的使用,可以编写出非常高效的程序。不过请注意,线程的上下文的切换开销,如果你创建了太多的线程,CPU花费在上下文的切换的时间将多于执行程序的时间!

关于这些就不深入介绍了,可以上网搜索有关资料。

 

 

附录

Java并发性和多线程介绍目录  https://ifeve.com/java-concurrency-thread-directory/

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值