黑马程序员--多线程

来源:互联网 发布:二维数组变成一维数组 编辑:程序博客网 时间:2024/05/18 01:10

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
注:视频来源,毕向东老师的 JAVA 基础视频。

一、线程和进程

1)进程:是一个在执行中的程序。每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

2)线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。

3) 理解:一个进程中,至少有一个线程。在JVM 启动的时候会有一个进程叫java.exe该进程中,至少有一个线程,在负责 java 程序的执行,而且这个线程,运行的代码存在于 main 方法中,该线程称之为主线程。其实,JVM 中更细节的说明了虚拟机不止一个线程,包含了一个主线程和一个负责控制垃圾回收机制的线程。通过API的查找,java已经提供了对线程这类事物的描述,就是 Thread 类。

4)多线程的随机性:因为多个线程都获取 cpu 的执行权。 cpu 执行到谁,谁就运行。但是要明确的是:在某一时刻,只能有一个程序在运行。(多核除外。)CPU 在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行形容为在互相抢夺 CPU 的资源(执行权)我们可以形象的把多线程的运行形容为在互相抢夺 CPU 的资源(执行权)这就是多线程的特性,谁抢到谁执行。至于执行多长时间。CPU 说的算。

5)覆盖 run 方法的说明:Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run 方法。也就是说 Thread 类中 run 方法,用于存储线程需要运行的代码。所以说自定义线程,就是把代码放在run方法中。

6)线程都有自己的默认名称。Thread-编号,该编号从0开始。

currentThread:获取当前线程对象,这是一个静态的线程可以直接调用。

Ps:static Thread currentThread()

getName():获取线程的名称。

setName():设置线程名称,setName或者通过构造函数。

二、线程的生命周期

1)运行过程:

被创建: start()–>运行– sleep(time) –> 冻结(:放弃执行资格)

运行– sleep时间到 <– 冻结(:放弃执行资格)

运行– wait() –> 冻结(:放弃执行资格)

运行– notify() <– 冻结(:放弃执行资格)

2)消亡:stop() ,就是run方法中的结束。

3) 说明:没有资格执行的状态,冻结状态。当wait状态的时候,线程冻结了,没办法自动重启,这时候可以使用notify方法唤醒线程。

4)临时阻塞:具备运行资格,但是没有执行权。

线程的图解
这里写图片描述
三、创建线程的两种方式

总结两种方式的区别:

继承 Thread :线程代码存放在 Thread 子类 的 run 方法中。

实现 Runnable:线程代码存放在接口的子类的 run 方法中。

方式一:继承 Thread 类。

步骤:

1、定义类继承 Thread 。

2、复写 Thread 类中的 run 方法。目的:将自定义的代码存储在 run 方法中,让线程运行。

3、调用线程的 start 方法。该方法有两个作用,启动线程,调用run方法。

方法二:实现 Runnable 接口。

步骤:

1、定义类实现 Runnable 接口。

2、覆盖 Runnable 接口中的 Run 方法。将线程要运行的方法存放在该 run 方法中。

3、通过 Thread 类建立线程对象。

4、将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。

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

5、调用 Thread 的 start 方法开启线程,并调用 Runnable接口子类的 run 方法。
简单的卖票程序:

package fuxi;/** *  *@author XiaLei */public class Day11Test {    public static void main(String[] args) {        ThreadTest t1 = new ThreadTest();//      ThreadTest t2 = new ThreadTest();//      ThreadTest t3 = new ThreadTest();//      ThreadTest t4 = new ThreadTest();//      t1.start();//      t2.start();//      t3.start();//      t4.start();被注释的是第一种方法,创建ThreadTest类继承Thread用其子类创建对象         /*        直接调用 run 方法,就没有开启线程。          start方法有两个作用:1.调用run方法2.开启线程        t1.run();          t2.run();          */         new Thread(t1).start();         new Thread(t1).start();         new Thread(t1).start();         new Thread(t1).start(); //实现线程第二种方法,实现Runnable接口。    }}//class ThreadTest extends Thread{//  private int tickets = 100;//  public void run(){//      //      while(true){//          if (tickets>0){//              System.out.println(currentThread().getName()+"sale"+tickets--); //显示线程名及余票数 //              //          }//      }//  }//}class ThreadTest implements Runnable{    private int tickets = 100;    public void run(){        while(true){            synchronized(this){ //给程序加同步,即锁  。            if (tickets>0){                try{Thread.sleep(10);}catch(Exception e){}//因为sleep方法有异常声明,所以这里要对其进行处理                  System.out.println(Thread.currentThread().getName()+"sale"+tickets--);              }            }        }    }}

四、多线程的安全问题

java 对于多线程的安全问题,提供了专业的解决方式。就是同步代码块。

好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源。

1)最常用的线程使用方法:implements,就是用实现的方法写线程Thread。

2)运行时出现安全问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。另一个线程参与了进来执行,导致了共享

