Java多线程

简介:

Windows等操作系统均支持多线程进程的并发处理机制。操作系统支持多线程,使多个程序能够并发执行,以改善资源使用率和提高系统效率;操作系统支持多线程,能够减少程序并发时所付出的时间和空间开销,使得开发粒度更细,并发性更好。

进程

进程是一个程序关于某个数据集合的一次执行过程,是操作系统进行资源分配和保护的基本单位。进程具有以下特性:
①结构性。进程包含了数据集合和运行于其上的程序。每个进程至少由三要素组成:程序块、数据块和进程控制块。进程控制块(Process Control Block, PCB)描述和记录进程的动态变化过程,使进程能正确运行。
②独立性。进程既是系统中资源分配和保护的基本单位,也是系统调度的独立单位(单线程进程),每个进程都以各自独立的速度在CPU上运行。
③动态性。进程是程序在数据集合上的一次执行过程,是动态概念。它的生命周期在多个状态间变化,由创建而产生,由调度而执行,因等待条件而阻塞,由撤销而消亡。程序是一组有序指令序列,是静态概念,程序作为一种系统资源是永久存在的。
④并发性。进程的并发性是指一组进程的执行在时间上是重叠的。
⑤交互性。多个进程可以共享变量,通过共享变量实现互相通信,多个进程之间能够协作完成一个任务。

线程

线程是进程中能够独立执行的实体(控制流),是处理器调度和分配的基本单位。线程是进程的组成部分,每个进程内允许包含多个并发执行的线程。同一个进程中的所有线程共享进程获得的内存空间和资源,但不拥有资源。
支持多线程的进程成为多线程进程。
线程的主要特性如下:
①结构性。线程是操作系统调度的基本单位,具有唯一的标识符合线程控制块,其中包含调度所需的一切信息。
②动态性。线程是动态的,而且有状态变化。当创建一个进程时,同时至少为其创建一个线程,需要时再创建其他线程。终止一个进程将导致进程中的所有线程终止。
③并发性。同一进程的多个线程可在一个或多个处理器上并发或并行的执行,进程之间的并发执行演变为线程之间的并发执行。在单处理器上,从宏观上看,在一个时间段中有几个线程都处于运行状态;在微观上看,任意时刻仅有一个线程在处理器上运行。并发的实质是一个处理器在多个线程之间的多路复用,是对有限的物理资源强制行使多用户共享,消除计算机不见之间的互等现象,提高系统资源利用率。
④共享性。同一进程的所有线程共享但部拥有进程的状态和资源,且驻留在进程的内存空间中,可以访问相同的数据。所有线程之间需要有通信和同步机制。

线程的状态

线程在其生命周期中经历着状态的变化,线程状态包括5种:新建、就绪、运行、阻塞、终止。
就绪(ready)态——进程具备运行条件,等待系统分配处理器以便运行。
运行(running)态——进场占用处理器正在运行。
阻塞(blocked)态——进程不具备运行条件,正在等待某个事件的完成。
线程在执行过程中的任一时刻,处于一种状态,根据运行条件在多个状态之间转变。一个进程创建后处于就绪态,运行中因等待条件处于阻塞态。

线程调度

任一时刻只有一个线程能够占用一个处理器运行,按照什么原则决定就绪队列中的哪个线程能够获得处理器就是线程调度的任务。
线程调度的功能就是按照某种原则选择一个线程使它获得处理器运行。线程调度是操作系统的核心部分,线程调度策略的优劣直接影响到操作系统的性能。
线程调度采用剥夺方式,当一个线程正在处理器上执行时,操作系统可以根据规定的原则剥夺它的处理器使用权,而把处理器分配给其他线程使用。常用的剥夺原则有两种:一是高优先级线程可以剥夺低优先级线程运行;二是当运行线程时间使用完后被剥夺处理器。

并发程序设计

顺序程序设计方法是指,程序模块按照语句次序顺序执行,其特性为:①执行的顺序性;②运行环境的封闭性;③执行结果的确定性;④计算结果的可再现性。
并发程序设计方法是指,将一个程序分为若干可同时执行的程序模块,每个程序模块和它执行时所处理的数据结合组成一个进程。操作系统以进程作为系统资源分配的基本单位,以线程系统调度的基本单位。其特性如下:
①并发执行的线程之间不具有顺序性。线程由操作系统调度执行,不会按照语句的书写顺序执行。
②运行环境不再是封闭的,一个线程的执行可能影响其他线程的执行结果。(计算过程不可再现)
③共享变量的多个线程(成为交互线程)之间实现线程通信,能够协作完成一个任务,也会出现与时间有关的错误。
④并发多线程程序设计的优点是,提高了系统性能,具体表现为快速切换线程、减少系统管理开销、线程通信易于实现、并发按程序提高、节省内存空间。

