黑马Java多线程的理解(简单易懂)

多线程

实现方法

  • 1.继承Thread类的方式进行实现
  • 2.实现Runnable接口的方式进行实现
  • 3.利用Callable接口和Future接口方式实现

继承Thread类的方式进行实现

步骤
  • 1.定义一个类继承Thread
  • 2.重写run方法
  • 3.创建子类对象,并启动线程(start方法
代码
//在类中
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("hello world!"+this.getName());
        }
    }
}
//在main中
public class Main {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.setName("t1");
        mt2.setName("t2");
        mt1.start();
        mt2.start();
    }
}

实现Runnable接口的方式进行实现

步骤
  • 1.自己定义一个类实现Runnable接口
  • 2.重写里面的run方法
  • 3.创建自己的类的对象
  • 4.创建一个Thread类的对象,并开启线程
代码
//在类中
public class MyThread implements Runnable {
    @Override
    public void run() {
        //获取当前线程的对象
        Thread t = Thread.currentThread();
        //执行逻辑
        for (int i = 0; i < 10; i++) {
            System.out.println("hello world!"+t.getName());
        }
    }
}
//在main中
public class Main {
    public static void main(String[] args) {
        //创建MyThread对象
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        //创建线程对象
        Thread t1 = new Thread(mt1);
        Thread t2 = new Thread(mt2);
        //给线程设置名字
        t1.setName("t1");
        t2.setName("t2");
        //开启线程
        t1.start();
        t2.start();
    }
}

利用Callable接口和Future接口方式实现

  • 特点
    • 对上述方法的补充
    • 可以获取多线程运行的结果
步骤
  • 1.创建一个类实现Callable接口
  • 2.重写call(是有返回值的,表示多线程运行的结果)
  • 3.创键实现Callable接口的对象(表示多线程要执行的任务
  • 4.创建FutureTask对象(作用管理多线程运行的结果
  • 5.创建Thread类的对象,并启动(表示线程
代码
//实现Callable的类
public class MyThread implements Callable {
    //返回值只能写包装类
    @Override
    public Integer call() throws Exception {
        int sum =0;
        for (int i = 0; i < 20; i++) {
            sum+=i;
        }
        return sum;
    }
}
//main
public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创键实现Callable接口的对象
        MyThread mt = new MyThread();
        //创建实现Future接口的对象
        FutureTask<Integer> ft = new FutureTask<>(mt);
        //创建线程的对象
        Thread t1 = new Thread(ft);
        //启动线程
        t1.start();

        //获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);
    }

三种方法对比

方法优点缺点
Thread类编程简单可拓展性差,使得类不能继承其他类
Runnable类拓展性强,类可以继承其他类的同时实现该接口编程较为复杂
Callable类拓展性强,类可以继承其他类的同时实现该接口,可以返回多线程的处理结果编程较为复杂

常见的成员方法

方法名称说明
String getName()返回此线程的名称
void setName(String name)设置线程的名字
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority)设置线程的优先级
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置守护线程
public static void yield()出让线程/礼让线程
public static void join()插入线程/插队线程
  • 解释:
    • 守护进程:守护进程就是在其他进程结束之后,再陆续结束(及时守护进程代码是循环1000次,也会及时结束
      • 例如:就像骑士守护公主一样,一旦公主不存在了,骑士也就没有了存在的意义。
    • 出让线程:出让CPU的执行权
    • 插入线程:就是在运行别的线程时优先运行插入的线程,当插入的线程运行完毕后再运行之前的线程。

线程的声明周期

在这里插入图片描述

线程安全问题

  • 例如一个影院分三个窗口,出售100张电影票。如果用之前学习到的方法写一个程序,你会发现有一些问题。例如:窗口出售重复的票,出售的票超过100张等问题。之类问题属于资源竞争问题,就是100张票的变量被三个进程访问,票数来不及更新就被下一个进程引用,从而出现上述问题。

  • 代码

//线程类
public class MyThread extends Thread {
    static int tickets = 100;

    @Override
    public void run() {
        //获取当前线程信息
        Thread t = Thread.currentThread();
        while(tickets>0){
            tickets--;
            System.out.println(t.getName() + "获取电影票,剩余" + tickets + "张!");
        }
    }
}
//main
public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();

        mt1.setName("1");
        mt2.setName("2");
        mt3.setName("3");

        mt1.start();
        mt2.start();
        mt3.start();
    }
}
  • 部分结果
