Java从零开始(2)--多线程详解

多线程指的是在单个程序中可以同时运行多个同的线程执行不同的任务。线程是程序内的顺序控制流,只能使用分配给序的资源和环境。

    一、线程与进程的区别

多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响。 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。

多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。

二、了解一下java在多线程中的基础知识

1.Java中如果我们自己没有产生线程,那么系统就会给我们产生一个线程(主线程,main方法就在主线程上运行),我们的程序都是由线程来执行的。

2. 进程:执行中的程序(程序是静态的概念,进程是动态的概念)。

3. 线程的实现有两种方式,第一种方式是继承Thread类,然后重写run方法;第二种是实现Runnable接口,然后实现其run方法。 

4. 将我们希望线程执行的代码放到run方法中,然后通过start方法来启动线程,start方法首先为线程的执行准备好系统资源,然后再去调用run方法。当某个类继承了Thread类之后,该类就叫做一个线程类。

5. 一个进程至少要包含一个线程。

6. 对于单核CPU来说,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行)。

7. 对于双核或双核以上的CPU来说,可以真正做到微观并行。

三、Thread源码研究:

1) Thread类也实现了Runnable接口,因此实现了Runnable接口中的run方法;

2) 当生成一个线程对象时,如果没有为其设定名字,那么线程对象的名字将使用如下形式:Thread-number,该number将是自动增加的,并被所有的Thread对象所共享(因为它是static的成员变量)。

3) 当使用第一种方式来生成线程对象时,我们需要重写run方法,因为Thread类的run方法此时什么事情也不做。

4)当使用第二种方式生成线程对象时,我们需要实现Runnable接口的run方法,然后使用new Thread(new MyThread())(假如MyThread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法或调就会MyThread类的run方法,这样我们自己编写的run方法就执行了。

说明:

Public void run(){

If(target!=null){

Target.run();

}}

当使用继承Thread生成线程对象时,target为空,什么也不执行,当使用第二种方式生成时,执行target.run(),target为runnable的实例对象,即为执行重写后的方法

总结:两种生成线程对象的区别:

1.两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。

2.在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。

四:线程的生命周期:

由上图可以看出,一个线程由出生到死亡分为五个阶段:

1).创建状态 

•当用new操作符创建一个新的线程对象时,该线程处于创建状态。

•处于创建状态的线程只是一个空的线程对象,系统不为它分配资源

2). 可运行状态 

•执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体—run()方法,这样就使得该线程处于可运行( Runnable )状态。

•这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行。

3).不可运行状态 

.当发生下列事件时,处于运行状态的线程会转入到不可运行状态。

调用了sleep()方法;

•线程调用wait方法等待特定条件的满足

•线程输入/输出阻塞

4)返回可运行状态: 

•处于睡眠状态的线程在指定的时间过去后

•如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变

•如果线程是因为输入/输出阻塞,等待输入/输出完成

5). 消亡状态 

当线程的run方法执行结束后,该线程自然消亡。

注意:

1.停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。

2.不能依靠线程的优先级来决定线程的执行顺序。

五:多线程并发

多线程并发是线程同步中比较常见的现象,java多线程为了避免多线程并发解决多线程共享数据同步问题提供了synchronized关键字

synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。

1.Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。

2. 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。

3.如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。

4. synchronized块,写法:

synchronized(object)

{

}

表示线程在执行的时候会对object对象上锁。

5.synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。

同步的线程状态图:

六:wait与notify

1.wait与notify方法都是定义在Object类中,而且是final的,因此会被所有的Java类所继承并且无法重写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或块当中。当线程执行了wait方法时,它会释放掉对象的锁。

2. 另一个会导致线程暂停的方法就是Thread类的sleep方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。

3.notify():唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。

此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:

o 通过执行此对象的同步实例方法。

o 通过执行在此对象上进行同步的 synchronized 语句的正文。

o 对于 Class 类型的对象,可以通过执行该类的同步静态方法。

一次只能有一个线程拥有对象的监视器。

关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。  如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。

七:死锁的问题: 

定义:线程1锁住了对象A的监视器,等待对象B的监视器,线程2锁住了对象B的监视器,等待对象A的监视器,就造成了死锁。

导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性访问权。当线程访问对象时,线程会给对象加锁

Java中每个对象都有一把锁与之对应。但Java不提供单独的lock和unlock操作。下面笔者分析死锁的两个过程“上锁”和“锁死” 。

(1) 上锁
     许多线程在执行中必须考虑与其他线程之间共享数据或协调执行状态,就需要同步机制。因此大多数应用程序要求线程互相通信来同步它们的动作,在 Java 程序中最简单实现同步的方法就是上锁。在 Java 编程中,所有的对象都有锁。线程可以使用 synchronized 关键字来获得锁。在任一时刻对于给定的类的实例,方法或同步的代码块只能被一个线程执行。这是因为代码在执行之前要求获得对象的锁。

为了防止同时访问共享资源,线程在使用资源的前后可以给该资源上锁和开锁。给共享变量上锁就使得 Java 线程能够快速方便地通信和同步。某个线程若给一个对象上了锁,就可以知道没有其他线程能够访问该对象。即使在抢占式模型中,其他线程也不能够访问此对象,直到上锁的线程被唤醒、完成工作并开锁。那些试图访问一个上锁对象的线程通常会进入睡眠状态,直到上锁的线程开锁。一旦锁被打开,这些睡眠进程就会被唤醒并移到准备就绪队列中。

(2)锁死
     如果程序中有几个竞争资源的并发线程,那么保证均衡是很重要的。系统均衡是指每个线程在执行过程中都能充分访问有限的资源,系统中没有饿死和死锁的线程。当多个并发的线程分别试图同时占有两个锁时,会出现加锁冲突的情形。如果一个线程占有了另一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。