Java的线程对象

Java支持内置的多线程机制。Java语言包中的Runnable接口约定线程的执行方法,Thread类提供创建、管理和控制线程对象的方法。

在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口;Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限。

public class NumberThread extends Thread{
    private int first;
    public NumberThread(String name, int first){
        super(name);
        this.first = first;
    }

    public void run(){
        System.out.print(this.getName() + ":");
        for (int i = first; i < 100; i += 2){
            System.out.print(i + " ");
        }
        System.out.println(this.getName() + "End!\n");
    }

    public static void main(String[] args){
        System.out.println("Current Thread:" +Thread.currentThread().getName());
        NumberThread thread1 = new NumberThread("JISHU", 1);
        NumberThread thread2 = new NumberThread("OUSHU", 2);
        thread1.start();
        thread2.start();
        System.out.println("Active Count:" + Thread.activeCount());
    }
}

在JDK的安装路径下,src.zip是全部的java源程序,通过此代码找到Thread中的start()方法的定义,可以发现此方法中使用了private native void start0();其中native关键字表示可以调用操作系统的底层函数,那么这样的技术成为JNI技术(java Native Interface)。

在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成。但是在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:public Thread(Runnable targer)此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。(start()可以协调系统的资源)

public class NumberRunnable implements Runnable{
    private int first;
    public NumberRunnable(int first){
        this.first = first;
    }

    public void run(){
        for (int i = first; i < 50; i += 2){
            System.out.print(i + " ");
        }
        System.out.println("End!\n");
    }

    public static void main(String[] args){
        NumberRunnable target = new NumberRunnable(1);
        Thread thread1 = new Thread(target, "JISHU");
        thread1.start();
        new Thread(new NumberRunnable(2), "OUSHU").start();
    }
}

两种实现方式的区别和联系:
在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
①适合多个相同的程序代码的线程去处理同一个资源(适合于资源共享)
②可以避免java中的单继承的限制
③增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

用一个买票程序来说明两种实现方式的区别:
继承Thread类:

public class TicketThread extends Thread {
    private int count = 10;
    public TicketThread(String name){
        super(name);
    }
    public void run(){
        while (count > 0){
            System.out.println(Thread.currentThread().getName() + " is selling ticket: " + count);
            count--;
        }
    }

    public static void main(String[] args){
        TicketThread thread1 = new TicketThread("窗口1");
        TicketThread thread2 = new TicketThread("窗口2");
        thread1.start();
        thread2.start();
    }
}

输出结果为:

窗口2 is selling ticket: 10
窗口2 is selling ticket: 9
窗口2 is selling ticket: 8
窗口2 is selling ticket: 7
窗口2 is selling ticket: 6
窗口2 is selling ticket: 5
窗口2 is selling ticket: 4
窗口2 is selling ticket: 3
窗口2 is selling ticket: 2
窗口2 is selling ticket: 1
窗口1 is selling ticket: 10
窗口1 is selling ticket: 9
窗口1 is selling ticket: 8
窗口1 is selling ticket: 7
窗口1 is selling ticket: 6
窗口1 is selling ticket: 5
窗口1 is selling ticket: 4
窗口1 is selling ticket: 3
窗口1 is selling ticket: 2
窗口1 is selling ticket: 1

实现Runnable接口:

public class TicketRunnable implements Runnable{
    private int count = 10;

    public void run(){
        while (count > 0){
            System.out.println(Thread.currentThread().getName() + " is selling ticket: " + count--);
        }
    }

    public static void main(String[] args){
        TicketRunnable target = new TicketRunnable();
        new Thread(target, "窗口1").start();
        new Thread(target, "窗口2").start();
    }
}

输出结果为:

窗口1 is selling ticket: 9
窗口1 is selling ticket: 8
窗口1 is selling ticket: 7
窗口1 is selling ticket: 6
窗口1 is selling ticket: 5
窗口1 is selling ticket: 4
窗口1 is selling ticket: 3
窗口1 is selling ticket: 2
窗口1 is selling ticket: 1
窗口2 is selling ticket: 10

如果多次执行,可以发现上述程序可能出现卖出编号为0的票的情况,这里涉及线程的同步机制,将在后续文章中提到。

线程对象的优先级