3获取电影票,剩余97张!
1获取电影票,剩余97张!
2获取电影票,剩余97张!
1获取电影票,剩余96张!
3获取电影票,剩余96张!
2获取电影票,剩余95张!
...
2获取电影票,剩余2张!
1获取电影票,剩余1张!
3获取电影票,剩余0张!
2获取电影票,剩余-1张!
1获取电影票,剩余-2张!

解决方法
同步代码块
  • 将操作共享数据的代码锁起来(实现在并发执行过程中,同一时刻只有一个线程能访问程序)

  • 特点:

    • 1.锁默认打开,有一个线程进去了,所自动关闭。
    • 2.里面的代码全部执行完毕,线程出来,锁自动打开。
  • 语法:

    • synchronized(锁对象){}
      //锁的对象一定要是惟一的
      //例如:
      static Object Obj = new Object();
      synchronized(Obj){}
      
  • 上述代码加锁结果:

    • 1获取电影票,剩余99张!
      1获取电影票,剩余98张!
      1获取电影票,剩余97张!
      1获取电影票,剩余96张!
      1获取电影票,剩余95张!
      1获取电影票,剩余94张!
      
  • 同步代码块的细节:

    • synchronized(锁对象){}不要写在循环的外面。
    • 锁的对象一定要是唯一的
