多线程学习总结(一)

本文详细介绍了多线程的概念,包括进程与线程的定义、区别,多线程编程的优势与风险,以及线程的生命周期状态。此外,还探讨了线程实现的两种方式,即继承Thread类和实现Runnable接口,并强调了线程安全和并发控制的重要性。
摘要由CSDN通过智能技术生成

一、进程与线程

1、进程和线程的定义

进程:进程是进行资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

线程:线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。

 例:进程 ->车间,线程->车间工人

 

2、进程和线程的区别

进程是资源分配的最小单位,线程是程序执行的最小单位。

地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的

线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

 

二、多线程编程的优势和风险

    优势:

        ① 提高系统的吞吐率(Throughput):多线程编程使得一个进程可以有多个线程进行并发的操作,例如当有一个线程因为I/O操作而处于等待状态时,其它线程扔可以执行其它操作

        ② 提高响应性(Responsiveness):在使用多线程编程情况下,对于GUI软件(如桌面应用程序)而言,一个慢的操作(比如从服务器上下载一个大的文件)并不会导致软件的界面出现被“冻住”的现象而无法响应用户其他的操作;对与Web应用程序而言,一个请求慢了并不会影响其他请求处理。

        ③ 充分利用多核处理器资源。如今的多核处理器越来越普及,就算是手机这样的消费类设备也普片使用多核处理器。实施恰当的多线程编程有助于我们充分 利用多核处理器资源,从而避免资源浪费。

        ④ 最小化对系统资源的使用。一个进程中的多个线程可以共享其所在进程所申请的资源(如内存空间),因此使用多个线程相比于使用多个进程进行编程来说,节约了对系统资源的使用。

        ⑤ 简化程序的结构。线程可以简化复杂应用程序的结构。

    风险:

        ① 线程安全问题。多个线程共享数据的时候,如果没有采取相对应的并发访问控制措施,那么就可能产生数据一致性问题,如读取脏数据(过期的数据)、丢失更新(某些线程所做的更新被其他线程所做的更新覆盖过)等。

        ② 线程活性问题。一个线程从其创建到运行结束的整个生命周期会经历若干状态。从单个线程角度来看,RUNNABLE状态是我们所期待的状态。但实际上,代码编写不当可能导致某些线程一直处于等待其他线程释放锁的状态(BLOCKED状态),及产生了死锁(Deadlock)。 例如,线程T1拥有锁L1,并试图去获得锁L2,而此时线程T2拥有锁L2而试图去获得锁L1,这就导致线程T1和T2一直处于等待对方释放锁而一直又得不到锁的状态。当然,一直忙碌的线程也可能会出现问题,他可能面临活锁(Livelock)问题,即一个线程一直在尝试某个操作但就是无法进展,这就好比小猫一直追着自己的尾巴咬却一直咬不到的情形。另外,线程是一种稀缺的计算资源,一个系统拥有的处理器数量相对于该系统中存在的线程数量而言总是少之又少。某些情况下可能出现线程饥饿(Starvation)的问题,即某些线程永远无法获取处理器执行的机会而永远处于RUNNABLE状态的READY子状态。

        ③ 上下文切换(Context Switch)处理器从执行一个线程转向执行另外一个线程的时候操作系统所需要做的一个动作被称为上下文切换。由于处理器资源的稀缺性,因此上下文切换可以被看作多线程编程的必然产物,它增加了系统的消耗,不利于系统的吞吐率。

        ④ 可靠性。多线程编程一方面可以有利于可靠性,例如某个线程意外提前终止了,但这并不影响其他线程继续其处理。另一方面,线程是进程的一个组件,它总是存在特定的进程中,如果这个进程由于某种原因意外提前终止,比如某个Java进程由于内存泄漏导致Java虚拟机崩溃而意外终止,那么该进程中所有的线程也就随之无法继续运行。因此,从提高软件可靠性的角度来看,某些情况下可能要考虑多进程多线程的编程方式,而非简单的单进程多线程方式。

 

三、线程的生命周期状态

 

