龙空技术网

Java线程的概念,各种状态以及使用方式

蜗牛与小羊 147

前言:

现在小伙伴们对“java中线程状态有哪些”大致比较关怀,小伙伴们都需要知道一些“java中线程状态有哪些”的相关知识。那么小编也在网络上搜集了一些有关“java中线程状态有哪些””的相关知识,希望各位老铁们能喜欢,小伙伴们快快来学习一下吧!

进程,线程概念与作用

在谈线程之前,我们先了解一下进程,是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。也就是说,我们个人计算机,手机里面的每一个应用都是一个进程。当我们开启多个应用程序,我们感觉这些程序是“同时”运行的,但实际上,一个处理器同一时刻只能运行一个进程,只是CPU在高速轮换执行让我们有这样的错觉,我们感受不到中断的原因是CPU执行速度相对于我们的感觉实在是太快了。操作系统大多使用抢占式多任务操作策略,以支持多进程的并发性线程。

关于线程,一个进程为了能像一个处理器一样并发处理多项任务,引入了线程,线程就是进程中并发执行的基本单位,线程也因此被称为“轻量级进程”。一个进程可以包含多个线程,每条线程都有其父进程。

多线程是有多条顺序”同时”执行的执行流,且它们之间互不干扰。说是同时执行,其实是看哪个线程能竞争到CPU资源,就先执行。多线程,让我们的应用程序有了并发的概念。举个例子,我们的网站,假如没有多线程,那它不能实现多个用户同时使用。

线程的几种状态

1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

2. 运行(RUNNABLE):已经调用start方法,已经在Java虚拟机中执行,但是在等待操作系统的其他资源。Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

3. 阻塞(BLOCKED):表示线程阻塞于锁。

4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

6. 终止(TERMINATED):表示该线程已经执行完毕。

线程状态切换几个常用的方法

1、Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。

2、Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。

3、thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。

4、obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。

5、obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

6 、LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。

线程的三种实现方式

1、 继承Thread类,通过实现Run方法,然后调用start方法开启线程执行,Thread继承Runnable接口,同时里面有大量的native方法,这正说明了线程的操作是直接与CPU相关,

public class ThreadExample extends Thread {    @Override    public void run() {        System.out.println("执行当前线程");    }    public static void main(String[] args) {        Thread thread = new ThreadExample();        thread.start();    }}

2、 实现Runnable接口,实现Run方法,这种方式与第一种方式不同的是,要通过实现Runnable启动多线也要通过Thread的start开始执行线程。

public class RunnerExample implements Runnable {    @Override    public void run() {        System.out.println("Runnable 线程执行");    }    public static void main(String[] args) {        Runnable runner = new RunnerExample();        new Thread(runner).start();    }}

3、 通过ExcutorService 线程,Callable接口实现有返回结果的线程,通过返回的Future对象获取结果,结果未返回时,当前线程就会阻塞知道结果返回。

public class CallableExample implements Callable {    @Override    public Object call() throws Exception {        System.out.println("执行Callable实现的线程");        return "my Callable";    }    public static void main(String[] args) throws ExecutionException, InterruptedException {        ExecutorService executorService = Executors.newFixedThreadPool(3);        Future<String> future =  executorService.submit(new CallExample());        System.out.println(future.get());    }}
如何合理的使用线程

既然线程是进程并发并行操作的基本单元,那是不是线程越多越好呢,其实不是,每天服务器资源都是有限的。需要结合我们的资源和业务进行一个综合的选择。线程数少,导致任务等待时间长,过多会导致CPU线程上下文切换频繁,减低性能

在开发中我认为多线程的作用有三方面

第一: 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二: 提高响应速度。 当任务到达时, 任务可以不需要等到线程创建就能立即执行。

第三: 提高线程的可管理性。

如何合理的设置线程数,我们可以尝试以下的几个维度

1. 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。(设CPU核数为N)

如果是CPU密集型应用,则线程池大小设置为N+1

如果是IO密集型应用,则线程池大小设置为2N+1

这种计算方式适合服务器只跑一个应用且只要一个线程池的情况下

2. 任务的优先级:高、中、低。

3. 任务的执行时间:长、中、短。

4. 任务的依赖性:是否依赖其他系统资源,如数据库连接等。

很多时候设置一个任务线程数,大多情况下是通过一个估算的方式设置一个数目,然后根据性能指标进行一个整合调试得到一个最优的结果,在调试不妨参考如下公式:

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数

比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

可以得出一个结论:

线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。

标签: #java中线程状态有哪些