# Java 基础 # 多线程

245 阅读5分钟

进程与线程

  • 进程
    • 程序的实例(一般一个程序只有一个实例)
  • 线程
    • 最小的可执行单元
    • 一个进程可以有多个线程(进程必须要有一个线程)

进程与线程的一个简单解释

线程的创建方式

  1. 继承 Thread 类
    • 如果直接调用 run() , 那么和调用普通的方法一样,会挂起当前方法等待 run() 执行完成
    class ThreadDemo extends Thread {
        // 无参构造器
        ThreadDemo(){ }
        
        public void run(){
            // 自定义的代码
        }
    }
    
    public statin void main(String[] args){
        
        // 调用无参构造器来创建线程对象
        ThreadDemo td = new ThreadDemo();
        
        /* 
            开辟新线程执行和调用 run() 方法有区别 
            run() 方法会将当前线程压栈,等待 run() 方法执行完成后,再回到当前方法
        */
        td.start();
    }
    
  2. 实现 Runnable 接口
    • 三种方式,本质上都是 Runnable 的实现
    class RunDemo implements Runnable {
        RunDemo(){}
        
        // 实现接口的 run() 方法
        public void run(){
            // 自定义的代码
        }
    }

    public static void main(String[] args){
        
        Runnable runDemo = new RunDemo();
        
        // 调用 Thread 类的有参构造器并传入线程接口的一个实现来创建线程,并启动线程
        new Thread(runDemo).start();
    }
  1. 实现 Callable 接口
    • Callable 需要被包装成 Future

    • Future 需要被包装成 FutureTask

    • FutureTask 事实上实现了 RunnableFuture 接口

    • RunnableFuture 接口是 Runnable 和 Future 的组合接口

    • 所以,Callable 本质上还是 Runnable

    • 可返回值的线程创建方式 3.1 Callable + Future

          public class CallAndFuture implements Callable{
      
       private String name;
      
       public CallAndFuture(String name ){
           this.name = name;
       }
      
      
       public String call(){
      
      
           return "11111133333" + this.name;
       }
      
      
       public static void main (String[] args) throws ExecutionException, InterruptedException {
           /* code */
      
           CallAndFuture callOne = new CallAndFuture("结果one");
           CallAndFuture callTwo = new CallAndFuture("结果two");
           CallAndFuture callThree = new CallAndFuture("结果three");
      
           ExecutorService es = Executors.newFixedThreadPool(3);
      
           Future futureOne = es.submit(callOne);
      
           System.out.println(futureOne.get());
      
           Future futureTwo = es.submit(callTwo);
      
           System.out.println(futureTwo.get());
      
           Future futureThree = es.submit(callThree);
      
           System.out.println(futureThree.get());
      
           es.shutdown();
       }
      
    3.2 Callable + FutureTask
    
  • get() 方法会阻塞主线程,一般使用带有超时时间的 get(long timeout, TimeUnit unit)

  • 一般在其它任务完成后,再去获取线程执行结果; 如果后续结果依赖线程执行的结果话,可以阻塞等待(但为什么要这么做呢?

线程的状态

  1. 新建(NEW)
  2. 运行(RUNNABLE)
    • 就绪
    • 运行中
  3. 等待(WAITING)
    • 等待其它线程的动作(通知或中断)
  4. 阻塞(BLOCKED)
    • 因锁阻塞
  5. 超时等待(TIMED_WAITING)
    • 有结束时间的等待
    • 过了指定时间后自行回到就绪状态排队
    • sleep() 方法会引发这个状态
  6. 死亡(TERMINATED)
    • 执行完毕

线程的最终执行方法

  • native void start0()
  • start0() 方法由静态方法 static native void registerNatives() 准备初始化工作
  • 所以,本质上 java 是无法开启线程的,而是交由底层 C++ 实现的方法 start0 开启

线程的常用方法

  1. wait
    • 调用本方法让线程进入等待状态,会释放锁
  2. join
    • 线程强制执行,俗称插队。谁调用了,谁就会被阻塞
  3. notify
    • 唤醒等待队列中的一个线程,来竞争 cpu
  4. notifyAll
    • 唤醒等待队列中的所有线程,来竞争 cpu
  5. stop(不建议使用)
  6. destroy(不建议使用)
  7. yield
    • 线程礼让。先退出 cpu ,重新和其它线程竞争。
    • 可能再次竞争到 cpu , 完全是看 cpu 的心情。
  8. sleep
    • 调用本方法阻塞线程,不会释放锁

线程的同步机制

  • synchronized
    • 可以锁住方法
      • public synchronized void test(){}
    • 可以锁住代码块
      • synchronized(obj){}
  • Lock 接口
    • 可以锁住代码块
    • ReentrantLock 锁
        ReentrantLock lock = new ReentrantLock();
        try{
            // 加锁
            lock.lock();
        }finally{
            // 解锁
            lock.unlock();
        }
    
    • LockSupport
    class FIFOMutex {
    
        private final AtomicBoolean locked = new AtomicBoolean(false);
        private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
     
        public void lock() {
              boolean wasInterrupted = false;
              Thread current = Thread.currentThread();
              waiters.add(current);
     
          // Block while not first in queue or cannot acquire lock
              while (waiters.peek() != current ||
                     !locked.compareAndSet(false, true)) {
                LockSupport.park(this);
                if (Thread.interrupted()) // ignore interrupts while waiting
                  wasInterrupted = true;
              }
     
              waiters.remove();
          if (wasInterrupted)          // reassert interrupt status on exit
                current.interrupt();
        }
     
        public void unlock() {
          locked.set(false);
          LockSupport.unpark(waiters.peek());
        }
      }}
    
    • ReentrantReadWriteLock 可重入的读写锁
    public class ReadWriteLockDemo {
    
        public static void main(String[] args) {
            Note note = new Note();
    
            for (int i = 0; i < 100; i++) {
                if( i%10 == 0){
                    new Thread(new TakeNote(note)).start();
                } else {
                    new Thread(new ViewNote(note)).start();
                }
            }
        }
    }
    
    
    class TakeNote implements Runnable{
    
        private Note note = null;
    
        public TakeNote(Note n){
            this.note = n;
        }
    
        @Override
        public void run() {
            try {
    
                this.note.add("====" + Thread.currentThread().getName());
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class ViewNote implements Runnable {
    
        private Note note = null;
    
    
        public ViewNote(Note note){
            this.note = note;
        }
    
        @Override
        public void run() {
    
            try {
                this.note.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    class Note{
    
        private ArrayList<String> al = new ArrayList<String>();
    
        private ReentrantReadWriteLock rtRw = new ReentrantReadWriteLock();
        private Lock readLock = rtRw.readLock();
        private Lock writeLock = rtRw.writeLock();
    
        public void add(String note) throws InterruptedException {
            writeLock.lock();
            System.out.println("===****正在做笔记笔记===" + Thread.currentThread().getName());
            al.add(note);
            Thread.sleep(5000);
            System.out.println("===****做完笔记===" + Thread.currentThread().getName());
            writeLock.unlock();
        }
    
        public String get() throws InterruptedException {
            // 读锁可以多次进入
            readLock.lock();
            System.out.println("===正在查看笔记===" + Thread.currentThread().getName());
            if(al.size() == 0) {readLock.unlock(); return "";}
            String re = al.get(al.size()-1);
            Thread.sleep(2000);
            System.out.println("===查完笔记===" + Thread.currentThread().getName());
            readLock.unlock();
            return re;
        }
    
    }
    
    • StampedLock 印章锁
    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        void move(double deltaX, double deltaY) { // an exclusively locked method
          long stamp = sl.writeLock();
          try {
            x += deltaX;
            y += deltaY;
          } finally {
            sl.unlockWrite(stamp);
          }
        }

        double distanceFromOrigin() { // A read-only method
          // 获取乐观的读锁
          long stamp = sl.tryOptimisticRead();
          double currentX = x, currentY = y;
          if (!sl.validate(stamp)) {
             stamp = sl.readLock();
             try {
               currentX = x;
               currentY = y;
             } finally {
                sl.unlockRead(stamp);
             }
          }
          return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        void moveIfAtOrigin(double newX, double newY) { // upgrade
          // Could instead start with optimistic, not read mode
          long stamp = sl.readLock();
          try {
            while (x == 0.0 && y == 0.0) {
              // 尝试转换为写锁
              long ws = sl.tryConvertToWriteLock(stamp);
              if (ws != 0L) {
                stamp = ws;
                x = newX;
                y = newY;
                break;
              }
              else {
                sl.unlockRead(stamp);
                stamp = sl.writeLock();
              }
            }
          } finally {
            sl.unlock(stamp);
          }
        }
      }}

线程的优先级

  • 优先级只是在概率上有更大可能先被 cpu 执行,不是优先级高就必然比优先级低先执行
  • 优先级的数值范围是正整数 [1-10]
  • setPriority() 设置优先级
  • 优先级的一些常量
    • Thread.MIN_PRIORITY (1)
    • Thread.NORM_PRIORITY (5)
    • Thread.MAX_PRIORITY (10)

守护线程(daemon)

  • 一般线程都是用户线程
  • setDaemon(true) 开启守护线程
  • 虚拟机必须等待所有用户线程执行完,但不用等待守护线程执行完
  • 常见的守护线程
    • GC 垃圾回收线程

线程通信

生产者消费者模式

锁本质

  • 锁究竟锁的是什么?(只有两个)
    • 实例对象(可以有多个)
    • 类对象 Class(一个类只存在一个 Class 对象

线程并发的一些问题

虚假唤醒

  • 把多个相关线程唤醒,但事实上不满足执行条件
  • wait 和 notifyAll 的使用
    • wait 要放在 while 循环中,避免虚假唤醒的问题

死锁

  • 死锁的四个必要条件
    • 互斥
    • 持有并等待
    • 不可强行剥夺资源
    • 循环等待(需要的资源在别人手里)