美文网首页程序员
Java多线程编程写法

Java多线程编程写法

作者: LucasPoint2 | 来源:发表于2019-04-04 00:16 被阅读0次

    写在开头

    前段时间看了些java多线程的书和博文,但是在接下来倒没有太多用到,为了防止遗忘,准备总结一篇博文记录一下。注:此文建议复习使用。

    多线程的写法

    多线程的实现方式大致有如下两种:
    1. 继承Thread类:extend Thread 实现 run 函数
    2. 实现Runnable接口:implements Runnable 实现run函数
    线程安全

    通过synchronized关键字实现线程间共享变量的数据安全。
    Thread.currentThread()可以获取当前线程实例。

    线程调度

    线程等待:使用sleep函数使线程等待相应时间
    线程中止:使用推出标志、使用stop(已废弃的方法不推荐,可能产生无法预期的后果 如:会释放锁让数据不一致)、使用interrupt(软中断,需要在run函数中判断isInterrupted是否已中断)
    线程暂停:suspend暂停线程 resume恢复线程,但是这种使用方式有如下两个缺点:1-暂停线程中的同步对象加锁后suspend会导致锁无法释放,其他线程饥饿等待。2-暂停线程中如果有传入对象进行数据操作,可能会造成只操作一半,数据不同步。
    另外,yield方法也可以将线程暂停由其他的线程获取CPU资源。
    线程优先级:setPriority可以设置线程优先级,线程优先级具有继承性、规则性及随机性。

    多线程对象及并发访问

    实例变量非线程安全:多个线程共享一个实例变量时可能会出现非线程安全,可以给变量或变量方法加同步锁(synchronized)
    Synchronized不仅可以给方法加锁也可以单独给语句块加锁来提升线程运行效率。

    synchronized(this){
       //coding
    }
    

    synchronized同步代码块会将括号中的对象作为对象监视器,在对象相同的代码块会呈现同步效果,也会与该对象的同步方法呈现同步效果。
    需要注意的是synchronized关键字加在static静态方法时,持有的Class锁,会和持有该class实例化对象锁的代码呈现同步效果。
    synchronized以string为锁对象的时候需要注意string常量池的问题,相同值的string持有一把锁,所以一般不建议使用string作为锁对象。

    volatile关键字

    volatile是线程同步的轻量实现,性能上优于synchronized,且在多线程访问时也不会造成阻塞,volatile可以保证多线程的数据可见性,但不能保证原子性,而synchronized可以同时保证原子性和可见性,另外volatile只能修饰变量。
    volatile的主要使用场合是在多线程可以感知实例变量被更改了,并且可以获得最新的值使用。

    原子类

    使用原子类(如:AtomicInt)可以保证其方法的原子性,但是方法和方法之间的调用不是原子的。所以还是需要在代码中使用synchronized。

    线程间通信

    线程(A)可以使用持有对象锁的wait()方法,此时会释放该对象的锁,然后等待其他线程(B)调用该对象的notify()方法唤起此线程(A)继续执行之后的代码,另外notify会随机唤醒一个此对象wait的线程,而notifyAll则唤醒全部。
    需要注意的是notify并不会释放锁,当前notify线程会继续运行直到释放对象锁后,被唤起的线程才会开始执行。
    wait也可以使用long型参数作为超时自动唤醒的参数,只要当时该对象锁未被占用,则线程自动唤醒。
    使用线程(A)的join方法可以使当前线程等待该线程(A)完成run之后开始执行,另外的join也可以传入long参数作为超时自动唤醒的参数。join的内部实现是基于wait的,所以join也会释放锁。
    前面提到了sleep也会阻塞线程使其等待,但是不同的是sleep不会释放锁。

    线程共享变量

    类ThreadLocal可以在不同线程中实现各线程隔离的共享变量,不同线程的ThreadLocal的值将保持隔离、不会污染、父子线程也不会继承。
    而类InheritableThreadLocal可以在子线程取得父线程继承下的值,但是在继承之后子线程与父线程仍会保持隔离。

    Lock的使用

    除了使用synchronized关键字实现同步外,Java1.5新增了ReentrantLock也可以达成同步的效果。synchronized可以使用wait/notify进行线程间通信。ReentranLock也提供了通信类Condition,该类可以使用await/signal/signalAll进行等待和唤醒,另外因为ReentranLock可以创建多个Condition,所以也可以实现点对点多路通知。同样的,await需要先持有对象锁进行lock。
    以下简单代码示例:

    public class Service
    {
         private Lock lock = new ReentrantLock();
         public Condition conditionA = lock.newCondition();
         public Condition conditionB = lock.newCondition();
         public void waitA(){
                 lock.lock();
                 conditionA.await();
                 lock.unlock();
         }
         public void signalA(){
                lock.lock();
                conditionA.signal();
                lock.unlock();
         }
    }
    

    另外ReentrantLock有以下几种方法:

    1. getHoldCount() 查询当前线程锁定的个数
    2. getQueueLength() 返回正在等待获得该锁定的线程数
    3. getWaitQueueLength(Condition c)返回等待该锁定此条件(c)的线程数
    4. hasQueueThread(Thread t)查询指定线程是否正在等待该锁定
    5. hasWaiters(Condition c)查询是否有线程等待该锁定此条件(c)
      剩余不再列举

    相对于完全互斥的ReentrantLock,ReentrantReadWriteLock在某些不需要操作实例变量的方法中,可以使用读写锁(readlock/writelock)来加快运行效率。读锁共享、写锁与其他锁互斥。

    定时器

    Timer使用schedule进行定时操作,第一个参数一般传入具体的timertask。其余可以接受task运行的具体日期时间Datetime或者延迟执行时间long,也可以传入间隔时间period(long型),以间隔时间循环运行task。
    另外timertask的cancel方法可以取消当前task的运行,而timer的cancel将清除所有的任务队列。
    相对于schedule,还有scheduleAtFixedRate该方法基于上次任务结束后的时间开始进行循环。

    写在结尾

    多线程是一门值得深入研究和探讨的核心技术,以上总结写得比较简单浅薄,如有遗漏请不吝提醒我,基本参考自《Java多线程编程核心技术》,非常感谢前辈们贡献知识。

    相关文章

      网友评论

        本文标题:Java多线程编程写法

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