[Java基础] Java线程复习笔记

先说说线程和进程,现代操作系统几乎无一例外地采用进程的概念,进程之间基本上可以认为是相互独立的,共享的资源非常少。线程可以认为是轻量级的进 程,充分地利用线程可以使得同一个进程中执行多种任务。Java是第一个在语言层面就支持线程操作的主流编程语言。和进程类似,线程也是各自独立的,有自 己的栈,自己的局部变量,自己的程序执行并行路径,但线程的独立性又没有进程那么强,它们共享内存,文件资源,以及其他进程层面的状态等。同一个进程内的 多个线程共享同样的内存空间,这也就意味着这些线程可以访问同样的变量和对象,从同一个堆上分配对象。显然,好处是多线程之间可以有效共享很多资源,坏处 是要确保不同线程之间不会产生冲突。

每个Java程序都至少有一个线程——main线程。当Java程序开始运行时,JVM就会创建一个main线程,然后在这个main线程里面调用
程序的main()方法。JVM同时也会创建一些我们看不到的线程,比如用来做垃圾收集和对象终结的(garbage collection and
object finalization,JVM最重要的两种资源回收),或者JVM层面的其他整理工作。

为什么要使用线程?

1、可以使UI(用户界面)更有效(利用多线程技术,可以把时间较长的UI工作交给专门的线程,这样UI的主线程就不会被长期占用,界面就会流畅而不停滞)

2、有效利用多进程系统(单线程+多进程,太浪费系统资源了)

3、简化建模

4、执行异步处理或者后台处理(不同的线程做不同的工作)

线程的生命周期:

通常有两种方法创建一个线程,1、implement Runnable接口,2、继承Thread类

创建完成后,这个线程就进入了New State,直到它的start()方法被调用,它就进入了Runnable状态。

一个线程从Running State进入Terminated / Dead State标志着线程的终结,正常情况下有这么几种可能性:

1、线程的run()执行结束

2、线程抛出没有捕捉到的异常或者错误

当一个Java程序所有的非守护进程(Daemon Thread,即守护进程,负责一些包括资源回收在内的任务,我们无法结束这些进程)结束时,程序宣告执行结束。

Java Thread的重要方法必须熟悉。

join():目标线程结束之前调用线程将会被Block,例如在main线程中创建了一个thread1线程,调用 thread1.join(),这就意味着thread1将优先执行,在thread1结束后main thread才会继续。一个join()方法的使用案例:将一个任务(比如从1万个元素的数组中选出最大值)分拆成10个小任务(每个小任务负责1000 个)分配给10个线程,调用它们的start(),然后分别调用join(),以确保10个任务都完成(分别选出了各自负责的1000个元素中的最大值) 后,主任务再进行下去(从10个结果中挑出最大值)。

sleep():使当前线程进入Waiting State,直到指定的时间到了,或者被其他线程打断,从而回到Runnable State。

wait():使调用线程进入Waiting State,直到被打断,或者时间到,或者被其他线程使用notify(),notifyAll()叫醒。

wait和sleep有一个非常重要的区别是,一个线程sleep的时候不会释放任何lock,而wait的时候会释放该对象上的lock。

notify():这个方法被一个对象调用时,如果有多个线程在等待这个对象,这些处于Waiting State的线程中的一个会被叫醒。

notifyAll():这个方法被一个对象调用时,如果有多个线程在等待这个对象,这些处于Waiting State的线程都会被叫醒。

多线程共享资源是讨论最多的话题,也是最容易出问题的地方之一,Java定义了两个关键字,synchronized和volatile,用来帮助共享的变量在多线程情况下能够正常工作。

synchronized一方面确保同一时间内只有一个线程能够执行一段受保护的代码,并且这个线程对数据(变量)进行的改动对于其他线程是可见
的。这里包含两层意思:前者依靠lock(锁)来实现,当一个线程处理一段受保护代码时,该线程就拥有lock,只有它释放了这个lock,其他线程才有
可能获得并访问这段代码;后者由JVM机制实现,对于受synchronized保护的变量,需要读取时(包括获取lock)会首先废弃缓存
(invalidate cache),进而直接读取main memory上的变量,完成改动时(包括释放lock)会flush缓存中的write
operation,强行把所有改动更新到main memory。

为了提高performance,处理器都是会利用缓存来保存一些变量储存在内存中的地址,这样就存在一种可能性,在一个多进程架构中,一个内存地
址在一个进程的缓存中被修改了,其他进程并不会自动获得更新,于是不同进程上的2个线程就会看到同一个内存变量的两个不同值(因为两个缓存中的保存的内存
地址不同,一个被修改过)。Volatile关键字可以有效地控制原始类型变量(primitive
variable,比如integer,boolean)的单一实例:当一个变量被定义为volatile的时候,无论读写,都会绕过缓存而直接对
main
memory进行操作。

关于Java的锁(Locking)有一个问题需要注意:一段被lock保护的代码并不意味着就一定不能被多线程同时访问,而只意味着不能被等待同一个lock的多线程同时访问。

