---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

黑马程序员-------------(四)多线程

目录

一 概述

二 线程的五种状态

三 自定义线程

    1.继承方式

    2.实现方式

    3.实现方式和继承方式的区别

四 同步

    1.多线程运行的安全问题

    2.解决方式-同步

    3.死锁

五 线程间通信

    1.等待唤醒机制

    2.JDK1.5新特性-Lock

    3.停止线程

    4.join方法

注:本章重点内容:线程的五种状态,自定义线程,同步,JDK1.5新特性-Lock。对于重点内容,根据知识的重要程度用◆◆◆、◆◆◆◆、◆◆◆◆◆进行了标注。

####################################################################################
一.概述
####################################################################################

1.进程:正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。进程只要启动就会在内存空间中分配一块空间,内存给它分配一个地址,而进程其实就是用于定义和标识空间,封装了里面的控制单元。

2.线程:是进程中用于控制程序执行的控制单元(执行路径,执行情景)。线程在控制着进程的执行。一个进程中至少有一个线程。Java的编译器启动的时候会有一个编译进程javac.exe, java JVM启动的时候会有一个运行进程java.exe.该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。其实更细节说明jvm,jvm启动时不止一个线程,还有负责垃圾回收机制的线程。

4.多线程存在的意义
多线程的出现可以让程序中的多个部分产生同时运行的效果。目的是为了让多部分代码同时执行。例如多线程下载可以提高效率;

5.线程中一些常见方法:

Runnable 接口
该接口只有一个方法。大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。
void run() 使用实现接口 Runnable 的对象创建一个线程时,启动该线程将会在独立执行的线程中调用对象的 run 方法。

Thread 类
构造方法摘要
public Thread() 分配新的 Thread 对象。
public Thread(Runnable target) 分配新的 Thread 对象。
public Thread(String name) 分配新的 Thread 对象。name - 新线程的名称。
public Thread(Runnable target, String name) 分配新的 Thread 对象。 target - 其 run 方法被调用的对象。name - 新线程的名称。
 
常用方法
public void run() 如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。
public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
public final String getName() 返回该线程的名称。
public final void join() 等待该线程终止。
public final void setDaemon(boolean on) 将该线程标记为守护线程(用户线程、后台线程)。当前台线程都运行结束后,后台线程会自动结束。
public static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。

####################################################################################
◆◆◆◆◆【二.线程的五种状态】◆◆◆◆◆
####################################################################################


1.被创建

2.运行状态

3.冻结状态(等待、睡眠)冻结状态的特点:放弃了执行资格。

4.临时状态(阻塞状态)临时状态的特点:具备了执行资格,但不具备执行权。

5.消亡


####################################################################################
◆◆◆【三.自定义线程】◆◆◆
####################################################################################

通过对API的查找,java已经提供了对线程这类事物的描述,就是Thread类。
    
1.继承方式
创建线程的第一种方式:继承Thread类。

1.1 步骤:
(1)定义类继承Thread。
(2)复写Thread类中的run方法。目的:将自定义的线程要运行的代码存储在run方法中。让线程运行。
(3)建立子类对象,其实就是在创建线程。调用线程的start方法。该方法两个作用:启动线程,JVM调用该线程run方法。

1.2 代码体现

 1     class Demo extends Thread
 2     {
 3         public void run()
 4         {
 5             //自定义的代码;
 6         }
 7     }
 8     class Test
 9     {
10         public static void main (String[] args)
11         {
12             Demo d = new Demo();//创建好一个线程。
13             d.start();//开启线程并执行该线程的run方法。
14             //d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
15         }
16     }


1.3 问题
(1)多线程的运行结果每一次都不同。
因为对于计算机来说,在某一个时刻,只能有一个程序在运行(多核除外),cpu在做着快速的切换,以达到看上去是同时运行的效果。多个线程都在获取cpu的执行权。cpu执行到谁,谁就运行。我们可以形象地把多线程的运行称为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。

(2)为什么要覆盖run方法呢?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。

注意:
a.线程都有自己默认的名称:Thread-编号 该编号从0开始。
b.主线程运行的代码存在于main方法中(虚拟机定义的); 自定义线程运行的代码存在于自定义子类的run方法中。
c.局部的变量在每一个线程区域当中都有独立的一份。线程0在运行时,栈内存中就会给线程0分配一个空间,这个空间里有一个run方法,方法
  里有一个x变量;线程1在运行时,栈内存中又会给线程1分配一个空间,这个空间里也有一个独立的run方法,方法里有一个x变量。

