Java中如何使用线程(Thread)来实现并发编程?
Java是一种支持多线程编程的编程语言,线程(Thread)是Java中最基本的并发编程单元。线程在Java中被封装在Thread类中,通过创建Thread对象并调用start()方法即可创建一个新线程。通过Java中的线程机制,可以实现一些需要并发运行的任务,提高程序执行效率。
一、线程的创建和启动
Java中有两种方式来创建线程:
1.实现Runnable接口
在Java中,线程的类继承Thread类,而线程所要执行的任务则封装在Runnable接口的实现类中,线程类负责开辟线程和调用任务类中的run方法。实现Runnable接口需要实现其中的run()方法,这里需要注意的是,run()方法必须是public void类型。下面是一个简单的实现Runnable接口的示例:
public class MyRunnable implements Runnable{
@Override
public void run(){
/*线程要执行的任务*/
}
}
创建Thread对象并调用start()方法实现线程的启动:
public static void main(String[] args){
Thread thread = new Thread(new MyRunnable());
thread.start();
}
2.继承Thread类
这种方式直接继承Thread类,实现多线程代码的对象和执行逻辑融为一体。它需要实现run()方法,在run()方法中编写线程执行的代码。
public class MyThread extends Thread{
@Override
public void run(){
/*线程要执行的任务*/
}
}
创建Thread对象并调用start()方法实现线程的启动:
public static void main(String[] args){
Thread thread = new MyThread();
thread.start();
}
二、线程的状态
在Java中,线程可以存在不同状态,线程的状态由JVM按照线程运行时的情况自动维护。以下是Java线程状态的分类:
1.新建状态(New)
当一个Thread对象被创建时,就处于新建状态。
2.运行状态(Runnable)
线程启动后,就处于运行状态。此时JVM会调用线程的run()方法。
3.阻塞状态(Blocked)
当线程睡眠(sleep)、等待(wait)或被其他线程阻塞时,就处于阻塞状态。
4.无限期等待状态(Waiting)
当线程执行无限期等待时(wait()),就处于无限期等待状态。
5.计时等待状态(Timed Waiting)
当线程在等待一段时间时,就处于计时等待状态(sleep(), join()等方法)。
6.终止状态(Terminated)
线程在执行完程序后进入终止状态。
三、线程的同步
在并发编程的环境下,多个线程可以同时访问同一个数据,造成读取数据时的不确定性,也会产生“死锁”等问题。为此,Java提供了同步机制来解决这类问题。Java中的同步机制一般使用synchronized关键字来实现线程之间的同步。
synchronized关键字可以有三种用法:
1.修饰方法:
在关键字后面加上方法声明,这样方式是将整个方法都加锁,调用该方法的线程将被阻塞。
public synchronized void method(){}
2.修饰块:
在关键字后面加上作用范围的对象,这样方式是将括号中的资源加锁。
synchronized(Object){/*需要同步的代码块*/}
3.修饰类:
在关键字后面加上class关键字和类,这样方式是将整个类加锁,调用该类的线程将被阻塞。
public class MyThread extends Thread{
/*static synchronized方式*/
public static synchronized void method(){}
}
四、线程的通信
线程在运行过程中,可能需要交互信息或协调运行,这时就需要用到线程之间的通信。Java中线程通信可以使用wait()、notify()、notifyAll()三个方法来实现。
1.wait()方法
该方法使线程进入等待状态,释放对象的锁。
2.notify()方法
该方法使等待中的一个线程从等待中恢复,并重新获取对象锁。
3.notifyAll()方法
该方法使等待中的所有线程从等待中恢复,并重新获取对象锁。
注意:在使用线程之间通信时,一定要使用synchronized关键字确保线程安全。
五、线程池的使用
线程池允许我们对线程的使用进行更好的管理。常见的线程池实现类有:FixedThreadPool,CachedThreadPool,SingleThreadPool。
1.创建FixedThreadPool
当使用FixedThreadPool时,我们需要指定每个线程需要处理的任务,这就是通过实现Runnable接口来实现的。假设我们需要处理100个任务,那么创建一个有10个线程的线程池:
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i=0;i<100;i++){
executorService.execute(new TaskObject());
}
executorService.shutdown();
我们使用ExecutorService.execute()方法将Runnable实例添加到线程池中,这样这些任务将会被线程池中的线程执行。
shutdown()方法可以使线程池中的任务执行完后关闭线程池。
2.创建CachedThreadPool
CachedThreadPool即可根据需要创建线程的线程池,线程数量无限制,如果不需要处理任务,它会自动清除线程。下面是CachedThreadPool的示例:
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<100;i++){
executorService.execute(new TaskObject());
}
executorService.shutdown();
CachedThreadPool对临时任务做了很好的控制,不会浪费CPU时间。
3.创建SingleThreadPool
SingleThreadPool是只有一个线程的线程池,它可以保证任务的按顺序执行,这是非常有用的。下面是创建SingleThreadPool的示例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i=0;i<100;i++){
executorService.execute(new TaskObject());
}
executorService.shutdown();
运行结果一定是按顺序执行。可以看出SingleThreadPool非常适合需要顺序执行任务的场景。
六、总结
Java中的线程(Thread)是Java中最基本的并发编程单元,通过线程机制可以实现一些需要并发运行的任务,提高程序执行效率。在Java中,线程有多种创建方式,线程状态有多种分类,线程之间的同步和通信是多线程程序中必须要掌握的技能点。线程池是多线程编程中比较实用的工具,可以方便地管理线程和任务。在多线程编程中,需要注意线程的安全和资源的同步,避免出现竞争的问题。
