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

目录
相关文章
|
18天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
80 17
|
28天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
14天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
30天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
30天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
1月前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
58 3
|
1月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
167 2
|
1月前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
54 6
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
66 3

热门文章

最新文章