2.实现方式
创建线程的第二种方式:实现Runnable接口。如果自定义的类中有多线程要运行的代码。但是该类有自己的父类。那么就不可以在继承Thread。

2.1 步骤:
(1)定义类实现Runnable接口。
(2)覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。
(3)创建Thread类的对象(创建线程)。
(4)将实现了Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。        
(5)调用Thread类的start方法,开启线程。

2.2 代码体现

 1     class Demo implements Runnable
 2     {
 3         public void run()
 4         {
 5             //自定义的代码;
 6         }
 7     }
 8     class Test
 9     {
10         public static void main (String[] args)
11         {
12             Demo d = new Demo();
13             Thread t=new Thread(d);
14             t.start();//开启线程并执行该线程的run方法。
15         }
16     }


2.3 为什么要将Runnable接口的子类对象传递给Thread的构造函数?
      因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去执行指定对象的run方法。就必须明确该run方法所属对象。

3.实现方式和继承方式的区别
    实现方式好处:(1)避免了单继承的局限性。在定义线程时,建议使用实现方式。(2)资源可以被独立共享。
    两种方式区别:继承Thread,线程代码存放Thread子类run方法中;实现Runnable,线程代码存在接口的子类的run方法。

注意:
Thread类本身也实现Runnable接口,因为Runnable接口的定义其实就是在确立线程要运行代码所存放的位置,即run方法。大多数情况下,如果只想重写run()方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。

####################################################################################
◆◆◆◆【四 同步 synchronized 】◆◆◆◆
####################################################################################

1.多线程运行的安全问题。

问题的原因:
多线程具备随机性。因为是由cpu不断的快速切换造成的。由于线程的随机性。当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

2.解决方式
Java对于多线程的安全问题提供了专业的解决方式:同步。同步的原理就是将部分操作功能数据的代码进行加锁。

2.1 同步代码块

格式
    synchronized(对象)
    {
        需要被同步的代码
    }

注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执
行权,也进不去,因为没有获取锁。例子:火车上的卫生间。

2.2 同步函数

(1)格式:
在函数上返回值之前加上synchronized修饰符即可。
(2)同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。

2.3 静态同步函数
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不再是this。因为静态方法中不可以定义this。静态进内存时,内存中还没有本类对象,但是一定有该类对应的字节码文件对象。类名.class  该对象的类型是Class。静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class

2.4 什么时候使用同步
(1)同步的利弊
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源;同步嵌套后,容易死锁。
(2)使用同步的前提:
1)必须要有两个或者两个以上的线程。
2)必须是多个线程使用同一个锁

3.死锁。
同步中嵌套同步。

代码体现:

 1     class Test implements Runnable
 2     {
 3 
 4   //定义一个循环的标记,让线程能执行不同的代码
 5         private boolean flag;
 6         Test(boolean flag)
 7         {
 8             this.flag = flag;
 9         }
10         public void run()
11         {
12             if(flag)
13             {
14                 while(true)
15                 {
16 
17       //获取locka锁
18                     synchronized(MyLock.locka)
19                     {
20                         System.out.println(Thread.currentThread().getName()+"...if locka ");
21 
22         //获取lockb锁
23                         synchronized(MyLock.lockb)
24                         {
25                             System.out.println(Thread.currentThread().getName()+"..if lockb");                    
26                         }
27                     }
28                 }
29             }
30             else
31             {
32                 while(true)
33                 {
34 
35       //获取lockb锁
36                     synchronized(MyLock.lockb)
37                     {
38                         System.out.println(Thread.currentThread().getName()+"..else lockb");
39 
40         //获取locka锁
41                         synchronized(MyLock.locka)
42                         {
43                             System.out.println(Thread.currentThread().getName()+".....else locka");
44                         }
45                     }
46                 }
47             }
48         }
49     }
50     class MyLock
51     {
52         static Object locka = new Object();
53         static Object lockb = new Object();
54     }
55     class  DeadLockTest
56     {
57         public static void main(String[] args)
58         {
59             Thread t1 = new Thread(new Test(true));
60             Thread t2 = new Thread(new Test(false));
61             t1.start();
62             t2.start();
63         }
64     }

 



####################################################################################
五 线程间通讯
####################################################################################
其实就是多个线程在操作同一个资源,但是操作的动作不同。常见的有生产者消费者的例子。

1.等待唤醒机制
wait();
notify();
notifyAll();

