黑马程序员--多线程

——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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值