/**
* Thread state for a thread which has not yet started.
*/
NEW,

NEW(初始化状态):一个已创建(new)的而还未启动(not yet started)的线程就处于这个状态。由于一个线程实例只能够被启动一次,因此一个现在程序只可能有一次处于该状态。

/**

* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system 
* such as processor.
*/

RUNNABLE,

RUNNABLE(可运行状态):该状态可以看成一个复合状态。它包括READY(就绪状态)和RUNNING(运行状态)。READY状态表示当前线程可以被线程调度器(Schedule)进行调度而进入RUNNING状态。RUNNING状态表示该线程正在运行,即相应的线程的run()方法所对应的指令正在由处理器执行。处于RUNNING状态的线程执行Thread.yield()方法(放弃获得CPU时间片)可以由RUNNING状态转换为READY状态。

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,

BLOCKED(阻塞状态):一个线程发起一个阻塞式I/O(Blocking I/O)操作后,或者申请一个由其他线程持有的独占资源(比如锁)时,该线程就会处于BLOCKED状态,该状态的线程不会占用处理器的资源。当阻塞式I/O结束或者该线程获得了其申请的资源,该线程又可以重新由BLOCKED状态转换为RUNNABLE状态。

/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,

WAITING(等待状态):一个线程执行了某些特定的方法之后就会处于等待其他线程执行特定的操作。如果一个线程调用了Object.wait()、Thread.join()或者LockSupport.part(Object)方法就会从RUNNABLE状态转换为WATING状态。如果一个线程调用了Object.notify()、Object.notifyAll()或者LockSupport.unpart(Object)方法就会从WATING状态转换为RUNNABLE状态。

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,

TIMED_WAITING(超时等待):该状态和WAITING状态类似,只是处于该状态的线程不会无限制的等待其他线程执行特定的操作,而是处于带有时间限制的等待状态。当其他线程没有在指定时间内执行该线程所期望的特定操作时,该线程会自动转换成RUNNABLE状态。例如:Object.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()或者 LockSupport.parkUntil()。

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;

TERMINATED(终止状态):已经执行结束的线程或者抛出异常而提前终止的线程都会处于该状态。由于一个线程实例只能够被启动一次,因此一个现在程序只可能有一次处于该状态。

 

四、线程实现的方式

线程有两种常见的实现方式,一种继承java.lang.Thread类,一种实现java.lang.Runnable接口。

(一)继承Thread类的方式:

public class Demo1 extends Thread{
    public Demo1(String name) {
        super(name);
    }
    @Override
    public void run() {
        while(true) {
            System.out.println(this.getName()+"当前线程执行了");            
        }
    }
    public static void main(String[] args) {
        Demo1 demo1 = new Demo1("first_thread");
        Demo1 demo2 = new Demo1("second_thread");
        demo1.start();
        demo2.start();
    }
}

说明:程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用两个对象的start方法,这两个线程也就启动了,这样,整个应用就在多线程下运行。

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

 

(二)实现Runnabale的方式:

public class Demo2 implements Runnable{
    @Override
    public void run() {
        while(true) {
            System.out.println(Thread.currentThread().getName() + "Thread Running");
        }
    }
    public static void main(String[] args) {
                //实例化一个Thread,并传入实例对象
        Demo2 d = new Demo2();
        Thread thread1 = new Thread(d);
        Thread thread2 = new Thread(d);
        thread1.start();
        thread2.start();
        
    }
}

注:实现Runnable接口的方式是以线程任务的形式存在,实现任务与线程的分离。

二者之间的差别:

  1. 从面相编程的角度来看:第一种创建方式(创建Thread类的子类)是一种基于继承的技术,第二种创建方式(以Runnable接口实例为构造器参数直接通过new创建Thread实例)是一种基于组合的技术。由于组合相对于继承来说,其类和类之间的耦合度更低,也就更加的灵活,所以一般我们优先使用组合的方式。

  2. 从对象共享的角度:第二种创建方式意味着多个线程实例可以共享一个Runnable实例。在某些情况下可能导致程序的运行结果会出乎我们的意料之外。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值