Java并发编程

虽然并发编程在本质上是一样的,但是不同语言的切换也需要去了解一下java的并发编程套路,写一篇总结一下以备忘,是为记。

和C++类似java也有锁Reentrantlock,然后再用condition来做判断。
多线程的问题一般是结果出错/初始化顺序问题/活跃性(活跃性是指活锁和死锁)三种问题。
和C11不用的是,java有线程并发包和线程池,不用再自己手撸线程池。

    public static void main(String[] args) { 
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10000; i++) { 
            service.execute(new Task());
        } 
    System.out.println(Thread.currentThread().getName());
    } 

创建线程池的源码


    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

其中拒绝策略可以注意下:

AbortPolicy,这种拒绝策略在拒绝任务时,会直接抛出一个类型为RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。

DiscardPolicy,这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

DiscardOldestPolicy,如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。

CallerRunsPolicy,相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处。
第一点新提交的任务不会被丢弃,这样也就不会造成业务损失。
第二点好处是,由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。

12.13

被synchronized 修饰的方法或代码块同一时刻只有一个线程可以运行,背后利用的是monitor实现。
对于同步方法和同步代码块有什么不同呢?
对于同步代码块

public class SynTest {
    public void synBlock() {
        synchronized (this) {
            System.out.println("lagou");
        }
    }
}

反汇编

  public void synBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                      // String lagou
         9: invokevirtual #4               // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return

每个对象维护着一个记录被锁次数的计数器,对于monitorenter

  1. 计数器为0,则加1,然后该线程为这个monitor的所有者
  2. 该线程是这个monitor的持有者,则重入,并在加1
  3. 其他线程拥有这个monitor,则该线程被阻塞,直到monitor的计数器变为0。
    对于monitorexit则是将monitor的计数器减1。

对于同步方法

public synchronized void synMethod() {
 
}

反汇编

  public synchronized void synMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 16: 0

对于Lock 类中的lock/unlock和C++中的无异,但是tryLock可以尝试获取锁,根据获取结果写业务逻辑。lockInterruptibly() 除非当前线程在获取锁期间被中断,否则便会一直尝试获取直到获取到为止。

然后再看看公平锁和非公平锁,公平锁就是哪个线程先等待这个锁哪个线程就下拿到这个锁,但是非公平锁却是线程A在解锁时,本来该是线程B来获取这个锁,但是这时突然有线程Y来获取这把锁,考虑到把线程Y加入等待队列和唤醒线程B都是有消耗的,非公平锁这时候会考虑让线程Y来得到这把锁。看源码的实现:

// 默认是非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

当然肯定也少不了读写锁,写锁与读锁和写锁都互斥。

读写锁的升降级:

再说说自旋锁,一言以蔽之就是自旋锁用循环不停去尝试获取锁,让线程始终处于runable状态,节省线程切换带来的开销,因为这可能比执行业务代码的开销还大。具体要看临界区的大小,如果临界区很大就不适合了,所以自旋锁适合并发不高且临界区小的情况,就像AtomicLong的实现

public final long getAndAddLong (Object var1,long var2, long var4){
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4));


    return var6;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值