(1)为什么只能使用在同步中?
因为要对持有监视器(锁)的线程操作。只有同步才具有锁,所以要使用在同步中。
(2)为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法存在于同步中,使用这些方法时必须要标识所属的同步的锁。也就是说,当操作同步中线程时,都必须要标识它们所操作线程持有的
锁,只有同一个锁上的等待线程,可以被同一个锁上的notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
(3)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
(4)对于多个生产者和消费者。
定义while判断标记。原因:让被唤醒的线程再一次判断标记。
定义notifyAll。       原因:因为需要唤醒对方线程。只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
(5)代码示例

 1 package cn.itheima.thread;
 2 public class ProducerConsumerTest {
 3     public static void main(String[] args) {
 4         Resource resource=new Resource("商品");
 5         new Thread(new Producer(resource)).start();
 6         new Thread(new Consumer(resource)).start();
 7         new Thread(new Producer(resource)).start();
 8         new Thread(new Consumer(resource)).start();
 9     }
10 }
11 class Resource{
12     private String name;
13     private int count=1;
14     private boolean flag;
15     public Resource(String name){
16         this.name=name;
17     }
18     public synchronized void produce(){
19         while(flag){
20             try {
21                 wait();
22             } catch (InterruptedException e) {
23                 e.printStackTrace();
24             }
25         }
26         System.out.println(Thread.currentThread().getName()+"---------生产者------"+name+count++);
27         flag=true;
28         notifyAll();
29     }
30     public synchronized void consume(){
31         while(!flag){
32             try {
33                 wait();
34             } catch (InterruptedException e) {
35                 e.printStackTrace();
36             }
37         }
38         System.out.println(Thread.currentThread().getName()+"---消费者---"+name+count);
39         flag=false;
40         notifyAll();
41     }
42 }
43 class Producer implements Runnable{
44     private Resource r;
45     public Producer(Resource r){
46         this.r=r;
47     }
48     public void run() {
49         while(true)
50             r.produce();
51     }
52 }
53 class Consumer implements Runnable{
54     private Resource r;
55     public Consumer(Resource r){
56         this.r=r;
57     }
58     public void run() {
59         while(true)
60         r.consume();
61     }
62 }

 


◆◆◆◆◆【2.JDK1.5新特性-Lock】◆◆◆◆◆    

JDK1.5 中提供了多线程升级解决方案。

(1)将隐式锁升级成了显示锁。将同步 synchronized 替换成实现 Lock 操作。
Lock 接口中的方法
    获取锁: void lock();
    释放锁: void unlock();注意:释放的动作一定要执行,所以通常定义在finally中。
    获取Condition对象: Condition newCondition();

(2)将Object中的wait,notify,notifyAll方法都替换成了Condition对象的await,signal,signalAll。该对象可以通过 Lock 的方法
     newCondition()进行获取。并且可以实现本方只唤醒对方操作。
    Condition
        await();
        signal();
        signalAll();

区别:
一个同步代码块具备一个锁,该锁具备自己的独立wait和notify方法。现在是将wait,notify等方法,封装进一个特有的对象Condition,而一个
Lock锁上可以有多个Condition对象。现在不用锁嵌套了。
(3)代码示例

  1 package cn.itheima.thread;
  2 
  3 import java.util.concurrent.locks.Condition;
  4 import java.util.concurrent.locks.Lock;
  5 import java.util.concurrent.locks.ReentrantLock;
  6 
  7 public class ProducerConsumerTest2 {
  8     public static void main(String[] args) {
  9 
 10   //创建资源
 11         Resource resource=new Resource("商品");
 12 
 13   //创建两个生产者和两个消费者
 14         new Thread(new Producer(resource)).start();
 15         new Thread(new Consumer(resource)).start();
 16         new Thread(new Producer(resource)).start();
 17         new Thread(new Consumer(resource)).start();
 18     }
 19 }
 20 
 21 //生产者
 22 class Resource2{
 23     private String name;
 24     private int count=1;
 25     private boolean flag;
 26     private Lock lock=new ReentrantLock();
 27     private Condition condition_pro=lock.newCondition();
 28     private Condition condition_con=lock.newCondition();
 29     public Resource2(String name){
 30         this.name=name;
 31     }
 32     public void produce() throws InterruptedException{
 33         lock.lock();
 34         try{
 35 
 36     //通过循环的标记进行判断
 37             while(flag){
 38                 condition_pro.await();
 39             }
 40             System.out.println(Thread.currentThread().getName()+"---------生产者------"+name+count++);
 41             flag=true;
 42 
 43     //唤醒对方所有线程
 44             condition_con.signalAll();
 45         }
 46         finally{
 47             lock.unlock();
 48         }
 49     }
 50     public synchronized void consume() throws InterruptedException{
 51         lock.lock();
 52         try{
 53 
 54     //通过循环的标记进行判断
 55             while(!flag){
 56             condition_con.await();
 57             }
 58             System.out.println(Thread.currentThread().getName()+"---消费者---"+name+count);
 59             flag=false;
 60 
 61     //唤醒对方的所有线程
 62             condition_pro.signalAll();
 63         }
 64         finally{
 65                 lock.unlock();
 66         }
 67     }
 68 }
 69 
 70 //生产者对应的线程
 71 class Producer2 implements Runnable{
 72     private Resource2 r;
 73     public Producer2(Resource2 r){
 74         this.r=r;
 75     }
 76     public void run() {
 77         while(true)
 78             try {
 79                 r.produce();
 80             } catch (InterruptedException e) {
 81                 e.printStackTrace();
 82             }
 83     }
 84 }
 85 
 86 //消费者对应的线程
 87 class Consumer2 implements Runnable{
 88     private Resource2 r;
 89     public Consumer2(Resource2 r){
 90         this.r=r;
 91     }
 92     public void run() {
 93         while(true)
 94             try {
 95                 r.consume();
 96             } catch (InterruptedException e) {
 97                 e.printStackTrace();
 98             }
 99     }
100 }

 