对于绝大多数的synchronized方法,它的lock就是调用方法的实例对象;对于static
synchronized方法,它的lock是定义方法的类(因为static方法是每个类只有一份copy,而不是每个实例都有一份copy)。因
此,即使一个方法被synchronized保护了,多线程仍然可以同时调用这个方法,只要它们是调用不同实例上的这个方法。

synchronized代码块稍微复杂一些,一方面它也需要和synchronized方法一样定义lock的类型,另一方面必须考虑如果最小化
被保护的代码块,即能不放到synchronized里面就不放进去,比如局部变量的访问通通不需要保护,因为局部变量本身就只存在于单线程上。

下面两种加锁的方法是等效的,都是以Point类的实例为lock(即多线程可以同时访问不同Point实例的synchronized setXY()方法):

    public class Point {
      public synchronized void setXY(int x, int y) {
    this.x = x;
    this.y = y; }
    }  
    public class Point {
      public void setXY(int x, int y) {
        synchronized (this) {
          this.x = x;
          this.y = y;
    } }
    }  

死锁(deadlock)是多线程编程中最怕遇到的情况。什么是死锁?当2个或2个以上的线程因为等待彼此释放lock而处于无限的等待状态就称 为死锁。简单来说就是线程1拥有对象A的lock,等待获取对象B的lock,线程2拥有对象B的lock,等待获取对象A的lock,这样就没完没了 了。

如何检测deadlock?

检查代码,看是否有层叠的synchronized代码块,或者调用彼此的synchronized方法,或者试图获取多个对象上的lock,等等。如果程序员不注意的话,这些情况都容易导致deadlock。

怎么防止deadlock是一个大话题,可以写一本书,简单来说的话就是当线程需要获取多个lock的时候(比如线程1和2都要获取对象A和B的 lock),永远按照一定的次序来。比如如果线程1和2都是先获取对象A的lock,再获取对象B,那就不会出现上面的deadlock了,因为如果1获 得了A lock,2就得等,而不是去获得B lock。

总结一下synchronized关键字的一些注意点:

1、synchronized关键字确保了需要同一个lock的多线程永远无法同时或并行访问同一个共享资源或者synchronized方法

2、synchronized关键字只能修饰方法或者代码块

3、任何时候一个线程想要访问synchronized方法或者代码块时,都要先获取lock,任何时候一个线程结束访问synchronized方法或代码块时,都会释放lock。即使因为错误或异常结束访问,也会释放lock

4、Java线程进入一个实例层synchronized方法时,要先获取对象层面的lock(object level lock);进入静态synchronized方法时,要先获取类层面的lock(class level lock)

5、一个Java synchronized方法调用另一个synchronized方法,两个方法需要同一个lock的时候,线程不需要重新获取lock

6、在synchronized(myInstance)中,如果myInstance为Null,会抛出NullPointerException

7、synchronized关键字一个主要缺点就是它不支持并行的读取(因此对于一些值不可变的情况不要使用这个关键字,否则会无谓地影响performance)

8、synchronized关键字还有一个限制,它只支持单一JVM内的共享资源访问,对于多JVM共享一些文件资源或者数据库资源的时候,单单使用它就不够了,这时候程序员需要实现全局性的lock

9、synchronized关键字对performance影响很大,因此只有当真正需要的时候才用

10、优先使用synchronized代码块,而不是synchronized方法,确保将synchronized代码减小到最精,能不synchronized就不用synchronized关键字

11、静态和非静态的synchronized方法可能同时或者并行运行,因为它们被认为是使用了不同的lock(一个是object level,一个是class level)

12、从Java 5开始,对于volatile修饰的变量,读和写都被保证是原子的(atomic),即安全的。从performance的角度,操作volatile变量比从synchronized代码中访问变量要高效

13、synchronized代码可能会导致死锁

14、Java不允许在构造函数中使用synchronized关键字。理由很简单,如果构造函数中出现synchronized关键字,那当一个线程在构造实例时,其他线程都不知道,这就违背了同步的原则

15、synchronized关键字不能用于修饰变量,正如volatile关键字不能用于修饰方法

16、Java.util.concurrent.locks包提供了synchronized关键字的扩展功能,可以帮助程序员编写更为复杂的多线程操作

17、synchronized关键字同步内存(线程内存和主内存)

18、Java synchronization的一些关键方法,比如wait()、notify()、notifyAll(),定义在Object类中

19、在synchronized代码块中不要以非final变量(non final
field)为锁,因为非final变量的引用常常会改变,一旦锁改变了,那synchronization就失去了意义。比如这个例子,一旦对
String变量进行操作,就在内存中生成新的String对象

    private String lock = new String("lock");
    synchronized(lock){
    System.out.println("locking on :"  + lock);
    }  

20、不推荐使用String对象作为synchronized代码块的锁,即使是final String。因为String存放在内存的String变量池中,可能会有其他代码或者第三方的代码使用了同一个String对象为锁,这样容易导致一 些无法预测的问题。在下面的例子中,与其使用LOCK为锁,还不如创建一个Object实例为锁。

    private static final String LOCK = "lock";   //not recommended
    private static final Object OBJ_LOCK = new Object(); //better  

    public void process() {
       synchronized(LOCK) {
          ........
       }
    }  

