Java多线程与并发

8-1 进程和线程 8-2 线程的start和run方法的区别 8-3 Thread和Runnable的关系 8-4 如何实现处理线程的返回值 8-5 线程的状态 8-6 sleep和wait的区别 8-7 notify()和notifyall()的区别 8-8 yield()函数-礼让 8-9 interrupt函数

8-1 进程和线程

  • 进程和线程的由来
    进程和线程的由来

进程市资源分配的最小单位,线程市 CPU 调度的最小单位

  • 进程和线程的区别
    • 线程不能看作独立应用,而进程可以看作独立应用
    • 进程由独立的地址空间,互不影响,线程只是进程的不同执行路径
    • 线程没有独立的地址空间,多进程的程序比多线程的程序健壮
    • 进程的切换比线程的切换开销大很多
  • Java 进程和线程的关系
    • Java对操作系统提供的功能进行封装,包括进程和线程
    • 运行一个程序会产生一个进程,进程包含至少一个线程
    • 每一个进程对应一个JVM实例,多个线程共享JVM里的堆
    • Java采用单线程编程模式,程序自动创建主线程
    • 主线程可以创建子线程,原则上要后于子线程完成执行

8-2 线程的start和run方法的区别

package com.imocc.javabasic.bytecode.thread;

/**
 * @author zhangjingyu
 */
public class ThreadTest {
    private static void attack(){
        System.out.println("Fight");
        System.out.println("Current THread is: "+ Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run(){
                attack();
            }
        };
        System.out.println("current main thread is : " + Thread.currentThread().getName());
        t.run();
        t.start();
    }
}

运行结果

  • 调用start()方法会创建一个新的子线程并启动
  • run()方法只是Thread的一个普通方法调用

线程的start和run方法的区别

8-3 Thread和Runnable的关系

  • Thread是实现了Runnable接口的类,使用run支持多线程
  • 因类的单一继承原则,推荐多使用Runnable接口

