《疯狂的JAVA讲义》笔记-第16章多线程

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/klli15/article/details/97006290

《疯狂的JAVA讲义》笔记-第16章多线程

  • 三种方法实现多线程
实现方法 特点 例子
继承 Thread 无法共享变量,以run为线程执行体 new Thread().start
实现 Runnable接口,并作为 Thread的target 运行 共享实现类变量,以run为线程执行体 new Thread(new MyRun()).start
实现Callable接口,并使用FutureTask包装,把包装对象作为Thread的target运行 可以抛异常且有返回值(task.get()),共享变量,以call为线程执行体 task = new FutureTask(new MyCall()); Thread thread = new Thread(task);
  • sleepyield 以及join

    • sleep:让线程睡眠一段时间,处于 阻塞状态。
    • join:让线程等待另一个线程执行结束,可用于流程性的控制
    • yield: 线程让步,不阻塞线程,只是将线程设为 就绪状态,将CPU让给优先级更高的线程。可能会出现没有其他优先级更高的线程,从而刚成为就绪状态后又被操作系统调度成为 运行 状态的线程
  • 后台线程/守护线程

    • 为其他线程服务的,一直在后台运行的线程,可以通过thread.setDaemon(true)设置,但是须在运行前进行设置,且中途不能修改。当所有的前台程序结束,后台线程自动结束。例子:JVM的GC
  • 线程同步

    • 同步代码块:同步监视器(互斥量)为 obj,只有获取到该监视器才可以进入代码块
    synchronized(obj){
    ....
    }
    
    • 同步方法:同步监视器为this,只有获取到account实例才可以进入代码块
    //Account.java
    public synchronized int method(){
    ....
    }
    
    • 灵活的加锁同步Lock :较为常用的是可重入锁ReetrantLocksynchronized 也是可重入锁
    private final lock = new ReentrantLock();
    public int method(){
    	lock.lock();
    	try{
    		...
    	}finally{
    		lock.unlock();
    	}
    }
    
  • 可重入锁与不可重入锁

    • 可重入锁:当一个进程获取到 A 锁,如果在递归或者其他需要 A 锁的地方时,无需再次获取 A 锁。
    • 不可重入锁:需要再次获取 A 锁,容易导致死锁
    	public synchronized int method1(){
    		method2();
    	}
    	public synchronized int method2(){
    			.....
    	}
    //这种情况下 可重入不会死锁,但 不可重入则会死锁
    
  • 锁的释放

    • 释放锁的情景
    1. 方法执行结束
    2. 同步代码块或同步方法执行了 break 或 return 语句
    3. 出现异常,方法结束
    4. 程序执行了 wait()
    • 不会释放锁的情景
    1. 调用sleep()yield() 方法
    2. 其他线程调用其他线程的 suspend()方法
  • 进程通信:当使用 同步方法时,同步监视器是 this,即对象本身,可以使用 Object类的wait``notify``notifyAll方法进行通信

    • wait():让当前进程暂停,直到其他线程调用 该同步监视器 的 notifynotifyAll方法才被唤醒,也可以 等指定秒数后自行唤醒
    • notify():唤醒 在同一个同步监视器上等待的单个线程。如果有多个,则选择是任意性的
    • notifyAll():唤醒所有在同一个同步监视器上等待的线程

    如果不使用 同步方法,则不存在 wait()等方法,可以使用 Condition 来进行类似的操作

    • await() :跟wait()相似,让当前线程暂停,并释放已经获得的 Lock 对象
    • signal()/signalAll():与 notify()/notifyAll()一样
  • 使用 阻塞队列 控制通信:消费者/生产者模型

    • 使用 take() put()可以使进程阻塞
  • 线程池

    • 由于频繁创建销毁非常影响性能,所以在 JAVA 5 后有了线程池,可使用工厂类 Executors的以下方法产生线程池
    • newCachedThreadPool():根据需要创建线程,并将线程缓存在线程池里
    • newFixedThreadPool():创建固定长度,可复用的线程池
    • newSingleThreadPool(): 创建只有一个线程的线程池
    • newScheduledThreadPool(int corePoolSize) :创建指定线程线程数的线程池,可以在指定延迟后执行线程任务

    以上方法返回一个 ExecutorService 对象,有以下方法

    • Future<?> submit(Runnable task) :由于 run方法没有返回值,所以返回值为 null,可以调用Future对象的 isDone/isCancelled方法获取执行状态
    • <T> Future<T> submit(Runnable task,T result):线程池在空闲时会执行 task 任务,在结束后返回 result
    • <T> Future<T> submit(Callable<T> task)Future对象是call()方法的返回值

    当不想提交任务后,请使用shutdown()方法关闭线程池,但是线程池中线程是在执行完当前任务后才会死亡,如果需要立即死亡,使用shutdownNow()方法,可以视图停止所有正在执行的任务

  • ForkJoinPool

    • 单核上多线程只能算 并发,但是多核上就可以实现 并行(parallelism),ForkJoinPool线程就是实现这个的,他是 ExecutorService的实现类
    • ForkJoinPool():根据 Runtime.availableProcessors()方法指定 并行线程数,当然也可以传入 int parallelism 指定并行线程数
    • 没有返回值的任务需要实现 RecursiveAction,有返回值的任务则实现 RecursiveTask<T> ,最后使用 submit方法提交即可
  • TreadLocal类可以实现每个线程都使用自己的变量副本,即每个线程的该变量都是独立的,不会发生死锁、等待锁的情况。使用方法 private ThreadLocal<String> name = new ThreadLocal<>();

END

猜你喜欢

转载自blog.csdn.net/klli15/article/details/97006290