您的位置:首页 > 职场人生

<黑马程序员> 第四篇:多线程

2015-12-03 10:24 447 查看
------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

 

5.1 多线程简介

5.1.1 进程和线程

进程:当前正在执行的程序,代表一个应用程序在内存中的执行区域。

线程:是进程中的一个执行控制单元,执行路径。

多线程:一个进程中有多个执行路径时,这个程序称为多线程。

 

CPU执行原理:

每个程序随机交换执行,CPU切换速度非常的快,貌似是在同时执行很多程序,其实不是,只是切换的很快我们看不出来而已。

 

思考:JVM虚拟机是单线程还是多线程?

答:JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。

1.  执行main函数的线程,该线程的任务代码都定义在main函数中。

2.  负责垃圾回收的线程。

 

5.1.2 多线程优势
优势:

1.  提高界面程序的响应速度。

2.  充分利用系统资源。

 

劣势:

相对于优势,劣势有限,如:当程序中的线程数量较多时,系统将花费大量的时间进行线程的切换,这反而会降低程序的执行效率。

 

 

5.1.3 线程生命周期(线程的五种状态)



A  被创建:

B  运行:既有执行权又有执行资格。

C  临时阻塞:释放了执行权,但持有执行资格。 

D  冻结:释放执行权,释放执行资格,sleep,wait导致阻塞,sleep的时间到

了,wait的被唤醒了,就到了就绪状态。

E  消亡

 

5.2 多线程实现方式

5.2.1 继承Thread类

5.2.1.1 步骤
1.  定义一个类继承Thread类。

2.  重写run方法,将要执行的代码放在run方法里。

3.  创建Thread类子类对象。

4.  调用start方法让其执行run方法里的代码。

 

5.2.1.2 Thread类常用方法
1. 设置名字

(1) 通过构造函数直接初始化名字

(2) 通过setName(String)方法设置线程的名字

2. 获取名字

getName()获取当前线程的名字。

3. 获取当前线程对象

Thread.currentThread()获取当前线程的引用。

4. 休眠

Thread.sleep(int)当时间到的时候,线程继续执行。

5. 守护

setDeamon(true);将线程设置为守护线程,守护线程的特点是,当非守护线程执行完了,守护线程即是没执行完,也会终止,因为有时间的缓冲,所以可能还会再多执行几句。

6. 加入

join()加入到另个线程中,相当于插队,加入的这个线程执行完,在执行被加入的线程。

 

5.2.1.3 继承Thread的特点
1.  当类去描述事物,事物中有属性和行为。如果行为中有部分代码需要被多线程所执行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线程对象。可是这样做会导致每一个对象中都存储一份属性数据。无法在多个线程中共享该数据。可以加上静态,虽然实现了共享但是生命周期过长。

2.  如果一个类明确了自己的父类,那么它就不可以在继承Thread。或者一个类继承了Thread后也同样不能继承别的类,因为java不允许类的多继承。

 

5.2.1.4 实例
public class Test_2 {

public static void main(String[] args) {

Demo2 a = new Demo2();

a.start();

for (int i = 0; i < 10000; i++) {

System.out.println("我是main中的输出语句");

}

}

}

class Demo2 extends Thread{

public void run() {

for (int i = 0; i < 10000; i++) {

System.out.println("我是Demo2中的run方法");

}

}

}

 

 

 

5.2.2 实现Runnable接口

5.2.2.1 步骤
1.  定义一个类实现Runnable接口。

2.  重写run方法,将要执行的代码放在run方法里。

3.  创建我们定义这个类的对象。

4.  将我们创建的Runnable接口的子类对象,通过参数形式传给Tread的构造函数。

 

思考:为什么要给Thread类的构造函数传递Runnable的子类对象?

因为这样相当于只创建一个对象,实现资源的共享,让多条线程共享一个资源

 

5.2.2.2 实现Runnable接口的特点
1.  描述事物的类中封装了属性和行为,如果有部分代码需要被多线程所执行。同时还在操作属性。那么可以通过实现Runnable接口的方式。因为该方式是定义一个Runnable接口的子类对象,可以被多个线程所操作实现了数据的共享。

