Java多线程编程写法

写在开头

前段时间看了些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多线程编程写法)