Java线程

Java线程

  • 使用线程

    • 实现Runnable接口

    • 实现Callable接口

    • 继承Thread类

      实现Runnable和Callable接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过Thead来调用,可以理解为任务是通过线程驱动从而执行的。
      
    • 实现

      //实现Runnable接口
      public class MyRunnable implements Runnable {
          @Override
          public void run() {
              // ...
          }
      }
      public static void main(String[] args) {
          MyRunnable instance = new MyRunnable();
          Thread thread = new Thread(instance);
          //调用start()方法来启动线程
          thread.start();
      }
      
      //实现Callable接口
      public class MyCallable implements Callable<Integer> {
          public Integer call() {
              return 123;   //和Runnable不同,有返回值
          }
      }
      public static void main(String[] args) throws ExecutionException, InterruptedException {
          MyCallable mc = new MyCallable();
          FutureTask<Integer> ft = new FutureTask<>(mc);
          Thread thread = new Thread(ft);
          thread.start();
          System.out.println(ft.get());
      }
      
      //继承Thread
      public class MyThread extends Thread {
          public void run() {
              // ...
          }
      }
      public static void main(String[] args) {
          MyThread mt = new MyThread();
          mt.start();
      }
      
      
      总结:
          实现接口会更好一些,因为:
          Java不支持多重继承,因此继承了Thread类就无法继承其他类,但是可以实现多个接口
          类可能只要求可执行就行,继承整个Thread类开销过大
      
  • 基础线程机制

    • Executor管理多个异步任务的执行,而无需程序员显式的管理线程的生命周期,这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

      • CachedThreadPool:一个任务创建一个线程

      • FixedThreadPool:所有任务只能使用固定大小的线程

      • SingleThreadPool:相当于大小为1的FixedThreadPool

        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) {
                executorService.execute(new MyRunnable());
            }
            executorService.shutdown();
        }
        
    • Daemon

      • 守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分,当所有的非守护线程结束时,程序也就终止,同时会杀死所有的守护线程,main()属于非守护线程,在线程启动之前使用setDaemon()方法可以将一个线程设置为守护线程

        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.setDaemon(true);
        }
        
    • sleep

      • Thread.sleep(millisec)方法会休眠当前只在执行的线程,millisec单位为毫秒

      • sleep()可能会抛出InterrupException,因为异常不能跨线程传播会main()中,因此必须在本地进行处理,线程中抛出的其他异常也同样需要在本地进行处理。

        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    • yield()

      • 对于静态方法Thread.yield()的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其他线程来执行,该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其他线程可以运行

        public void run() {
            Thread.yield();
        }
        
  • 中断

    一个程序执行完毕之后会自动结束,如果在运行过程中发生异常一会提前结束

    • InterruptedException

      • 通过调用一个线程的interrupt来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出InterruptedException,从而提前结束该线程,但是不能中断I/O阻塞和synchronized锁阻塞

        public class InterruptExample {
        
            private static class MyThread1 extends Thread {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        System.out.println("Thread run");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new MyThread1();
            thread1.start();
            thread1.interrupt();
            System.out.println("Main run");
        }
        
        //对于以上代码,在main()中启动一个线程之后在中断它,由于线程中调用了Thread.sleep()方法,因此会抛出一个InterrupedException,从而提前结束线程,不执行之后的语句
        
    • interrupted()

      • 如果一个线程的run()方法执行了一个无限循环,并且没有执行sleep()等会抛出一个InterruptedException的操作,那么调用现成的interrupt()方法就无法使线程提前结束
        但是调用interrupt()方法会设置线程的中断标记,此时调用interrupted()会返回true,因此可以在循环体中使用interrupted()方法来判断线程是否处于中断状态,从而提前结束线程。

        public class InterruptExample {
        
            private static class MyThread2 extends Thread {
                @Override
                public void run() {
                    while (!interrupted()) {
                        // ..
                    }
                    System.out.println("Thread end");
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread thread2 = new MyThread2();
            thread2.start();
            thread2.interrupt();
        }
        
    • Executor的中断操作

      • 调用Executor的shutdown()方法会等待线程都执行完毕之后在关闭,但是如果调用的使shutdownNow()方法,则相当于调用每个线程的interrupt()方法

        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(() -> {  //使用Lanmbda创建线程,相当于创建了一个匿名内部线程
                try {
                    Thread.sleep(2000);
                    System.out.println("Thread run");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            executorService.shutdownNow();
            System.out.println("Main run");
        }
        
      • 如果只想中断Executor的一个线程,可以通过使用submit()方法来提交一个线程,它会返回一个Future<?>对象,通过调用该对象的cancel(true)方法就可以中断线程

        Future<?> future = executorService.submit(() -> {
            // ..
        });
        future.cancel(true);
        
  • 互斥同步

    java提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个使JVM实现的synchronized,而另一个是JDK实现的ReentrantLock

    • synchronized

      • 同步代码块

        public void func() {
            synchronized (this) {
                // ...
            }
        }
        //它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步
        
        //对于以下代码,使用ExecutorService执行了两个线程,由于调用的是同一个对象的同步代码块,因此这两个线程会进行同步,当另一个线程进行同步语句块时,另一个线程就必须等待.
        public class SynchronizedExample {
        
            public void func1() {
                synchronized (this) {
                    for (int i = 0; i < 10; i++) {
                        System.out.print(i + " ");
                    }
                }
            }
        }
        public static void main(String[] args) {
            SynchronizedExample e1 = new SynchronizedExample();
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(() -> e1.func1());
            executorService.execute(() -> e1.func1());
        }
        
        //对于一下代码,两个线程调用了不同对象的同步代码块,因此这两个线程就不需要同步,从输出结果可以看出,两个线程交叉执行
        public static void main(String[] args) {
            SynchronizedExample e1 = new SynchronizedExample();
            SynchronizedExample e2 = new SynchronizedExample();
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(() -> e1.func1());
            executorService.execute(() -> e2.func1());
        }
        
      • 同步一个方法

        //它和同步代码块一样,作用于同一个对象
        public synchronized void func () {
            // ...
        }
        
      • 同步一个类

        public void func() {
            synchronized (SynchronizedExample.class) {
                // ...
            }
        }
        //作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步
        
        public class SynchronizedExample {
        
            public void func2() {
                synchronized (SynchronizedExample.class) {
                    for (int i = 0; i < 10; i++) {
                        System.out.print(i + " ");
                    }
                }
            }
        }
        public static void main(String[] args) {
            SynchronizedExample e1 = new SynchronizedExample();
            SynchronizedExample e2 = new SynchronizedExample();
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(() -> e1.func2());
            executorService.execute(() -> e2.func2());
        }
        
      • 同步一个静态方法

        public synchronized static void fun() {
            // ...
        }
        //作用于整个类
        
    • ReentrantLock

      ReentrantLock是java.util.concurent(JUC)包中的锁

      public class LockExample {
      
          private Lock lock = new ReentrantLock();
      
          public void func() {
              lock.lock();
              try {
                  for (int i = 0; i < 10; i++) {
                      System.out.print(i + " ");
                  }
              } finally {
                  lock.unlock(); // 确保释放锁,从而避免发生死锁。
              }
          }
      }
      public static void main(String[] args) {
          LockExample lockExample = new LockExample();
          ExecutorService executorService = Executors.newCachedThreadPool();
          executorService.execute(() -> lockExample.func());
          executorService.execute(() -> lockExample.func());
      }
      
      ReentrantLock和synchronized的区别:
      	1.synchronized是JVM实现的,ReentrantLock是JDK实现的
      	2.新版本java对synchronized加了自旋锁等,synchronized与ReentrantLock大致相同
      	3.ReentrantLock可中断,synchronized不行
      	4.synchronized中的锁是非公平的,ReentrantLock默认情况下也是非公平的,但是也可以是公平的
      	5.一个ReentrantLock可以同时绑定多个Condition对象
          
      使用选择:
          除非需要使用ReentrantLock的高级功能,否则优先使用synchronized。因为synchronized是JVM实现的一种锁机制,JVM原生的支持它,而ReentrantLock不是所有的JDK版本都支持,并且使用synchronized不用担心没有释放锁而导致死锁问题,因为JVM会确保锁的释放。
      
  • 线程之间的协作

    • join()

      在线程中调用另一个现成的join()方法,会将当前线程挂起,而不是忙等待,知道目标线程结束
      
    • wait()

      调用wait()使得线程等待某个条件满足,线程在等待是会被挂起,当其他现成的运行使得这个条件满足时,其他线程会调用notify()或者notifyAll()来唤醒挂起的线程
      
      使用wait()挂起期间,线程会释放锁,这是因为如果没有释放锁,其他线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行notify()或者notifyAll()来唤醒挂起的线程,造成死锁
      
    • wait()和sleep()的区别

      • wait()是Object的方法,而sleep()是Thead的静态方法
      • wait()会释放锁,sleep()不会
    • TODO

  • 线程状态

    • 新建

      新建后尚未启动
      
    • 可运行

      正在Java虚拟机中运行,但是在操作系統层面,它可能处于运行状态,也可能等待资源调度,资源调度完成就进入运行状态。所以该状态的可运行是指可以被运行,具体有没有运行要看底层操作系统的资源调度
      
    • 阻塞

      请求获取monitor lock从而进入synchronized函数或者代码块,但是其他线程已经占用了该monitor lock,所以处于阻塞状态,要结束该状态进入从而RUNABLE需要其他线程释放monitor lock
      
    • 无限期等待

      等待其他线程显示的唤醒
      
      阻塞和等待的区别在于,阻塞时被动的,他是在等待获取monitor lock。而等待是主动的,通过调用Object.wait()等方法进入
      
    • 限期等待

      无需等待其他线程显示的唤醒,在一定时间之后会被系統自动唤醒
      
    • 死亡

      可以是线程结束任务之后自己结束,或者产生了异常而结束
      
posted @ 2021-07-07 16:05  zmy98  阅读(38)  评论(0)    收藏  举报