2.  实现了Runnable接口的好处,避免了单继承的局限性。也就说,一个类如果已经有了自己的父类是不可以继承Thread类的。但是该类中还有需要被多线程执行的代码。这时就可以通过在该类上功能扩展的形式。实现一个Runnable接口。

 

5.2.2.3 实例
public class Test_3 {

public static void main(String[] args) {

Demo3 b = new Demo3();

Thread a = new Thread(b);

a.start();

for (int i = 0; i < 10000; i++) {

System.out.println("我是main中的输出语句");

}

}

}

class Demo3 implements Runnable {

public void run(){

for (int i = 0; i < 10000; i++) {

System.out.println("我是Demo3中的run方法");

}

}

}

 

 

5.2.3继承Thread 与实现Runnable的区别
1.  继承的代码简单

2.  继承是单继承,实现可以多实现

继承Thread这个类后就不能在继承别的类,如果你这个本身就有父类这个方法就行不通,就必须用第二种,如果你定义的类不需要继承任何类,建议用第一种,因为代码简单,是Thread的子类,它里面的方法可以直接用。

 

5.2.4 用匿名内部类实现多线程

5.2.4.1 方式一:直接创建Thread类的的匿名内部类实现多线程
实例:

public class Test_4 {

public static void main(String[] args) {

new Thread () {

public void run(){

for (int i = 0; i < 1000; i++) {

System.out.println("匿名内部类1中的run方法");

}

}

}.start();

new Thread () {

public void run(){

for (int i = 0; i < 1000; i++) {

System.out.println("匿名内部类2中的run方法");

}

}

}.start();

}

}


 

 

5.2.4.2 方式二:创建Runnable 的子类,作为参数传递给Thread
实例:

public class Test_5 {

public static void main(String[] args) {

new Thread (new Runnable(){

public void run(){

for (int i = 0; i < 10000; i++) {

System.out.println("I'm the sentence in the run method1");

}

}

}).start();

new Thread (new Runnable(){

public void run(){

for (int i = 0; i < 10000; i++) {

System.out.println("I'm the sentence in the run method2");

}

}

}).start();

}

}


 

5.2.4.3 方式三:混用
public
4000
class CurrentThread {

public static void main(String[] args) {

Thread t1 = new Thread("天一") {

public void run() {

for (int i = 0; i <1000; i++) {

System.out.println(getName() +" : bbbbbbbbb");

}

}

};

Thread t2 = new Thread(new Runnable() {

public void run() {

for (int i = 0; i < 1000; i++) {

System.out.println(Thread.currentThread().getName() + ":aaaaa");

}

}

}, "美美");

t1.setName("梦鸽");

t2.setName("白鸽");

Thread.currentThread().setName("哥们是主线程");

System.out.println(Thread.currentThread().getName());

System.out.println(t1.getName());

System.out.println(t2.getName());

t1.start();

t2.start();

}

}

 

 

 

5.3线程之间的同步

5.3.1 线程的安全问题

5.3.1.1 概述
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

 

5.3.1.2 线程安全问题出现的原因
1.  多个线程访问出现延迟。

2.  线程随机性。

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

 

资料补充:

线程安全问题都是由全局变量及静态变量引起的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。      

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

 

5.3.1.3 安全问题涉及的内容
1.  当线程中多条代码在同时操作共享数据。

2.  是否被多条语句操作。

 

这也是判断多线程程序是否存在安全隐患的依据。

 

5.3.1.4 解决安全问题的方式  -同步机制
解决原理:

让多条操作共享数据的代码在某一时间段,被一个线程执行完,在执行过程中,其他线程不可以参与运算。

 

5.3.2 同步(synchronized)

5.3.2.1 同步的前提
1.  同步需要两个或者两个以上的线程。

2.  多个线程使用的是同一个锁。

未满足这两个条件,不能称其为同步。

 

5.3.2.2 同步的好处
同步的出现解决了多线程的安全问题。

 

5.3.2.3 同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

 

5.3.2.4 同步代码块
格式:

synchronized(对象) {      //对象可以是任意对象 

//被同步的代码,也就是可能出现安全问题的代码

}

 

思考:对象可以是匿名对象么?

答:不行,这样相当于每个线程都会持一把锁,达不到同步的目的。

 

5.3.2.5 同步函数
格式:

在函数上加上synchronized修饰符即可。

 

思考:同步函数用的是哪个锁呢?

