17 多线程

lix_uan
• 阅读 718

并发与并行

  • 并行
    • 多个事件同一时刻发生
  • 并发
    • 多个事件在同一时间段内发生

线程与进程

  • 进程
    • 系统运行程序的基本单位
    • 每个进程都有一个独立的内存空间
    • 是系统调度和资源分配的最小单位
  • 线程
    • 进程中的一个执行单元
    • 一个进程可以有多个线程
    • 共享进程的内存
    • 是CPU调度的最小单位

创建线程和启动线程

继承Thread类

public class MyThread extends Thread {
    //定义指定线程名称的构造方法
    public MyThread(String name) {
        //调用父类的String参数的构造方法,指定线程的名称
        super(name);
    }
    /**
     * 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+":正在执行!"+i);
        }
    }
}
public class Demo01 {
    public static void main(String[] args) {
        //创建自定义线程对象
        MyThread mt = new MyThread("新的线程!");
        //开启新线程
        mt.start();
        //在主方法中执行for循环
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程!"+i);
        }
    }
}

实现Runnable接口

  public class MyRunnable implements Runnable{
      @Override  
      public void run() {
          for (int i = 0; i < 20; i++) {
              System.out.println(Thread.currentThread().getName()+" "+i);         
          }       
      }    
  }
  public class Demo {
      public static void main(String[] args) {
          //创建自定义类对象  线程任务对象
          MyRunnable mr = new MyRunnable();
          //创建线程对象
          Thread t = new Thread(mr, "小强");
          t.start();
          for (int i = 0; i < 20; i++) {
              System.out.println("旺财 " + i);
          }
      }
  }

使用匿名内部类对象来实现线程的创建和启动

new Thread("新的线程!"){
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+":正在执行!"+i);
        }
    }
}.start();
new Thread(new Runnable(){
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":" + i);
        }
    }
}).start();

Thread类

常用方法

  • public void run() :此线程要执行的任务在此处定义代码
  • public String getName() :获取当前线程名称
  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用
  • public final void setPriority(int newPriority):改变线程的优先级(10最高、1最低、5普通)
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
  • void join() :等待该线程终止
  • void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待

示例

import java.util.Scanner;

public class TestJoin {
    public static void main(String[] args) {
        ChatThread t = new ChatThread();
        t.start();

        for (int i = 1; i < 10; i++) {
            System.out.println("main:" + i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
         //当main打印到5之后,需要等join进来的线程停止后才会继续了。
            if(i==5){
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class ChatThread extends Thread{
    public void run(){
        Scanner input = new Scanner(System.in);
        while(true){
            System.out.println("是否结束?(Y、N)");
            char confirm = input.next().charAt(0);
            if(confirm == 'Y' || confirm == 'y'){
                break;
            }
        }
        input.close();
    }
}

守护线程

  • 为其他线程服务,如果所有线程都死亡,那么守护线程自动死亡

  • JVM的垃圾回收线程就是典型的守护线程

  • 调用setDaemon(true)方法可将指定线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常

    public class TestThread {
        public static void main(String[] args) {
            MyDaemon m = new MyDaemon();
            m.setDaemon(true);
            m.start();
    
            for (int i = 1; i <= 100; i++) {
                System.out.println("main:" + i);
            }
        }
    }
    
    class MyDaemon extends Thread {
        public void run() {
            while (true) {
                System.out.println("我一直守护者你...");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

线程安全问题

卖票存在的问题

  • 某张票被卖了两回
  • 不存在的票,比如0票-1票

解决

  • 同步代码块
  • 同步方法
  • 锁机制

同步代码块

class Ticket extends Thread{
    private static int ticket = 100;

    public Ticket() {
        super();
    }

    public Ticket(String name) {
        super(name);
    }

    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        // 每个窗口卖票的操作
        // 窗口永远开启
        while (true) {
            synchronized (Ticket.class) {//这里不能选用this作为锁,因为这几个线程的this不是同一个
                if (ticket > 0) { // 有票可以卖
                    // 出票操作
                    // 使用sleep模拟一下出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 获取当前线程对象的名字
                    System.out.println(getName() + "正在卖:" + ticket--);
                }
            }
        }
    }
}

同步方法

格式

public synchronized void method(){
    可能会产生线程安全问题的代码
}

同步方法的锁对象

  • 静态方法:当前类的Class对象
  • 非静态方法:this

线程间通信

等待唤醒机制

  • 在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程

wait 和 notify 方法需要注意的细节

  • wait 和 notify 方法必须要由同一个锁对象调用

  • wait 和 notify 方法是属于Object方法的

  • wait 和 notify 方法必须要在同步代码块或者是同步函数中使用

生产者消费者问题

包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
public class BaoZi {
     String  pier ;
     String  xianer ;
     boolean  flag = false ;//包子资源 是否准备好  包子资源状态
}
public class ChiHuo extends Thread{
    private BaoZi bz;

    public ChiHuo(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag == false){//没包子
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }                

                System.out.println("吃货正在吃:"+bz.pier+","+bz.xianer+"包子");
                bz.flag = false;
                bz.notify();
            }
        }
    }
}
public class BaoZiPu extends Thread {

    private BaoZi bz;

    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        //造包子
        while(true){
            //同步
            synchronized (bz){
                if(bz.flag == true){//包子资源  存在
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 没有包子  造包子
                System.out.println("包子铺开始做包子");
                if(count%2 == 0){
                    // 薄皮  蟹黄包
                    bz.pier = "薄皮";
                    bz.xianer = "蟹黄灌汤";
                }else{
                    // 厚皮  牛肉大葱
                    bz.pier = "厚皮";
                    bz.xianer = "牛肉大葱";
                }
                count++;

                bz.flag=true;
                System.out.println("包子造好了:"+bz.pier+bz.xianer);
                System.out.println("吃货来吃吧");
                //唤醒等待线程 (吃货)
                bz.notify();
            }
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        //等待唤醒案例
        BaoZi bz = new BaoZi();

        ChiHuo ch = new ChiHuo("吃货",bz);
        BaoZiPu bzp = new BaoZiPu("包子铺",bz);

        ch.start();
        bzp.start();
    }
}

线程的生命周期

17 多线程

Thread 和 Runnable的区别

  • 类只能是单继承,继承了Thread类就不能继承别的类了,而实现Runnable接口之后还可以继承别的类
  • 线程池中只能放入实现Runnable或Callable类的线程,不能直接放入继承Thread的类

释放锁操作与死锁

会释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束
  • 方法中遇到break,return终止了方法的继续执行
  • 遇到了未处理的Error或Exception,导致线程异常结束
  • 执行了wait()方法,导致线程被挂起并释放锁

不会释放锁的操作

  • 程序调用Thread.sleep()、Thread.yield()

死锁

  • 不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁

sleep() 和 wait() 的区别

  • sleep()不释放锁,wait()释放锁

  • sleep()指定休眠时间,wait()可以指定时间也可等待直到notify

  • sleep()是在Thread类中声明的静态方法,wait方法在Object类中声明

评论区
推荐文章

暂无数据

lix_uan
lix_uan
Lv1
学无止境,即刻前行
7
文章
3
粉丝
0
获赞