数据的错误。

3)解决方法:在对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其它线程不可以参与执行。java 对于多线程的安全问题,提供了专业的解决方式。就是同步代码块。

4)同步的前提:

1、必须要有两个或者两个以上的线程才需要synchroized锁上。

2、必须是多个线程同时使用一个锁。

5)实现方式:

synchronized(对象){

需要被同步的代码//同步带共享操作的数据,如if的判断tick开始。

}

对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程,即使获取了 CPU 的执行权,也进不去,因为没有获取锁。

package fuxi;/** * 需求:银行有一个金库。有两个储户分别存300员,每次存一百。存3次。 * 目的:该程序是否有安全问题:有的 * 如何找问题: * 1、明确那些代码是多线程运行的代码。 * 2、明确共享数据。 * 3、明确多线程代码中哪些语句是操作共享数据的。 * 同步函数用的是哪一个锁呢?函数需要被对象调用。那么函数都有一个属性对象引用。就是this * 所以同步函数使用的锁是this 。 *@author XiaLei */public class Day11Test2 {    public static void main(String[] args) {        Cuz c1 = new Cuz();        new Thread(c1).start();        new Thread(c1).start();    }}class Bank{    private int sum;    public void add(int x){        synchronized(this){//也可以在函数上直接加锁         sum+=x;         System.out.println(Thread.currentThread().getName()+"=="+sum);        }    }}class Cuz implements Runnable{    private Bank b = new Bank();    public void run(){        for (int x = 0;x<3;x++){            b.add(100);        }    }}

显示结果:
这里写图片描述
6)静态函数的同步方式
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
经典示例:

package fuxi;/** *  *@author XiaLei */public class day11Test11 {    public static void main(String[] args) {    }}//懒汉式单例设计模式class Single{    private static Single s = null;    private Single(){}    public static Single getInstance(){        if (s==null)            synchronized(Single.class){                if (s==null){                    s = new Single();                }            }        return s;    }   }

7)线程的死锁,同步中嵌套同步

package fuxi;/** * 思路:设计一个死锁程序,死锁就是同步里面嵌套同步,但是两个同步用的锁不同。 *@author XiaLei */public class Day11Test3 {    public static void main(String[] args) {        DeadLock dl = new DeadLock(true);        DeadLock d2 = new DeadLock(false);        new Thread(dl).start();        new Thread(d2).start();    }}class Lock{    static  Lock locka = new Lock();    static  Lock lockb = new Lock();}class DeadLock implements Runnable{    boolean flag = true;    DeadLock (boolean flag){        this.flag = flag;    }    public void run(){        if (flag){            synchronized(Lock.locka){                System.out.println("if=="+"Lock.locka");                synchronized(Lock.lockb){                    System.out.println("if=="+"Lock.lockb");                }            }        }        else{            synchronized(Lock.lockb){                System.out.println("else=="+"Lock.lockb");                synchronized(Lock.locka){                System.out.println("else=="+"Lock.locka");                }            }        }    }}

打印结果:程序卡住,不能继续执行
这里写图片描述
五、线程间的通信之等待唤醒机制

1)线程间的通信:其实就是多个线程在同时操作一个资源,但是操作的动作不同。

2)等待唤醒机制:其实就是 wait 方法和 notify 方法的使用。

使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。

3)为什么这些操作线程的方法要定义在Object类中呢?

因为这些方法在操作同步线程时,都必须要标识他们所操作线程的锁。只有同一个锁上的被等待线程,可以被同一个锁 notify 唤醒,不可以对不同锁中的线程唤醒。也就是说,等待和唤醒必须是同一个锁。而锁,可以是任意对象,所以可以被任意对象调用的方法定义在 Object 中。

等待唤醒机制示例:

package fuxi;/** *  *@author XiaLei */public class Day12Test {    public static void main(String[] args) {        Res r = new Res();        new Thread(new Input(r)).start();        new Thread(new Output(r)).start();    }}class Res{    private String name;    private String sex;    private boolean flag = false;    public synchronized void set(String name,String sex){            if (flag)                try{this.wait();}catch(Exception e){}            this.name = name;            this.sex = sex;            flag = true;            this.notify();    }    public synchronized void out(){            if (!flag)                try{this.wait();}catch(Exception e){}            System.out.println(name+">>>"+sex);            flag = false;            this.notify();//唤醒等待    }}class Input implements Runnable{    private Res r;    Input(Res r){        this.r = r;    }    public void run(){        int x = 0;        while (true){            if (x==0)                r.set("jack", "man");            else                r.set("丽丽", "女女女女");            x=(x+1)%2;//让x变成1;          }    }}class Output implements Runnable{    private Res r;    Output(Res r){        this.r = r;    }    public void run(){        while(true){            r.out();        }    }}

部分打印结果:
这里写图片描述
4)全部唤醒

对于多个生产者和消费者,就必须用 while + notifyAll(唤醒全部)。

1)为什么要用 while 判断标记呢?

原因:让被唤醒的线程再一次判断标记。

2)为什么定义 notifyAll ?

因为唤醒自己的同时还要唤醒对方的线程。只用 notify ,容易出现只唤醒本方线程的情况。导致程序中的所有线程都在等待。这也是比较有用的方法。
示例:

package fuxi;/** *  *@author XiaLei */public class Day12Test1 {    public static void main(String[] args) {        Resouce r = new Resouce();        new Thread(new Producer(r)).start();        new Thread(new Producer(r)).start();        new Thread(new Consumer(r)).start();        new Thread(new Consumer(r)).start();    }}class Resouce{    private String name;    private int count = 1;    private boolean flag = false;    public synchronized void set(String name){        while(flag)            try{                wait();            }            catch(Exception e){                throw new RuntimeException();            }            this.name = name + count++;            System.out.println(Thread.currentThread().getName()+"...生产者......"+this.name);            flag = true;            notifyAll();    }    public synchronized void out(){        while(!flag)            try{                wait();            }            catch(Exception e){                throw new RuntimeException();            }            System.out.println(Thread.currentThread().getName()+"+消费者+"+this.name);            flag = false;            notifyAll();//全部唤醒     }}class Producer implements Runnable{    private Resouce r;    Producer(Resouce r){        this.r = r;    }    public void run(){        while(true){            r.set("汉堡");        }    }}class Consumer implements Runnable{    private Resouce r;    Consumer(Resouce r){        this.r = r;    }    public void run(){        while(true){            r.out();        }    }}

部分打印结果:
这里写图片描述
5)JDK1.5中提供了多线程升级解决方案
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
升级解决方案的示例:

package fuxi;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;/** *  *@author XiaLei */public class Day12Test2 {    public static void main(String[] args) {            Resouce r = new Resouce();            new Thread(new Producer(r)).start();            new Thread(new Producer(r)).start();            new Thread(new Consumer(r)).start();            new Thread(new Consumer(r)).start();        }    }    class Resouce{        private String name;        private int count = 1;        private boolean flag = false;        private ReentrantLock lock = new ReentrantLock();        //创建两Condition对象,分别来控制等待或唤醒本方和对方线程          private Condition condition_pro = lock.newCondition();        private Condition condition_con = lock.newCondition();        public void set(String name) throws InterruptedException{            lock.lock();                try{                    while(flag)                        condition_con.await();                    this.name = name + count++;                    System.out.println(Thread.currentThread().getName()+"...生产者......"+this.name);                    flag = true;                    condition_pro.signal();                }                finally{                    lock.unlock();//解锁动作一定要执行                  }        }        public void out() throws InterruptedException{            lock.lock();                try{                    while(!flag)                        condition_pro.await();                    System.out.println(Thread.currentThread().getName()+"+消费者+"+this.name);                    flag = false;                    condition_con.signal();                }                finally{                    lock.unlock();                }        }    }    class Producer implements Runnable{        private Resouce r;        Producer(Resouce r){            this.r = r;        }        public void run(){            while(true){                try {                    r.set("汉堡");                } catch (InterruptedException e) {                }            }        }    }    class Consumer implements Runnable{        private Resouce r;        Consumer(Resouce r){            this.r = r;        }        public void run(){            while(true){                try {                    r.out();                } catch (InterruptedException e) {                }            }        }    }

部分结果:
这里写图片描述

六、停止线程
1) JDK 1.5 后,停止线程的 stop 方法已经过时,那如何停止线程呢?

只有一种 run 方法结束。

2) 开启多线程运行,运行代码通常是循环结构。只要控制循环,就可以让 run 方法结束,也就是线程结束。

3)特殊情况:当线程处于冻结状态。就不会读取标记,那么线程就不会结束。

处理方法:强制中断线程:interrupt

当没有指定的方式让冻结的线程回复到运行的状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态来,实际上就是获取运行资格。这样就可以操作标记,让线程结束。

守护线程,setDaemon(boolean b);

4) 扩展知识:
1、join方法
当A线程执行到了b线程的join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。

2、setPriority()方法用来设置优先级
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY 分配给线程的默认优先级

3、yield()方法可以暂停当前线程,让其他线程执行。

0 0