Java提供10个等级的线程优先级,分别用1~10表示,优先级最低为1,最高为10,默认值是5。Thread类声明了以下三个表示优先级的公有静态常量:
public static final int MIN_PRIORITY = 1; //最低优先级
public static final int MAX_PRIORITY = 2; //最高优先级
public static final int NORM_PRIORITY = 3; //默认优先级
每个线程兑现创建时自动获得默认优先级5,调用setPriority()方法可以改变线程对象的优先级。

线程对象的生命周期

6种线程状态:
①新建态(NEW)。已创建,未启动。
②运行态(RUNNABLE)。从操作系统角度看,处于新建态的线程启动后,进入就绪态,再由操作系统调度执行而成为运行态。由于线程调度由操作系统控制和管理,程序无法控制,无法区分就绪态和运行态。所以,从程序设计角度看,线程启动后即进入运行态RUNNABLE。进入运行态的线程执行其run()方法。
③阻塞态(BLOCKED)和等待态。一个运行态的线程因某种原因不嫩继续运行时,进入阻塞态或等待态。等待态有两种:WAITING(等待时间不确定)和TIMED_WAITING(等待时间确定)。
④终止态(TERMINATED)。线程对象停止运行未被撤销时是终止态。

Thread类中改变和判断线程状态的方法:

  • start() 新建态到运行态。
  • isAlive() 判断线程是否为活动状态。当一个线程未被终止时,返回true,此时线程处于运行态、阻塞态、等待态之一。但一个线程未启动或已终止时,返回false。
  • sleep() 方法使当前进程停止执行若干毫秒,线程由运行态进行等待态,睡眠时间到,线程可再次进行运行态。
  • interrupt()方法为当前线程设置一个中断标记,以便于run()方法运行时使用IsInterrupted()能够检测到。此时,线程爱sleep()之类的方法中被阻塞时,由sleep()方法抛出java.lang.InterruptedException,线程中断异常,可捕获这个异常进行中断处理操作。

interrupt()只是为线程设置一个中断标记,并没有中断线程运行,该方法没有抛出异常。一个线程被设置了中断标记后仍可运行,isAlive()返回true。
当抛出一个InterruptedException异常时,记录该线程中断情况的标记将会被清除,这样再调用isInterrupted()将返回false。

线程的同步机制

交互线程间存在两种关系:竞争关系协作关系
对于竞争关系的交互线程间需要采用线程互斥方式解决共享资源冲突问题;对于协作关系的交互线程间需要采用线程同步方式解决线程间通信及因执行速度不一致而引起的不同步问题。

交互的并发线程是指他们共享某些变量,一个线程的执行可能影响到其他线程的执行结果,交互的并发线程之间具有制约关系。

无关线程间并发执行时,不会产生于时间有关的错误。
资源竞争出现了两个问题:一个是死锁deadlock)问题,一组线程如果都获得了部分资源,还想得到其他线程所占用的资源,最终所有的线程将陷入死锁;另一个是饥饿starvation)问题,一个线程由于其他线程总是优先于它而被无限期拖延。

线程互斥和临界区管理

线程互斥是解决线程间竞争关系的手段。线程互斥(mutual exclusion)是指若干个线程要使用同一共享资源时,任何时刻最多允许一个线程去使用,其他要使用该资源的线程必须等待,直到占有资源的线程释放该资源。

共享变量代表的资源成为临界资源(critical resource),并发线程中与共享变量有关的程序段成为临界区(critical section)。由于与同一变量有关的临界区分散在各有关线程的程序段中,而各线程的执行速度不可预知,因此,操作系统对共享一个变量的若干线程各自进入临界区有以下3个调度原则
①一次至多一个线程能够在它的临界区内
②不能让一个线程无限期的停留在它的临界区内
③不能强迫一个线程无限地等待进入它的临界区。特别地,进入临界区的任一线程不能妨碍等待进入的其他线程的进展。
把临界区的调度原则总结成四句话:无空等待、有空让进、择一而入、算法可行。

操作系统提供“互斥锁”机制实现并发线程互斥地进入临界区,对共享资源进行操作。

Java的互斥机制实现

Java提供关键字synchronized用于声明一段程序为临界区,使线程对临界资源采用互斥使用方式。synchronized有两种用法:声明一条语句,或者声明一个方法。
同步语句
synchronized(对象)
语句
其中,对象是多个线程共同操作的公共变量,即需要被锁定的临界资源。

同步方法
synchronized 方法声明
同步方法的方法体成为临界区,互斥使用(锁定)的是调用该方法的对象。

线程间的协作关系与线程同步