8-4 如何实现处理线程的返回值

  • 如何给run()方法传参?
    • 构造函数传参
    • 成员变量传参
    • 回调函数传参
  • 如何实现处理线程的返回值?
    • 主线程等待法
      • 实现简单
      • 需要自己实现循环等待逻辑,无法明确等待时间,无法精准控制
    package com.imocc.javabasic.bytecode.thread;
    
    /**
    * @author zhangjingyu
    */
    public class CycleWait implements Runnable {
        private String value;
    
        @Override
        public void run() {
            try {
                Thread.currentThread().sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            value = "we have data now";
        }
    
        public static void main(String[] args) throws InterruptedException {
            CycleWait cw = new CycleWait();
            Thread t= new Thread(cw);
            t.start();
            while (cw.value == null){
                Thread.currentThread().sleep(100);
            }
            System.out.println("value is "+ cw.value);
        }
    }
    
    • 使用Thread类的join()阻塞当前线程以等待子线程处理完毕
      • 实现简单,精准控制
      • 粒度不够细(比如子线程10次循环,想要第5次的时候去执行另一个子线程的run方法无法实现)
    package com.imocc.javabasic.bytecode.thread;
    
    /**
    * @author zhangjingyu
    */
    public class CycleWait implements Runnable {
        private String value;
    
        @Override
        public void run() {
            try {
                Thread.currentThread().sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            value = "we have data now";
        }
    
        public static void main(String[] args) throws InterruptedException {
            CycleWait cw = new CycleWait();
            Thread t= new Thread(cw);
            t.start();
            t.join();
            System.out.println("value is "+ cw.value);
        }
    }
    
    • 通过Callable接口实现:通过 Or 线程池获取
    package com.imocc.javabasic.bytecode.thread;
    import	java.util.concurrent.Callable;
    
    /**
    * @author zhangjingyu
    */
    public class MyCallable implements Callable<String>{
        @Override
        public String call() throws Exception {
            String value = "test";
            System.out.println("Reday to work");
            Thread.currentThread().sleep(5000);
            System.out.println("task done");
            return value;
        }
    }
    

    FutureTask

    package com.imocc.javabasic.bytecode.thread;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
    * @author zhangjingyu
    */
    public class FuntureTestDemo {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask<String> task = new FutureTask<String> (new MyCallable());
            new Thread(task).start();
            if (!task.isDone()){
                System.out.println("task has not finfshed, please wait");
            }
            System.out.println("task return: " + task.get());
    
        }
    }
    

    线程池

    package com.imocc.javabasic.bytecode.thread;
    
    import java.util.concurrent.*;
    
    /**
    * @author zhangjingyu
    */
    public class ThreadPoolDemo {
        public static void main(String[] args) {
            ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
            Future<String> future =  newCachedThreadPool.submit(new MyCallable());
            if (!future.isDone()){
                System.out.println("task has not finfshed, please wait");
            }
            try {
                System.out.println("task return: " + future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }finally {
                newCachedThreadPool.shutdown();
            }
        }
    }
    

8-5 线程的状态

线程状态的转换

  • 新建(new):创建后尚未启动的线程的状态(新创建的线程但还没有调用start方法)
    MyThread thread = new MyThread();
    System.out.println(thread.getState());
  • 运行(Runnable):包含Running和Ready
    MyThread thread = new MyThread(lock);
    thread.start();
    System.out.println(thread.getState());
  • 无限期等待(Waiting):不会被分配CPU执行时间,需要显式唤醒(notify、notifyAll),以下3个方法会让线程陷入无限期等待状态:
    • 没有设置Timeout参数的Object.wait()方法
    • 没有设置Timeout参数的Thread.join()方法
    • LockSupport.park()方法
  • 限期等待(Timed Waiting):在一定时间后会由系统自动唤醒(也不会分配CPU执行时间)以下方法会让线程进入限期等待:
    • Thread.sleep(long)
    • Object.wait(long)
    • Thread.join(long)
    • LockSupport.parkNanos()
    • LockSupport.parkUntil()
  • 阻塞(Blocked):等待获取排他锁(synchronized)
  • 结束(Terminated):已终止线程的状态,线程已经结束执行

8-6 sleep和wait的区别

  • sleep是Thread类的方法,wait是Object类的方法
  • sleep方法是可以在任何方法使用
  • wait方法只能在synchronized方法或者synchronized块中使用(因为只有获取锁了才能释放锁)

最主要的本质区别:

  • Thread.sleep只会让出CPU,不会导致锁行为的改变
  • Object.wait不仅让出CPU,还会释放已经占有的同步资源锁
package com.imocc.javabasic.bytecode.thread;

/**
 * @author zhangjingyu
 */
public class WaitSleepDemo {
    public static void main(String[] args) {
        final Object lock = new Object();
        new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("Thread A is waiting to get lock");
                synchronized (lock) {
                    try {
                        System.out.println("Thread A get lock");
                        Thread.sleep(20);
                        System.out.println("Thread A do wait method");
                        lock.wait(1000);
                        System.out.println("Thread A is down");
                    }catch (InterruptedException e) {

                    }

                }
            }
        }).start();
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("Thread B is waiting to get lock");
                synchronized (lock) {
                    try {
                        System.out.println("Thread B get lock");
                        System.out.println("Thread B is sleeping 10ms");
                        Thread.sleep(20);
                        System.out.println("Thread B is down");
                    }catch (InterruptedException e) {

                    }

                }
            }
        }).start();
    }
}

sleep和wait运行结果

8-7 notify()和notifyall()的区别

  • 锁池EntryList
    • 假设线程 A 已经拥有了某个对象(不是类)的锁,而其他线程 B、C 想要调用这个对象的某个 synchronized 方法(或者块),由于 B、C 线程在进入对象的 synchronized 方法(或者块)之前必须先获得该对象的锁的拥有权,而恰巧该对象的锁目前正在被线程 A 所占用,此时 B、C 线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
  • 等待池WaitSet
    • 假设线程 A 调用了某个对象的 wait() 方法,线程 A 就会释放该对象的锁,同时线程 A 就进入到了该对象的等待池中,进入到等待池中的线程不回去竞争该对象的锁。
  • notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会

  • notify是随机选择一个处于等待池的线程进入锁池去竞争获取锁的机会

8-8 yield()函数-礼让

  • 概念:当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。

  • yield并不会让出当前占用的锁

8-9 interrupt函数

如何中断线程

  • 已被抛弃的方法:
    • 通过调用stop()方法停止线程(这种方法太过暴力,而且是不安全的。突然调用stop停止了线程,导致线程的一些清理工作无法完成,而且会释放锁,可能导致数据不同步的问题)
    • 通过调用suspend()和resume()方法(原因和stop类似)
  • 目前使用的方法:
    • 调用interrupt(),通知线程应该中断了
      • 如果线程处于被阻塞状态,那么线程将立刻退出被阻塞状态,并抛出一个InterruptedException异常
      • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响
      • 需要被调用的线程配合中断:
        • 在正常运行任务时,经常检查本线程的中断标志位(Thread.currentThread().isInterrupted()方法判断),如果被设置了中断标志就自行停止线程
Comment