答:同步函数使用的锁是 this。

 

静态同步函数的锁:

1.  静态函数使用的锁肯定不是this,因为静态函数中不可以定义this

2. 静态随着类的加载而加载,这时有可能内容还没有该类的对象,但是一个类加载进内存,会先将这个对应的字节码文件封装成对象,该对象的表示方式:类名.class

3.  该对象在内存中是唯一的.

 

5.3.3 死锁
当同步代码块嵌套时很容易出现死锁,不要想怎么写死锁,要想怎么避免。

 

5.3.4 线程优先级
 

5.4 多线程通信

5.4.1 问题引出
设计一个存储空间和两个线程,一个线程负责向存储空间中存入数据,一个线程负责从存储空间中取出存入的数据,程序运行要求的效果是输入线程向数组中放入数据,输出线程取出输入线程取出输入线程刚放入的数据,达到“你放我取”动态平衡。

而让系统资源去调度线程的执行是无法满足 实际需求的,要想完成线程之间的协同工作,需要让两个线程之间互相通信,即存放数据的数组中没有数据时,不能让输出线程从数组中取数据;同样存放数据的数组中数据放满时,输入线程不能向数组中放入数据。

 

5.4.2 问题如何解决
为了让线程之间实现通信,在Java中是通过object类的wait()、notify()、notifyAll()方法来解决线程间通信问题的,由于object是顶层父类,所以任何类的实例对象都可以直接使用这些方法。

方法声明
功能描述
void wait()
使当前线程放弃同步锁并进入等待,直到其它线程进入此同步锁,并调用notify()方法,或notifyAll()方法唤醒该线程为止。
void notify()
唤醒此同步锁上等待的第一个调用wait()方法的线程。
void notifyAll()
唤醒此同步锁上调用wait()方法的所有线程。
注意:这三个方法的调用者应该是同步锁对象,而且只有在相同的同步锁之调用才能相互起到作用,如果调用等待和唤醒的方法线程没有同步锁,Java虚拟机就会抛出IllegalMonitorStateException异常。

 

实例:

package exercise;

/*

* A: 1

* A: 2

* A: 3

*

* B: 1

* B: 2

* B: 3

*

* C: 1

* C: 2

* C: 3

*

* A: 1

* A: 2

* A: 3

*

* B: 1

* B: 2

* B: 3

*

* C: 1

* C: 2

* C: 3

*

* A: 1

* A: 2

* A: 3

*

* B: 1

* B: 2

* B: 3

*

* C: 1

* C: 2

* C: 3

*

*

*/

public class Exercise01 {

public static void main(String[] args) {

final Printer p = new Printer();

new Thread() {

public void run() {

for (int i = 1; i <= 3; i++)

try {

p.print1();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}.start();

new Thread() {

public void run() {

for (int i = 1; i <= 3; i++)

try {

p.print2();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}.start();

new Thread() {

public void run() {

for (int i = 1; i <= 3; i++)

try {

p.print3();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}.start();

}

}

class Printer {

private int num = 1;

public synchronized void print1() throws InterruptedException {

while(num != 1)

wait();

for (int i = 1; i <= 3; i++)

System.out.println("A: " + i);

System.out.println();

num = 2 ;

notifyAll();

}

public synchronized void print2() throws InterruptedException {

while(num != 2)

wait();

for (int i = 1; i <= 3; i++)

System.out.println("B: " + i);

System.out.println();

num = 3 ;

notifyAll();

}

public synchronized void print3() throws InterruptedException {

while(num != 3)

wait();

for (int i = 1; i <= 3; i++)

System.out.println("C: " + i);

System.out.println();

num = 1 ;

notifyAll();

}

}


 

 

5.4.3 JDK1.5新特性 ——— 同步锁
当创建多个线程时,多个存入,多个读取,那么调用其中一个存数据线程的notify()、notifyAll()方法可能会唤醒另一个存数据的线程,虽不会发生线程安全问题但降低了效率。

为此,JDK1.5之后提供了一个Lock类,可以支持多个相关的Condition对象。Lock替代了synchronized的使用,Condition替代了object同步锁的使用,一个Lock锁可以创建多个Condition对象,Condition对象中定义了await()、signal()、signalAll()方法,相当于Object类中的wait()、notify()、notifyAll()方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: