夜雪天狼
学习笔记
技术博文
转载备份
心灵鸡汤
目录
多线程
发布者:caijw
阅读量:62818
发布时间:2013-08-08 00:28:13
# 概述 ## 进程 是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。 ## 线程 就是进程中的一个独立的控制单元。线程在控制着进程的执行。一个进程中至少有一个线程。 ## 多线程 在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。 ## 多线程的好处 多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。 例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。 ## 计算机CPU的运行原理 我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。其实不是这样,而是在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。 而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定 # 创建线程的方式 创建线程共有两种方式:继承Thread类和实现Runnable接口 ## 继承Thread类 java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继承Thread类,然后复写其run方法的方式来创建线程。 创建步骤: 1. 定义类继承Thread。 2. 复写Thread中的run方法。目的:将自定义代码存储在run方法中,让线程运行 3. 创建定义类的实例对象。相当于创建一个线程。 4. 用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。 > 注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。 覆盖run方法的原因: Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就是run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。 ```java /* 小练习 创建两线程,和主线程交替运行。 */ //创建线程Test class Test extends Thread { // private String name; Test(String name) { super(name); // this.name=name; } //复写run方法 public void run() { for(int x=0;x<60;x++) System.out.println(Thread.currentThread().getName()+"..run..."+x); // System.out.println(this.getName()+"..run..."+x); } } class ThreadTest { public static void main(String[] args) { new Test("one+++").start();//开启一个线程 new Test("tow———").start();//开启第二线程 //主线程执行的代码 for(int x=0;x<170;x++) System.out.println("Hello World!"); } } ``` ## 实现Runnable接口 使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复习其中run方法的方式 创建步骤: 1. 定义类实现Runnable的接口。 2. 覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。 3. 通过Thread类创建线程对象。 4. 将Runnable接口的子类对象作为实参传递给Thread类的构造方法。 5. 调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法 >为什么要将Runnable接口的子类对象传递给Thread的构造函数? 因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象 实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式 # 两种方式的区别和线程的几种状态 ## 两种创建方式的区别 继承Thread类:线程代码存放在Thread子类run方法中。 实现Runnable接口:线程代码存放在接口子类run方法中。 ## 几种状态 被创建:等待启动,调用start启动。 运行状态:具有执行资格和执行权。 临时状态(阻塞):有执行资格,但是没有执行权。 冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。 消忙状态:stop()方法,或者run方法结束。 > 注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。 # 线程安全问题 ## 导致安全问题的出现的原因 当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。 简单的说就两点: 1. 多个线程访问出现延迟 2. 线程随机性 > 注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的 ## 解决办法——同步 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步) 同步有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现 ### 同步代码块 用法:synchronized(对象){需要被同步的代码} 同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁 ### 同步函数 用法:在函数上加上synchronized修饰符即可 那么同步函数用的是哪一个锁呢? 函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。 ## 同步的前提 1. 必须要有两个或者两个以上的线程 2. 必须是多个线程使用同一个锁 如果使用了同步,还出现了安全问题,肯定是这两个前提没有遵守 ## 同步的利弊 好处:解决了多线程的安全问题 弊端:多个线程需要判断锁,较为消耗资源 ## 如何寻找多线程中的安全问题 1. 明确哪些代码是多线程运行代码 2. 明确共享数据 3. 明确多线程运行代码中哪些语句是操作共享数据的 # 静态函数的同步方式 如果同步函数被静态修饰后,使用的锁是什么呢? 通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如: 类名.class 该对象的类型是Class 这就是静态函数所使用的锁:是该方法所在类的字节码文件对象。类名.class # 死锁 当同步中嵌套同步时,就有可能出现死锁现象 -separator-