21、在Java库中,很多类默认不是线程安全的,需要程序员特别注意加上安全保护,比如Calendar, SimpleDateFormat等。

[Java基础] Java线程复习笔记,布布扣,bubuko.com

时间: 2024-09-30 20:38:07

[Java基础] Java线程复习笔记的相关文章

(CZ深入浅出Java基础)线程笔记

一.线程的引入 1.多线程概述 1.1.进程 a.正在运行的程序,是系统进行资源分类和调用的独立单位. b.每个进程都有它自己的内存空间和系统资源. 1.2.线程 a.是进程中的单个顺序控制流,是一条执行路径. b.一个进程如果只有一条路径,则称为单线程程序. c.一个进程如果有多条执行路径,则称为多线程程序. 1.3.小结 线程多的进程抢到CPU执行权的概率大,但是仍具有随机性. 2.Java程序运行原理 2.1.Java运行 Java命令会启动Java虚拟机,启动JVM,等于启动了一个应用程

JAVA 基础之 IO复习

一.文件: 在IO包中唯一与文件相关的类就是 File类. File类中常用的常量和方法 1.创建文件: 指定路径和将要创建的文件名字以及类型: 然后调用 createNewFile()方法 File file = new File("D:"+File.separator+"MyJavaProgram"+File.separator+"hello.java"); file.createNewFile(); 2.删除文件: 指定路径和文件,包括类型

[Java基础] java的守护线程与非守护线程

最近重新研究Java基础知识,发现以前太多知识知识略略带过了,比较说Java的线程机制,在Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) ,(PS:以前忽略了). 估计学过Unix开发但是没有细致学习Java的同学们会疑惑了,操作系统里面是没有所谓的守护线程的概念,只有守护进程一说,但是Java语言机制是构 建在JVM的基础之上的,意思是Java平台把操作系统的底层给屏蔽起来,所以它可以在它自己的虚拟的平台里面构造出对自己有利的机制,而语言或者说

Java基础--Java入门

IsCoder 标记: Java基础,Java环境配置 一.Java环境配置 Java Develop Kit(JDK安装) 系统环境变量设置(JAVA_HOME) Java源程序编辑工具 Java编译运行命令 运行经典的HelloWorld程序 1.1 JDK安装 JDK,就是甲骨文公司提供给我们的Java开发工具包,包括最常用的Javac.exe编译工具和Java.exe运行工具.需要指出的是,JDK中已经包含了JER(Java Runtime Environment,Java运行时环境),

Java基础--Java编程规范

IsCoder 标记: Java基础,Java编程规范 摘要:Java虽然没有强制性的编程规范,但是为了便于统一,Java有不成文的编程规范,为了形成良好的编程习惯,建议熟悉并遵守Java编程规范,提高代码的阅读性. 一.Java标识符 在任何语言中,都要自己的一套标识符规则.Java的标识符包括:Java关键字.Java特殊功能的标识符.合法的用户自定义标识符.其中,我们能改变的只有自定义的标识符,和大部分计算机编程语言一样,Java标识符原始只支持ASCII的编码,但是随着Java在世界的影

[Java基础] Java float保留两位小数或多位小数

方法1:用Math.round计算,这里返回的数字格式的. float price=89.89; int itemNum=3; float totalPrice=price*itemNum; float num=(float)(Math.round(totalPrice*100)/100);//如果要求精确4位就*10000然后/10000 方法2:用DecimalFormat 返回的是String格式的.该类对十进制进行全面的封装.像%号,千分位,小数精度.科学计算. float price=

[Java基础] Java对象内存结构

转载地址:http://www.importnew.com/1305.html 原文于2008年11月13日 发表, 2008年12月18日更新:这里还有一篇关于Java的Sizeof运算符的实用库的文章. 学C/C++出身的我,对Java有一点非常困惑,那就是缺乏计算对象占用内存大小的机制.而在C++中就可以通过sizeof运算符来获得基本类型以及类实例的大小.C和C++中的这个操作符对于指针运算.内存拷贝和IO操作都非常有用. Java中并没有一个类似的运算符.事实上,Java也不需要这种运

Java基础----Java中的String和StringBuffer

String和StringBuffer String是一个特殊的对象,一旦被初始化,就不会被改变.()指的是abc 不是变量 s1. String s1="abc"; s1是一个类类型变量,"abc"是一个对象. String s2=new String("abc"); s1和s2的区别: s1在字符串常量池中创建了一个abc字符串 s2在堆中创建了两个对象一个是默认对象一个是字符串对象. ==和equals的区别 ==比较的是地址,equals

Java基础----Java API中的常用类

System:描述系统的一些信息 preperties();获取系统信息 Properties prop =new System.getProperties(); 是hashtable 的子类.用map的方法去除该类集合中的元素.该集合中存储的都是字符串,没有泛型定义. String calue=(String)prop.get(obj); System.out.println(obj+":"+value); //如何在系统中自定义一些特有信息? System.setProperty(