3.停止线程
stop方法已经过时。
    
如何停止线程?
只有一种方式,run方法结束。开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();

如何让run方法结束?
(1)定义循环结束标记。因为线程运行代码一般都是循环,只要控制了循环即可。
(2)使用interrupt(中断)方法。该方法是结束线程的冻结状态,使线程回到运行状态中来。
 
4.join方法
join可以用来临时加入线程执行。当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。

如果线程顺序是ABC,A线程(主线程)-->B线程 开启;B线程jion();-->C线程开启;
首先开始A线程,然后开始B线程,当A线程执行到B线程的join方法时会释放执行权,进入冻结状态,直至B线程结束从冻结状态解除;然后开始了C线程,A和C互相争夺CPU执行权。

如果线程顺序是ABC,A线程(主线程)-->B线程 开启;-->C线程开启;B线程jion();
首先开始A线程,然后开始B线程,然后开始了C线程,当A线程执行到B线程的join方法时会释放执行权,进入冻结状态,直至B线程结束从冻结状态解除,此时A线程只是释放了执行权,但是B和C线程都活着,所以它们会争夺cpu执行权,即BC交替运行,当B线程结束时,不管C线程是否结束,A线程都会解除冻结状态,如果C尚未结束,就变成A和C抢夺执行权,即Ac交替运行。
   
5.代码示例
   

 1 class MyThread extends Thread
 2     {
 3         public void run()
 4         {
 5             try
 6             {
 7                 Thread.currentThread().sleep(3000);
 8             }
 9             catch (InterruptedException e) {}
10             System.out.println("MyThread running");
11         }
12     }
13     public class ThreadTest
14     {
15         public static void main(String[] args)
16         {
17             MyThread t = new MyThread();
18             t.run();
19             t.start();
20             System.out.println("Thread Test");
21         }
22     }


代码分析过程:

MyThread t = new MyThread();
创建了一个线程。

t.run();
调用MyThread对象的run方法。这时只有一个线程在运行,就是主线程。当主线程执行到了run方法中的sleep(3000);时。这时主线程处于冻结状
态。没有任何程序执行。当3秒过后,主线程打印了  MyThread running。 run方法执行结束。

t.start();
开启了t线程。有两种可能情况。
第一种情况,主线程在只执行了t.start()后,还具有执行权,继续往下执行,打印了Thread Test。主线程结束。t线程获取执行权,调用自己的run方法。然后执行的sleep(3000);冻结3秒。3秒后,打印 MyThread running。t线程结束,整个程序结束。
第二种情况:主线程执行到t.start();开启了t线程,t线程就直接获取到了执行权。就调用自己的run方法。执行到sleep(3000).t线程冻结3秒,这时t线程就释放了执行权。那么主线程开始执行打印了Thread Test,主线程结束。等到3秒后,t线程打印MyThread running ,然后t线程结束。程序结束。

posted @ 2013-10-13 22:25  赵晨光  阅读(208)  评论(0编辑  收藏  举报
---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ------- --------------- 详细请查看:http://edu.csdn.net