多线程基础知识点

多线程基础知识

1、并发与并行

  1. 什么是并发?
    • 并发:指两个或多个事件在同一个时间段内发生(交替执行)。
    • 比如:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发 。
    • 关键:并发的关键是你有处理多个任务的能力,不一定要同时
  2. 什么是并行?
    • 并行:指两个或多个事件在同一时刻发生(同时执行)
    • 比如:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发 。
    • 关键:并发的关键是你有处理多个任务的能力,不一定要同时

2、进程与线程

  1. 什么是进程?
    • 进程:程是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;
    • 进程其实就是应用程序的可执行单元
    • 特点:
      • 每个进程都有一个独立的内存空间
      • 一个应用程序可以有多个进程
  2. 什么是线程?
    • 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
    • 线程是进程的一个可执行单元
    • 特点:
      • 每个线程都有一个独立的内存空间
      • 一个进程可以有多个线程
    • 一个java程序其实就是一个进程,而一个进程一次只能执行一条线程,所有java只有高并发。
  3. 进程与线程的区别
    • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
    • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程的小的多。
  4. 线程调度
    • 分时调度
      • 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
    • 抢占式调度
      • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。---->就是谁抢到谁用。
  5. 扩展
    • Java程序的进程里面至少包含两个线程,一个main()方法线程,另一个就是垃圾回收机制线程。
    • 由于创建一个线程的开销要比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是进程。

3、创建线程的方式

  1. 继承方式
    • 创建一个类继承Thread类
    • 重写Run方法,把线程需要执行的任务代码放在run方法中
    • 创建子类线程对象
    • 调用start方法,启动线程,执行任务
  2. 接口实现的方式
    • 创建一个实现类实现Runnable接口
    • 重写Run方法,把线程需要执行的任务代码放在run方法中
    • 创建实现类对象
    • 创建Thread线程对象,把实现类对象作为参数传入
    • 调用start方法,启动线程,执行任务
  3. 两者的区别:
    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
  4. 接口实现的方式有何优点
    • 适合多个相同的程序代码的线程去共享同一个资源
    • 可以避免java中的单继承的局限性
    • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
    • 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

5.继承实现的方式有何优点

  • 任务对象可以重复使用
  • 任务和线程是分开的
  • 解决单继承的弊端
  • 线池中只能存放实现接口方式的线程对象(Runable,Callable)。

4、线程安全

  1. 安全问题
    电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。

  2. 解决方法: 给线程加锁------如下

/**
 * @author Lee
 */
public class Demo1 {

    public static void main(String[] args) {


        MyRunnable mr = new MyRunnable();

        new Thread(mr, "A").start();
        new Thread(mr, "B").start();
        new Thread(mr, "C").start();
        new Thread(mr, "D").start();
    }
}



/**
 * @author Lee
 */
public class MyRunnable implements Runnable {

    //模拟卖100张票
    int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets < 1) {
                break;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
            tickets--;
        }
    }
}
  • 可以发现一下问题:
+ 卖重复票
+ 漏票
+ 卖负票
  1. synchronized 关键字
    • synchronized关键字:表示同步的,***可以对多行代码进行同步,将多行代码当做一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其他线程才会执行。***这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕。
  • 使用方法:
    • 同步代码块
    • 同步方法(常用)
  1. 同步代码块
    • 同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
    • 格式
    synchronized(同步锁){
    代码块
    

}
```

  • eg:
    public class MyRunnable implements Runnable {
    
    //模拟卖100张票
    int tickets = 100;
    
    @Override
    public void run() {
        while (true) {
           synchronized (this){
               if (tickets < 1) {
                   break;
               }
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
               tickets--;
           }
        }
    }
    
    • 同步锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

      • 锁对象可以是任意的
      • 多个线程对象,要使用同一把锁
    • 注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁谁就进入代码块,其他线程只能在外等待。

  1. 同步方法

    • 同步方法:synchronized关键字修饰的方法,就叫做同步方法。
    • 格式
    	public synchronzied void method(){
    		代码
    	}
    
    1. 同步锁的对象是什么?

      • 对于非static方法,同步锁的对象是this。
      • 对于static方法,同步锁对象是使用当前方法所在类的字节码对象(.Class)
    2. eg

    public class MyRunnable implements Runnable {
      int tickets = 100;// 4个窗口共同卖的票 共享变量
    
      @Override
      public  void run() {
          // 实现卖票的操作
          // 死循环卖票
          while (true) {
              // 当票卖完了,就结束
              // 加锁
              if (sellTickets()) break;
    
              // 释放锁
          }
      }
    
      private  synchronized boolean sellTickets() {
          if (tickets < 1) {
              return true;
          }
          try {
              Thread.sleep(200);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
          tickets--;
          return false;
      }
    }
    
  2. Lock锁

    • Lock锁:也称同步锁,将加锁与释放锁方法化了
    • public void lock(),加同步锁
    • public void unlock(),释放同步锁
    public class MyRunnable implements Runnable {
     int tickets = 100;// 4个窗口共同卖的票 共享变量
     Lock lock = new ReentrantLock();
    
     @Override
     public void run() {
         // 实现卖票的操作
         // 死循环卖票
         while (true) {
             // 当票卖完了,就结束
             // 加锁
             lock.lock();
             if (tickets < 1) {// 窗口1 0
                 lock.unlock();
                 break;// 结束卖票
             }
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
             tickets--;
             // 释放锁
             lock.unlock();
         }
     }
    }
    
    

5、高并发及线程安全

  1. 高并发及线程安全

    • 高并发:是指在某个时间点上,有大量的用户(线程)同时访问同一资源。例如:天猫的双11购物节、12306的在线购票在某个时间点上,都会面临大量用户同时抢购同一件商品/车票的情况。
    • 线程安全:在某个时间点上,当大量用户(线程)访问同一资源时,由于多线程运行机制的原因,可能会导致被访问的资源出现"数据污染"的问题。
  2. 多线程的运行机制:

    • 当一个线程启动后,JVM会为其分配一个独立的"线程栈区",这个线程会在这个独立的栈区中运行。
    • 在这里插入图片描述
  3. 多线程的安全性问题----->可见性

    • 可见性:一个线程没有看见另一个线程对共享变量的修改
    • 为什么没有看见:
      简而言之: 就是所有共享变量都是存在主内存中的,线程在执行的时候,有单独的工作内存,会把共享变量拷贝一份到线程的单独工作内存中,并且对变量所有的操作,都是在单独的工作内存中完成的,不会直接读写主内存中的变量值
    • 演示
    	public class Demo4 {
    	
    	public static void main(String[] args) {
    	     /*
    	        多线程的安全性问题-可见性:
    	            一个线程没有看见另一个线程对共享变量的修改
    	     */
    	    // 创建子线程并启动
    	    MyThread myThread = new MyThread();
    	    myThread.start();
    	
    	    while (true) {
    	        if (MyThread.flag == true) {
    	            System.out.println("死循环结束");
    	            break;
    	        }
    	    }
    	}
    	}
    	
    	
    	public class MyThread extends Thread {
    	static boolean flag = false;
    	
    	@Override
    	public void run() {
    	    try {
    	        Thread.sleep(1000);
    	    } catch (InterruptedException e) {
    	        e.printStackTrace();
    	    }
    	
    	    flag = true;
    	    System.out.println("修改后的flag的值为:"+flag);
    	}
    	}
    
    1. 会发现,一直处于死循环之中。
    2. 如何解决
      • 在共享的成员变量上加上volatile关键字。
	public class MyThread extends Thread {
    volatile static boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag = true;
        System.out.println("修改后的flag的值为:"+flag);
    }
}
  1. 多线程的安全性问题----->有序性

    • 有些时候“编译器”在编译代码时,会对代码进行“重排”,例如:
    	int a = 10;     //1
    	
    	int b = 20;     //2
    	
    	int c = a + b;   //3
    
    

    第一行和第二行可能会被“重排”:可能先编译第二行,再编译第一行,总之在执行第三行之前,会将1,2编译完毕。1和2先编译谁,不影响第三行的结果。

    1. 但在“多线程”情况下,代码重排,可能会对另一个线程访问的结果产生影响:
      在这里插入图片描述
    2. 解决方式:在成员变量上加上volatile关键字
  2. 多线程的安全性问题------>原子性

    • 概述:所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。
    • 请看以下示例:
      一条子线程和一条主线程都对共享变量a进行++操作,每条线程对a++操作100000次
    public class Demo5 {
    
    public static void main(String[] args) {
        new Mythread().start();
    
        for (int i = 0; i < 1000000; i++) {
            Mythread.a++;
        }
    
        try {
            Thread.sleep(50000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        System.out.println("最终a的值为:" +Mythread.a);
    }
    }
    
    
    public class Mythread extends Thread {
    public static int a = 0;
    
    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            a++;
        }
        System.out.println("子线程已加完");
    }
    }
    
    1. 发现最后值没有200000,因为有些自加的操作被覆盖掉了
    2. 如何解决:
  • 使用AtomicInteger(详见原子类)

6、volatile关键字

  1. volatile关键字:volatile是一个变量修饰符,它只能修饰成员变量,它能强制线程每次从内存中获取值,并能保证此变量不会被编译器优化。
  2. volatile关键字能解决线程的可见性、有序性。
    • 可见性:当变量被volatile关键字修饰时,会迫使线程每次使用此变量时,都会去内存获取,保证其可见性
    • 有序性:当变量被volatile关键字修饰时,会禁止代码重排。
  3. volatile关键字不能解决线程的原子性。
    volatile关键字,只能解决变量的可见性、有序性问题,并不能解决原子性问题。

7、原子类

  1. AtomicInteger类
    • 在java.util.concurrent.atomic包下定义了一些对“变量”操作的“原子类”:
      1).java.util.concurrent.atomic.AtomicInteger:对int变量操作的“原子类”;
      2).java.util.concurrent.atomic.AtomicLong:对long变量操作的“原子类”;
      3).java.util.concurrent.atomic.AtomicBoolean:对boolean变量操作的“原子类”;
      它们可以保证对“变量”操作的:原子性、有序性、可见性。
    • eg
    /**
    * 解决原子性问题
    */
    public class Demo6 {
    
    public static void main(String[] args) {
    new Mythread().start();
    
    for (int i = 0; i < 100000; i++) {
       Mythread.a.getAndIncrement();
    }
    
    try {
       Thread.sleep(5000);
    } catch (InterruptedException e) {
       e.printStackTrace();
    }
    
    System.out.println("最终a的值为:" + Mythread.a);
    }
    }
    
    
    public class Mythread extends Thread {
    //    public static int a = 0;
    static AtomicInteger a = new AtomicInteger();//共享变量
    @Override
    public void run() {
    for (int i = 0; i < 100000; i++) {
    //            a++;
       a.getAndIncrement();//自加
    }
    System.out.println("子线程已加完");
    }
    }
    
  2. AtomicInteger类值的执行原理 —CAS机制
    • 执行原理
      • 1 当执行子线程(主线程)时,子线程(主线程)会从主内存中拷贝共享变量的值
      • 2 拿到从主内存中的获取的值(开始拷贝的) 与主内存中的值进行比较,
      • 3 如果值相等(也就是返回的值为true)便进行对应的操作
      • 4 如果值不相等(也就是返回的false),则重新从主内存中获取值,并进行比较,如果相等则进行相应的操作,不相等便重复以上的步骤。
  3. AtomicIntegerArray类示例
    • 上码:
/**
 * AtomicIntergerArray演示
 */
public class Demo7 {
    public static void main(String[] args) {

        for (int i = 0; i < 10000; i++) {
            new MyThread().start();
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < MyThread.integerArray.length(); i++) {
            System.out.print(MyThread.integerArray.get(i)+" ");
        }
    }
}

public class MyThread extends Thread {
    static AtomicIntegerArray integerArray = new AtomicIntegerArray(1000);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            integerArray.getAndAdd(i, 1);
        }

        System.out.println("子线程执行完毕!!!");

    }
}
  1. 底层源码
    在这里插入图片描述

8、并发包

  1. CopyOnWriteArrayList

    • ArrayList线程不安全
    • CopyOnWriteArrayList线程安全
  2. CopyOnWriteArraySet

    • HashSet线程不安全
    • CopyOnWriteArraySet线程安全
  3. ConcurrentHashMap

    • HashMap线程不安全
    • Hashtable线程安全
      • 效率相对于ConcurrentHashMap低
      • 效率低的原因
        • 使用了大量的synchronized来修饰
        • HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。
    • ConcurrentHashMap线程安全
      • 效率高的原因:
        +CAS + 局部(synchronized)锁定
  4. CountDownLatch

    • CountDownLatch:允许一个或多个线程等待其他线程完成操作。
    • CountDownLatch构造方法:
      • public CountDownLatch(int count):初始化一个指定计数器的CountDownLatch对象。
    • CountDownLatch方法:
      • public void await()throws InterruptedException:让当前线程等待,当计数器的值为0的时候,就结束等待
      • public void countDown():计数器进行减1
public class Demo11 {
    public static void main(String[] args) {

        CountDownLatch latch = new CountDownLatch(1);
        new MyThread1(latch).start();//创建线程1
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new MyThread2(latch).start();//线程2
    }
}


public class MyThread1 extends Thread {

    CountDownLatch cdl;

    public MyThread1(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        System.out.println("A");
        try {
            cdl.await();//等待,为0是在走
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}


public class MyThread2 extends Thread {

    CountDownLatch cdl;

    public MyThread2(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        System.out.println("B");
        cdl.countDown();//减一
    }
}
  1. CyclicBarrier
    • CyclicBarrier:是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。就是等所有到齐了才进行下一步
    • CyclicBarrier构造方法:
    public CyclicBarrier(int parties, Runnable barrierAction
    //parties: 代表要达到屏障的线程数量
    //barrierAction:表示达到屏障后要执行的线程
    
    1. CyclicBarrier方法:
    	public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
    
    public class Demo12 {
    
       public static void main(String[] args) {
           CyclicBarrier cb = new CyclicBarrier(5, new Runnable() {
               @Override
               public void run() {
                   System.out.println("begin!");
               }
           });
    
           MyRunnable mrb = new MyRunnable(cb);
           new Thread(mrb, "A").start();
           new Thread(mrb, "B").start();
           new Thread(mrb, "C").start();
           new Thread(mrb, "D").start();
           new Thread(mrb, "E").start();
       }
    }
    
    public class MyRunnable implements Runnable{
    
       CyclicBarrier cb;
    
       public MyRunnable(CyclicBarrier cb) {
           this.cb = cb;
       }
    
       @Override
       public void run() {
           System.out.println(Thread.currentThread().getName() +"到了");
           try {
               cb.await();//z阻塞
           } catch (InterruptedException e) {
               e.printStackTrace();
           } catch (BrokenBarrierException e) {
               e.printStackTrace();
           }
    
           System.out.println(Thread.currentThread().getName() + "离开");
       }
    }
    
  2. Semaphore
    • Semaphore:主要作用是控制线程的并发数量,可以设置同时允许几个线程执行
    • 重要方法
    	public Semaphore(int permits)						permits 表示许可线程的数量
    	public void acquire()                            	表示获取许可
    	public void release()								表示释放许可
    
    1. 码–同学上课
public class Demo13 {
    public static void main(String[] args) {

        ClassRoom cr = new ClassRoom();
        MyRunnabel rb = new MyRunnabel(cr);

        new Thread(rb,"同学1").start();
        new Thread(rb,"同学2").start();
        new Thread(rb,"同学3").start();
        new Thread(rb,"同学4").start();
        new Thread(rb,"同学5").start();
        new Thread(rb,"同学6").start();
        new Thread(rb,"同学7").start();
        new Thread(rb,"同学8").start();
        new Thread(rb,"同学9").start();
        new Thread(rb,"同学10").start();
        new Thread(rb,"同学11").start();
        new Thread(rb,"同学12").start();
        new Thread(rb,"同学13").start();
        new Thread(rb,"同学14").start();
        new Thread(rb,"同学15").start();
        new Thread(rb,"同学16").start();
        new Thread(rb,"同学17").start();
        new Thread(rb,"同学18").start();
        new Thread(rb,"同学19").start();
        new Thread(rb,"同学20").start();

    }
}


public class ClassRoom {

    Semaphore sh = new Semaphore(5);

    public void into() {
        try {
            sh.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + ":进入了教室");

        try {
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + ":离开了教室");
        sh.release();
    }
}


public class MyRunnabel implements Runnable{

    ClassRoom cr;

    public MyRunnabel(ClassRoom cr) {
        this.cr = cr;
    }

    @Override
    public void run() {
        cr.into();
    }
}
  1. Exchanger
    • Exchanger:是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
      • 这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方
    • API: public Exchanger()
      public V exchange(V x) 参数: 要交换的数据 返回值: 对方线程传递的数据
    1. 码–互传数据
    public class Demo14 {
       public static void main(String[] args) {
           Exchanger<String> exchanger = new Exchanger<>();
    
           new MyThreadA(exchanger).start();
           new MyThreadB(exchanger).start();
       }
    }
    
    
    public class MyThreadA extends Thread{
    
       Exchanger<String> ex;
    
       public MyThreadA(Exchanger<String> ex) {
           this.ex = ex;
       }
    
       @Override
       public void run() {
           System.out.println("准备传数据给B------");
           try {
               String messB = ex.exchange("我是从线程A来的数据A");
               System.out.println("B给我的数据是:" + messB);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
    
    public class MyThreadB extends Thread{
    
       Exchanger<String> ex;
    
       public MyThreadB(Exchanger<String> ex) {
           this.ex = ex;
       }
    
       @Override
       public void run() {
           System.out.println("准备传数据给A------");
           try {
               String messA = ex.exchange("我是从线程B来的数据B");
               System.out.println("A给我的数据是:" + messA);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
    

9、线程池

  1. 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源----->线程池是一个存储多条线程的容器,该线程池中的线程是可以重复利用的

  2. 线程池的好处:

    • 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
    • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
    • 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
  3. 线程池

        真正的线程池接口是java.util.concurrent.ExecutorService。
    java.util.concurrent.Executors线程工厂类提供了生成一些常用的线程池的方法。
    Executors线程工厂类:
       public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。
                        (创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
    ExecutorService线程池接口:
       public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行任务
       public Future<?> submit(Callable task):获取线程池中的某一个线程对象,并执行任务
    Future接口:用来记录线程任务执行完毕后产生的结果。  get()方法,可以获取任务执行完后返回的结果
    
  4. 线程池工作原理:

    • 线程池被创建,就会初始化指定数量的线程
    • 往线程池中添加任务
      • 如果线程池中有多条空闲的线程,那么就会随机分配线程来执行任务
      • 如果线程池中没又空闲的线程,那么添加的任务就在任务队列中进行等待,当有线程执行完任务,空闲下来,就会分配空闲线程来执行等待的任务。
  5. 线程池的使用

    • Runnable:
    • 步骤:
    1. 创建线程池对象。
    2. 创建Runnable接口子类对象。(task)
    3. 提交Runnable接口子类对象。(take task)
    4. 关闭线程池(一般不做)。
    
    1. 上码:
    public class Demo15 {
    
       public static void main(String[] args) {
           //1、创建线程池
           ExecutorService executorService = Executors.newFixedThreadPool(3);
    
           //2、提交事务,并执行任务
           MyRunnable mr = new MyRunnable();
           executorService.submit(mr);
    
           //3、销毁线程池(一般不操作)
           executorService.shutdown();
       }
    }
    
    	public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("开始: 实现Runnable接口的任务...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束:实现Runnable接口的任务...");
        }
    }
    
    1. Callable:
    • 步骤:----------->采用callable方式有返回结果
    	xxxxxxxxxx3 1
    	//1、创建线程池
    	//2、提交事务,并执行任务
    	//3、销毁线程池
    
    1. 上码
    	public class Demo15_2 {
    
        public static void main(String[] args) {
            //1、创建线程池
            ExecutorService executorService = Executors.newFixedThreadPool(3);
    
            //2、提交事务,并执行任务
            MyCallable mc = new MyCallable();
    
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
            executorService.submit(mc);
    
            //3、销毁线程池
        }
    }
    
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("开始: 实现Callable接口的任务...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束:实现Callable接口的任务...");
        return "LeeXM";
    }
}

10、死锁

  1. 什么是死锁?
    在多线程程序中,使用了多把锁,造成线程之间相互等待.程序不往下走了。
  2. 产生死锁的条件
    • 有多把锁
    • 有多个线程
    • 有同步代码块嵌套

11、线程的状态

  1. 线程的六种状态
线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程对象,没有线程特征。创建线程对象时
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典教法)。调用start方法时
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。等待锁对象时
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。**调用wait()方法时 **
NTimed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。**调用sleep()方法时 **
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。**run方法执行结束时,或者执行任务的时候出现了异常,但没有try处理 **
  1. 线程状态的切换
    在这里插入图片描述

  2. 小结

    • 线程状态
      • 新建 :创建线程对象
      • 可运行:调用start()方法
      • 锁阻塞:等待锁对象时
      • 无限等待:使用锁对象调用wait()方法进入无限等待,直到被其他线程唤醒
      • 计时等待:调用Thread类的sleep()方法,或者调用wait(long timeout)方法
      • 被终止:run方法执行完毕,或者run方法执行期间出现异常,而没有捕获处理造成非正常结束线程
    • 线程状态的切换------>结合上图
      • 调用wait方法和调用notify方法的锁对象要一致
      • 使用锁对象调用wait方法,进入无限等待
      • 使用锁对象调用notify方法唤醒对应的无限等待线程
      • 调用sleep()方法进入计时等待,那么该线程就不会霸占CPU资源
      • 调用wait()方法进入无限等待,那么该线程就不会霸占CPU资源,也不会霸占锁对象。

12、等待与唤醒机制

  1. 等待唤醒机制:

    • 这是多个线程间的一种协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
      就是在一个线程进行了规定操作后,就进入无限等待状态(wait()),调用notfiy()方法唤醒其他线程来执行,其他线程执行完后,进入无限等待,唤醒等待线程执行,依次类推… 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
    • wait()进入无限等待,采用notify()唤醒其他线程
  2. 等待唤醒机制的相关方法介绍

    • public void wait():让当前线程进入到等待状态,此对象必须锁对象调用
    • public void notify():唤醒当前锁对象上等待状态的线程,此方法必须锁对象调用
  3. 来一个简单的案例演示一下:

    /**
     * 等待唤醒机制的小demo
     * @param args
     */
    public static void main(String[] args) {
        Baozi bz = new Baozi();

        new BaoZiPu(bz).start();
        new ChiHuo(bz).start();
    }
}
public class Baozi {

    boolean flag = false;//默认为false,没有包子
    String xianer ;//馅儿
}

public class ChiHuo extends Thread{
    Baozi bz;

    public ChiHuo(Baozi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true){
            synchronized (bz) {
                //判断有没有包子
                if (bz.flag == true) {
                    //有包子吃
                    System.out.println("正在吃"+bz.xianer+"包子");
                    bz.flag = false;//吃完变为false
                    bz.notify();
                    System.out.println("吃完了");
                }

                if (bz.flag == false) {
                    System.out.println("没有包子,等待做");
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }
}
public class BaoZiPu extends Thread {
    Baozi bz;

    public BaoZiPu(Baozi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        //判断有没有包子
        while (true) {
            synchronized (bz) {
                if (bz.flag == true) {
                    //有包子,进入无限等待
                    System.out.println("包子铺线程:由于有包子,所以准备进入无限等待");
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没有包子
                if (bz.flag == false) {
                    bz.xianer = "鲜肉";
                    bz.flag = true;
                    bz.notify();//唤醒吃货线程
                    System.out.println("包子铺线程: 包子包好了,快来吃包子");
                }
            }
        }
    }
}
  1. 小结
    • 等待与唤醒机制
      • 使用锁对象调用wait()方法进入无限等待
      • 使用锁对象调用notify或者notifyAll()方法唤醒对应的无限等待的线程
      • 调用wait()方法和调用notify()方法的锁对象要一致
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值