13、多线程

91 阅读6分钟

一、概念

进程和线程的概念:

进程:正在运行的程序

线程:是进程中的一个执行单元(执行路径)

一个进程至少包含一条线程,叫主线程。

在Java中,主函数所在的线程就是主线程.还有垃圾回收所在的线程(守护线程)

并发和并行:

并发:某个时间段多个任务同时执行

并行:同时发生

多线程的执行:

  1. 分时调度:任何进程轮流获取CPU的执行权,多个任务交替执行
  2. 抢占式调度:多个进程随机的获取CPU的执行权(Java中的多线程)

二、线程的创建

方式一:继承Thread

  1. 定义类继承Thread
  2. 重写run()方法,run()方法中的功能就是需要使用线程并发执行的功能
  3. 创建子类对象
  4. 调用start()方法启动线程

注:如果一个线程对象多次调用start()方法,会发生IllegalThreadStateExcetpion

方式二:实现Runnable

  1. 定义类实现Runnable
  2. 重写run()方法,run()方法中的功能就是需要使用线程并发执行的功能
  3. 创建实现类对象
  4. 将这个实现类对象作为Thread构造函数的参数,创建出Thread对象
  5. 调用Thread的start()方法启动线程

使用匿名内部类创建线程并开启:

// 继承Thread
new Thread(){
 @Override
 public void run() {
     //...
 }
}.start();
​
// 实现Runnable
new Thread(new Runnable(){
 @Override
 public void run() {
    //...
 }
}).start();

三、线程安全问题

什么是线程安全的:使用多线程并发执行多个任务最终结果和单线程的效果相同就是线程安全的

线程不安全的原因:多个线程操作同一个数据

解决办法:同步技术,保证当某一条线程获取到CPU执行权在执行功能,其他线程要处于等待状态

关键字:synchronized同步的

同步代码块:将可能发生线程安全问题的代码放在同步代码块中

synchronized(obj){
 // 可能发生线程安全问题的代码
}
// obj可以是任意类型的对象,必须保证该对象唯一

同步函数:将可能发生线程安全问题的代码封装一个同步函数中

public synchronized void 方法名(){
 // 可能发生线程安全问题的代码
}

注:

  1. 同步函数中也有锁对象,锁对象是本类对象this
  2. 使用继承的方式创建线程,不能使用同步函数,因为同步函数的对象是this,而本类对象不唯一;此时要使用静态同步函数,静态同步函数的锁对象是类名.class

同步的原理:

  1. 抢夺到CPU执行权的线程会先判断锁,判断是否有锁对象可以获取
  2. 如果有锁对象就获取锁对象进行同步代码块/同步函数,如果没有锁对象就处于阻塞状态,等待其他线程归还锁
  3. 获取到锁对象的线程进入同步代码块/同步函数中,此时其他线程处于阻塞状态
  4. 当抢夺到CPU执行权的线程执行完功能后会归还锁

四、线程终止

线程终止的方式:

  1. 当线程执行完任务后,自动终止
  2. 通过使用成员变量来控制run()方法退出
class MyRunnable implements Runnable{
​
    private boolean isRun = true;
​
    @Override
    public void run() {
        for(int i = 1;i <= 10000000;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(!isRun){
                break;
            }
            System.out.println(Thread.currentThread()+":"+i);
        }
    }
​
    public boolean isRun() {
        return isRun;
    }
​
    public void setRun(boolean run) {
        isRun = run;
    }
}
public class TestStopThread {
    public static void main(String[] args) {
​
        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        t.start();
​
        for(;;){
            String s = new Scanner(System.in).next();
            if(s.equals("2")){
                mr.setRun(false);
                break;
            }
        }
    }
}

五、线程池

概念:存储线程对象的容器

好处:

  1. 减少了线程对象的频繁创建和销毁,提高了程序的性能
  2. 提高线程对象复用性,节省了资源
  3. 提高了对线程对象的管理

常用线程池相关的接口和类:

Executor:线程池的顶层接口

ExecutorService:线程池的接口,提供了执行线程功能的方法submit(Runnable r)

Executors:线程池的工厂类,提供了获取线程池的方法

1. newFixedThreadPool(int n):获取具有指定数量个线程对象的线程池,有归还的线程会先用归还的线程,并且优先使用后归还的线程
​
2. newCachedThreadPool():获取线程池,线程对象可以是任意多个

只要从线程池中获取过了线程对象,哪怕线程功能都已经执行完毕,线程对象都已经归还到池中,这个线程池仍旧处于可以使用的状态,只有通过调用线程池的shutdown()方法才可以关闭线程池,关闭了的线程池再调用submit()方法会发生RejectedExecutionException

六、线程的常用方法

1.setName(String name):设置线程名
2.getName():获取线程名
3.start():启动线程
4.run():线程功能重写的方法
5.setPriority(int p):设置优先级,优先级范围[1,10]
6.getPriority():获取优先级
7.sleep(long time):休眠
8.interrupt():中断线程的休眠状态
9.yield():线程礼让,让出CPU的执行权,但是礼让不一定成功
10.join():线程加入
11.currentThread():它是一个静态方法,返回当前线程对象的信息,包含:线程名、线程优先级、线程创建的位置

七、锁

  1. 锁对象也称为互斥锁,为了保证操作的共享数据的完整性
  2. 互斥锁必须唯一,任一时刻只有一个线程对象可以访问锁对象
  3. synchronized中的锁对象就是互斥锁,它是非公平锁,非公平锁的效率要高于公平锁
  4. 同步的问题:会导致程序的效率降低
  5. 同步函数的锁是this
  6. 静态同步函数的锁是类名.class

死锁:多个线程中都占用了其他的锁对象,都不肯释放,就会导致死锁。

public class Test {
​
public static void main(String[] args) throws InterruptedException {
  MT mt1 = new MT(true);
  MT mt2 = new MT(false);
​
  mt1.start();
  mt2.start();
}
​
}
​
class MT extends Thread{
​
static Object o1 = new Object();
static Object o2 = new Object();
​
boolean flag;
​
public MT(boolean flag) {
  this.flag = flag;
}
​
@Override
public void run() {
  while(true){
      if(flag){
          synchronized (o1){// mt1线程获取了o1锁对象
              System.out.println(Thread.currentThread()+":进入了1号同步代码块");
              synchronized (o2){
                  System.out.println(Thread.currentThread()+":进入了2号同步代码块");
              }
          }
      }else{
          synchronized (o2){// mt2线程获取o2锁对象
              System.out.println(Thread.currentThread()+":进入了3号同步代码块");
              synchronized (o1){
                  System.out.println(Thread.currentThread()+":进入了4号同步代码块");
              }
          }
      }
  }
}
}

八、线程的生命周期

在Thread.State中可以查看到线程的状态,有6个:

1.NEW:刚刚创建完毕,没有调用的状态
2.RUNNABLE: 正在JVM中执行的状态
3.BLOCKED: 受阻塞的状态,没有拿到CPU执行权,或没有拿到锁的状态
4.WAITING: 无限等待状态,
5.TIMED_WAITING: 有限的等待
6.TERMINATED: 已退出的状态