当合作线程中的一个到达协调点后,在尚未得到其伙伴线程发来的信号之前应先阻塞自己,直到其他合作线程发来协调信号后方被唤醒并继续执行。这种协作线程之间相互等待对方消息或信号的协调关系成为线程同步。
线程同步是解决线程间协作关系的手段。线程同步(synchronization)是指两个以上线程基于某个条件来协调它们的活动。一个线程的执行依赖于另一个线程的信号,当一个线程没有得到来自于另一个线程的信号时则需要等待,直到信号到达才被唤醒。

线程互斥是一种特殊的线程同步机制,即逐次使用互斥共享资源,也是对线程使用资源次序上的一种协调。

操作系统实现线程同步有一种成为信号量PV操作。测试信号量状态的操作成为P操作,改变信号量状态的操作称为V操作,这两种操作是互斥的,并且执行时不能被打断。多个线程之间彼此根据信号量的状态确定谁该执行。

Java的线程通信方法

java.lang.Object类提供wait()、notify()、和notifyAll()方法实现线程间通信。

注意:

  • 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁—-而且同步方法很可能还会被其他线程的对象访问。
  • 每个对象只有一个锁(lock)与之相关联。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

synchronized作为函数修饰符时,它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。


转载:http://blog.csdn.net/foreverling/article/details/46572621

目录
相关文章
|
16天前
|
Java 调度
Java线程的六种状态
Java线程有六种状态: 初始(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)、终止(TERMINATED)。
37 1
|
2天前
|
Java 程序员 调度
Java中的多线程编程:概念、实现及性能优化
【5月更文挑战第85天】本文主要探讨了Java中的多线程编程,包括其基本概念、实现方式以及如何进行性能优化。首先,我们将介绍多线程的基本概念,然后详细讨论如何在Java中实现多线程,包括继承Thread类和实现Runnable接口两种方式。最后,我们将探讨一些提高多线程程序性能的策略,如使用线程池和减少同步开销等。
|
2天前
|
监控 Java 开发者
深入理解Java并发编程:线程池的原理与实践
【5月更文挑战第85天】 在现代Java应用开发中,高效地处理并发任务是提升性能和响应能力的关键。线程池作为一种管理线程的机制,其合理使用能够显著减少资源消耗并优化系统吞吐量。本文将详细探讨线程池的核心原理,包括其内部工作机制、优势以及如何在Java中正确实现和使用线程池。通过理论分析和实例演示,我们将揭示线程池对提升Java应用性能的重要性,并给出实践中的最佳策略。
|
3天前
|
安全 Java 数据处理
Java并发编程:线程同步与协作的深度解析
在探索Java并发编程的海洋中,线程同步与协作的灯塔指引着航向。本文将深入挖掘线程同步机制的核心原理,揭示锁、条件变量等工具如何确保数据的一致性和线程间有序的通信。通过案例分析,我们将解码高效并发模式背后的设计哲学,并探讨现代Java并发库如何简化复杂的同步任务。跟随文章的步伐,您将获得提升多线程应用性能与可靠性的关键技能。 【7月更文挑战第24天】
17 5
|
1天前
|
Java
如何在Java中实现线程池?
在Java中,线程池是高效管理线程的关键机制,避免了无限制创建线程的资源浪费和系统不稳定。通过`Executor`和`ExecutorService`接口,代码与具体线程池实现解耦,提供灵活性。`Executors`类简化线程池创建,适合基本需求
|
1天前
|
Java
如何在Java中实现多线程的Socket服务器?
在Java中,多线程Socket服务器能同时处理多个客户端连接以提升并发性能。示例代码展示了如何创建此类服务器:监听指定端口,并为每个新连接启动一个`ClientHandler`线程进行通信处理。使用线程池管理这些线程,提高了效率。`ClientHandler`读取客户端消息并响应,支持简单的文本交互,如发送欢迎信息及处理退出命令。
|
2天前
|
Java
如何使用jstack命令查看Java进程的线程栈
如何使用jstack命令查看Java进程的线程栈?
10 2
|
5天前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。
|
16天前
|
存储 安全 Java
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
64 13
|
7天前
|
安全 算法 Java
Java 中的并发控制:锁与线程安全
在 Java 的并发编程领域,理解并正确使用锁机制是实现线程安全的关键。本文深入探讨了 Java 中各种锁的概念、用途以及它们如何帮助开发者管理并发状态。从内置的同步关键字到显式的 Lock 接口,再到原子变量和并发集合,本文旨在为读者提供一个全面的锁和线程安全的知识框架。通过具体示例和最佳实践,我们展示了如何在多线程环境中保持数据的一致性和完整性,同时避免常见的并发问题,如死锁和竞态条件。无论你是 Java 并发编程的新手还是有经验的开发者,这篇文章都将帮助你更好地理解和应用 Java 的并发控制机制。