在编写多线程代码时,笔者认为死锁是最难处理的问题之一。因为死锁可能在最意想不到的地方发生,所以查找和修正它既费时又费力。例如,常见的例子如下面这段程序。print?

1 public int sumArrays(int[] a1, int[] a2){

2   int value = 0;

3   int size = a1.length;

4   if (size == a2.length) {

5      synchronized(a1) { //1

6        synchronized(a2) { //2

7          for (int i=0; i<size; i++)

8             value += a1[i] + a2[i];

9        }

10      }

11   } return value;

12 }

这段代码在求和操作中访问两个数组对象之前锁定了这两个数组对象。它形式简短,编写也适合所要执行的任务;但不幸的是,它有一个潜在的问题。这个问题就是它埋下了死锁的种子。

ThreadLocal类(这个类本人没用过,占时不太懂)

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

时间: 2024-07-31 14:31:20

Java从零开始(2)--多线程详解的相关文章

java多线程详解

转自:线程间通信.等待唤醒机制.生产者消费者问题(Lock,Condition).停止线程和守护线程.线程优先级 1  线程间通信 1.1  线程间通信 其实就是多个线程在操作同一个资源,但是操作的动作不同. 比如一个线程给一个变量赋值,而另一个线程打印这个变量. 1.2  等待唤醒机制 wait():将线程等待,释放了CPU执行权,同时将线程对象存储到线程池中. notify():唤醒线程池中一个等待的线程,若线程池有多个等待的线程,则任意唤醒一个. notifyAll():唤醒线程池中,所有

Java 多线程详解(二)------如何创建进程和线程

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html 在上一篇博客中,我们已经介绍了并发和并行的区别,以及进程和线程的理解,那么在Java 中如何创建进程和线程呢? 1.在 Windows 操作系统中创建进程 在 windows 操作系统中,我们创建一个进程通常就是打开某个应用软件,这便在电脑中创建了一个进程.更原始一点的,我们在命令提示符中来做(我们以打开记事本这个进程为例): 第一步:windows+R,

Java 多线程详解(五)------线程的声明周期

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程:http://www.cnblogs.com/ysocean/p/6883491.html Java 多线程详解(三)------线程的同步:http://www.cnblogs.com/ysocean/p/6883729.html Java 多线程详解(四)------生产者和消费者:http:/

Java 多线程详解(三)------线程的同步

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程:http://www.cnblogs.com/ysocean/p/6883491.html 介绍完如何创建进程以及线程了,那么我们接着来看一个实例: 利用多线程模拟 3 个窗口卖票 第一种方法:继承 Thread 类 创建窗口类 TicketSell package com.ys.thread; p

Java多线程详解与晋级(一)

Java多线程详解与晋级(一) Java线程的概念与原理 一.操作系统中线程和进程的概念 进程是指操作系统内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程. 线程是指进程中的一个执行流程,一个进程中可以运行多个线程.比如java.exe进程中可以运行很多线程.线程总是属于某个进程,进程中的多个线程共享进程的内存. 简单来说,进程就是一个应用程序在操作系统上的一次执行过程,而线程是进程中的一次执行流程,进程包含多个线程在运行. 二.Java的线程 Thread类

java classLoader体系结构使用详解

原创整理不易,转载请注明出处:java classLoader体系结构使用详解 代码下载地址:http://www.zuidaima.com/share/1774052029516800.htm jvm classLoader architecture: Bootstrap ClassLoader/启动类加载器 主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作. Extension ClassLoader/扩展类加载器  主要负

iOS开发——多线程OC篇&amp;多线程详解

多线程详解 前面介绍了多线程的各种方式及其使用,这里补一点关于多线程的概念及相关技巧与使用,相信前面不懂的地方看了这里之后你就对多线程基本上没有什么问题了! 1——首先ios开发多线程中必须了解的概念: 进程 正在进行中的程序被称为进程,负责程序运行的内存分配 每一个进程都有自己独立的虚拟内存空间 线程 线程是进程中一个独立的执行路径(控制单元) 一个进程中至少包含一条线程,即主线程 可以将耗时的执行路径(如:网络请求)放在其他线程中执行 创建线程的目的就是为了开启一条新的执行路径,运行指定的代

Java 8 Stream API详解

Java 8 Stream API详解 一.Stream API介绍 Java 8引入了全新的Stream API,此Stream与Java I/O包里的InputStream和OutputStream是完全不同的概念,它不同于StAX对XML解析的Stream,也不同于Amazon Kinesis对大数据实时处理的Stream.Stream API更像具有Iterable的集合类,但行为和集合类又有所不同,它是对集合对象功能的增强,专注于对集合对象进行各种非常便捷.高效的聚合操作或大批量数据操

Android研究之游戏开发多线程详解

 游戏开发与软件开发多线程的重要性       如果程序主线程被阻塞超过5秒,系统会提示"应用程序无响应" 这就是ANR . ANR的全称是Application Not Responding,使用多线程可以避免ANR.但是这里要注意一下不要为了避免ANR而过多的使用多线程,除非万不得已的情况. 比如访问网络服务端返回的过慢.数据过多导致滑动屏幕不流畅.或者I/O读取过大的资源等等.这里可以开启一个新线程来处理这些耗时的操作. 如果过多使用多线程会出现数据同步的问题须要程序员去处理

Java内存模型(JMM)详解

在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的理解底层原理,写出更高效的代码. 就Java内存模型而言,它是深入了解Java并发编程的先决条件.对于后续多线程中的线程安全.同步异步处理等更是大有裨益. 硬件内存架构 在学习Java内存模型之前,先了解一下计算机硬件内存模型.我们多知道处理器与计算机存储设备运算速度有几个数量级的差别.总不能让处理