美文网首页
Java 多线程

Java 多线程

作者: X小飞侠 | 来源:发表于2018-03-29 11:43 被阅读23次

    1 简述

    1.1 线程机制

    Java使用的是抢占式的线程机制,调度机制周期性地切换上下文,切换线程,从而为每个线程提供时间片。看似同时执行,其实是不停地切换。在这种机制下,一个线程的阻塞不会导致整个进程阻塞。

    优先级较低的线程仅仅是执行的频率较低,不会得不到执行。

    要实现线程行为,你必须显示地将一个任务(Runnable)附着到线程(Thread)上。Thread类只是驱动赋予它的任务Runnable。

    1.2 阻塞(se第四声)

    程序中一个任务因为该程序控制范围外的因素(条件通常是IO),而不能继续执行,这个任务线程被阻塞了。

    2 Executor 线程池

    用new Thread()的方式不利于性能优化,所以采用线程池替代,达到线程的复用。首选CachedThreadPool回收旧线程停止创建新线程。

    ExecutorService exec = Executors.newCachedThreadPool();
    exec.execute(new Runnable() {
            @Override
            public void run() {
    
            }
    });
    

    Runnable设返回值,需要实现Callable接口call()方法,并且必须使用ExecutorService.submit()方法调用。
    使用 Future.get() 获取返回值。
    例如:

    public class CallableAndFuture {
        static class MyThread implements Callable<String> {
            @Override
            public String call() throws Exception {
                return "Hello world";
            }
        }
    
        static class MyThread2 implements Runnable {
            @Override
            public void run() {
    
            }
        }
    
        public static void main(String[] args) {
            ExecutorService threadPool = Executors.newSingleThreadExecutor();
            Future<String> future = threadPool.submit(new MyThread());
    
            try {
                System.out.println(future.get());
            } catch (Exception e) {
    
            } finally {
                threadPool.shutdown();
            }
        }
    }
    

    3 线程安全

    3.1 实质

    线程安全其实就是解决共享资源竞争的问题。
    解决方法是:当一个资源被一个任务使用时,加锁。

    解锁的时候,下一个要使用资源的任务并没有按照排队的方式按次序来获取,而是通过竞争获取资源。可以通过yield()和setPriority()来给线程调度器提供建议,这效果取决于具体平台和JVM实现。

    3.2 synchronized加锁

    对于某个特定对象,其所有的synchronized方法共享同一个锁。
    一个任务可以多次获得对象的锁。

    判断是否应该加锁:如果你正在写一个变量,这个变量接下来将被另一个线程读取;你正在读一个上一次已经被另一个线程写过的变量。以上两种情况都必须使用同步。并且,读写线程都必须用相同的监视器锁同步。

    3.3 使用显式的Lock对象加锁

    除了synchronized方式加锁之外,还可以用Lock对象:

    private Lock lock = new ReentrantLock();//重入锁
    ......
    lock.lock();
    try{
    ......
    return ...;//在try中return确保unlock()不会过早发生
    }finally{
    lock.unlock();
    }
    

    和synchronized的区别:tryLock()

    1. 可以尝试着获取锁,最终获取失败。
    2. 可以尝试着获取锁一段时间,然后放弃获取。

    如果其他的线程已经获取了这个锁,你可以决定离开,去执行其他一些事,而不是等待直至这个锁释放。提供了比Synchronized更细粒度的控制力。(例如可以释放当前锁之前,捕获下一节点的锁)

    4 volatile

    volatile关键字主要提供2种作用:

    1. 可见性:当使用volatile关键字去修饰变量的时候,所有线程都会直接读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的
    2. 禁止指令重排序:应用在标志位

    5线程本地存储

    防止共享资源产生冲突的第二种方式是根除变量的共享,使用ThreadLocal。

    6 终结任务

    线程的各个状态


    线程的各个状态

    停止线程主要使用标志位,在run()方法中加以判断,是否要执行。

    1. 自己定义一个volatile boolean的标志位
    2. 使用interrupt()方法,但是这个方法也是打个停止的标志,并不是真正的停止线程,还是要在run()方法中调用Thread.interrupted()方法判断。

    7 线程之间的协作

    yield()方法:调用的线程放弃cpu时间让给别的线程。
    join()方法:调用的线程先执行。

    7.1 wait()

    wait()是object的方法,它会释放锁,而sleep()和yield()不会释放。
    因此在该对象(未锁定的)中的其他synchronized方法可以在wait()期间被调用。

    只能在同步控制方法或者同步控制块里调用wait()、notify()、notifyAll(),因为要操作对象的锁。

    wait()必须用一个检查感兴趣的条件的while循环包围,本质是检查感兴趣的条件,并在条件不满足的情况下返回到wait()方法中。

    Thread1:
    synchronized(sharedMonitor){
      <setup condition for Thread2>
      sharedMonitor.notify();
    }
    //错误的使用:时机太晚,错失信号,盲目进入wait(),产生死锁。
    Thread2:
    while(someConditon){
      synchronized(sharedMonitor){
        sharedMonitor.wait();
      }
    }
    //正确的使用:防止在someCondition变量上产生竞争条件
    Thread2:
     synchronized(sharedMonitor){
        while(someConditon){
             sharedMonitor.wait();
        }
     }
    
    

    7.2 notify()和notifyAll()

    notify():在众多等待同一个锁的线程中,只有一个会被唤醒去获取锁。
    notifyAll():在众多等待同一个锁的线程中,全部会被唤醒去共同竞争锁。
    两个同样最终只有一个线程能获取到锁。

    可能有多个线程某单个对象上处于wait状态,因此调用notifyAll()比notify()更安全。
    只有一个线程实际处于wait()状态,这时可以用notify()代替notifyAll()优化;

    如果有多个线程在等待不同条件,使用notify()就不能知道是否唤醒了恰当的任务。

    8 死锁

    某个线程在等待另一个线程,而后者又在等待别的线程,这样一直下去,直到这个链上的线程又在等待第一个线程释放锁。这得到了一个状态:线程之间相互等待的连续循环,没有哪个线程能继续。这被称为死锁

    死锁的4个条件:

    1. 互斥条件(资源的特点):线程使用的资源中至少有一个是不能共享的。
    2. 占用请求条件(单个线程的特点):至少有一个线程,它持有一个资源,并且等待获取另一个当前被别的线程持有的资源。
    3. 不可剥夺条件(单个线程的特点):已持有的资源不能被别的线程抢占,而它(持有资源的线程)自己也不会主动去抢别的线程的资源。
    4. 循环等待条件(多个线程形成的特点):必须有循环等待。

    解决:让一方先放弃资源作出让步。

    相关文章

      网友评论

          本文标题:Java 多线程

          本文链接:https://www.haomeiwen.com/subject/tprucftx.html