同步方法
  • synchronized关键字加到方法上。

  • 语法格式:修饰符 synchronized 返回值类型 方法名(方法参数){...}

  • 特点:

      1. 同步方法时锁住方法里面所有的代码
      2. 锁对象不能自己指定
  • 代码:(将之前同步代码块的抽取成一个方法体

    • public class MyThread extends Thread {
          static int tickets = 0;
          private static final int MaxTickets = 100;
          static Object obj = new Object();
      
          @Override
          public void run() {
              while(true) {
                  if (sellTicket()) break;
              }
          }
          private synchronized boolean sellTicket() {
              if (tickets >= 100) {
                  return true;
              } else {
                  tickets++;
                  System.out.println(Thread.currentThread().getName() + "获取电影票,剩余" + tickets + "张!");
              }
              return false;
          }
      }
      
  • 结果

    • 1获取电影票,剩余1张!
      2获取电影票,剩余2张!
      3获取电影票,剩余3张!
      2获取电影票,剩余5张!
      1获取电影票,剩余4张!
      2获取电影票,剩余7张!
      3获取电影票,剩余6张!
      

Lock锁
  • 特点:

    • Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

    • Lock中提供获取锁和释放锁的方法

  • 语法:

    • void lock(); 获取锁
    • void unlock();释放锁
  • 细节:

    • Lock是接口不能直接实例化,需要采用实现类ReentrantLock来实例化
    • ReentrantLock的构造方法
    • ReentrantLock();创建一个ReentrantLock()的实例
    • 检查程序退出前时,是否进行释放锁的操作
  • 代码

    • import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class MyThread extends Thread {
          static int tickets = 0;
          private static final int MaxTickets = 100;
      
          //创建锁对象
          /*
          注意:lock需要用static修饰(指明锁是所有对象共享的)
          * */
          static Lock lock = new ReentrantLock();
      
          @Override
          public void run() {
              while(true) {
                  //上锁
                  lock.lock();
                  if (tickets >= MaxTickets) {
                      //释放锁
                      lock.unlock();
                      break;
                  } else {
                      tickets++;
                      System.out.println(Thread.currentThread().getName() + "获取电影票,剩余" + tickets + "张!");
                  }
                  //释放锁
                  lock.unlock();
              }
          }
      }
      
  • 改进(上述写法需要考虑lock.unlock()的位置,引入try…catch…finally{}改进)

    • finally{}中的代码在程序退出前,一定会执行。

    • public void run() {
              while(true) {
                  //上锁
                  lock.lock();
                  try {
                      if (tickets >= MaxTickets) {
                          break;
                      } else {
                          tickets++;
                          System.out.println(Thread.currentThread().getName() + "获取电影票,剩余" + tickets + "张!");
                      }
                  }finally {
                      lock.unlock();
                  }
              }
          }
      
死锁
  • 死锁(Deadlock)是指在一个多进程(或多线程)的并发环境中,两个或多个进程(或线程)因争夺资源而造成的一种互相等待的现象,使得它们中的每一个都无法继续执行下去,在开发过程中应该避免死锁出现。
等待唤醒机制
常见方法
  • 方法名称说明
    void wait()当前线程等待,直到被其他线程唤醒。
    void notify()随机唤醒单个线程
    void notifyAll()唤醒所有线程
  • 细节:

    • 上述方法在使用的时候需要锁对象调用,例如:lock.wait(),lock.notifyAll(),原因方便系统知道该唤醒哪些线程,而不是唤醒所有的线程。
  • 生产者消费者模型代码

    //消费者
    public class Consumer extends Thread{
        @Override
        public void run() {
            DoConsume();
        }
        //消费者消费逻辑
        public void DoConsume() {
            while(true){
                synchronized (Desk.lock){
                    if(Desk.MaxNum!=0){
                        //有食物就消耗
                        if(Desk.ProduceExit){
                            Desk.MaxNum--;
                            Desk.ProduceExit = false;
                            System.out.println("消费者消费了一份食物!");
                            //通知生产者
                            Desk.lock.notifyAll();
                        }else{
                            try{
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }else{
                        break;
                    }
                }
            }
        }
    }
    //生产者
    public class Producer extends Thread {
        @Override
        public void run() {
            DoProduce();
        }
        //生产者逻辑
        public void DoProduce(){
            while(true){
                synchronized(Desk.lock){
                    if(Desk.MaxNum!=0){
                        //没有食物就生产食物
                        if (!Desk.ProduceExit){
                            Desk.ProduceExit=true;
                            System.out.println("生产者生产一份食物!");
                            //通知消费者
                            Desk.lock.notifyAll();
                        }else{
                            //有食物就进行等待
                            try{
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }else{
                        break;
                    }
                }
            }
        }
    }
    //中间变量Desk
    public class Desk {
        //产品最大数量
        static int MaxNum = 3;
        //是否存在产品
        static boolean ProduceExit = false;
        //创建一个锁对象
        static final Lock lock = new ReentrantLock();
    }
    //Main函数
    public class Main {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            Consumer c = new Consumer();
            Producer p = new Producer();
    
            p.start();
            c.start();
        }
    }
    
    
    
  • 结果

    生产者生产一份食物!
    消费者消费了一份食物!
    生产者生产一份食物!
    消费者消费了一份食物!
    生产者生产一份食物!
    消费者消费了一份食物!
    
等待唤醒机制(阻塞队列方法
  • LinkedBlockingQueue 底层是链表

  • ArrayBlockingQueue底层是数组

  • LinkedBlockingQueue<类型> name = new LinkedBlockingQueue<>(cap);

    • 类型:指定阻塞队列中的数据类型
    • name:阻塞队列的名称
    • cap:指定队列的最大容量
  • 常用方法

  • 方法说明
    void put(参数)向阻塞队列存入数据
    返回值类型 take()向阻塞队列拿取数据
  • 细节:

    • 阻塞队列中,在底层源码已经利用锁控制同步代码块了。所以我们不需要利用锁、同步代码块等操作同步我们的代码。
线程的状态

在这里插入图片描述

  • 32
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我不吃牛肉!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值