JAVA与并发

空扰寡人 提交于 2021-01-19 03:54:16

API

在java1.5之前,java的并发API都是依靠Thread, Runnable, ThreadLocal, ThreadGroup以及Object特有的现成先关方法所构成。此外,还有synchronized, volatile两个关键字对同步和内存一致性的定义。从1.5开始,java的API中多了一个包, java.util.concurrent, 更丰富了并发的API。

本文不是普及知识,所以不对每个API都做详尽的解释,但还是有必要列一张简单的表格。

同步机制

API Java版本 描述 示例
synchronized(obj) { } 1.4

多线程在此代码块必须同步执行

线程的interrupt()方法对同步锁上的阻塞是无效的,当线程获取到锁而进入同步块后,

可以调用Thread.interrupted()方法来检验本线程是否被interrupted了。

synchronized(obj) {
    if (Thread.interrupted()) {
       // This thread is interrupted.
    }
}

Lock since 1.5

替换synchronized。

Lock l = new ReentrantLock();
l.lock();
try {
   // access the resource protected by this lock
} finally {
   l.unlock();
}

obj.wait()
1.4

线程阻塞。只能在obj同步块中使用。跳出阻塞的三种方法

1,其它线程调用obj.notify()

2,其它线程调用obj.notifyAll()

3,其它线程对此线程对象调用了thread.interrupt()

第3中方法会使wait()跑出InterruptedException。

线程执行wait的时候,会释放掉obj上的锁,这使得其它线程可以进入obj的同步块。

synchronized(obj) {
    try {
      while (!conditionReady)
          obj.wait();
    } catch (InterruptedException e) {
        // interrupted
    }
}

condition.await()

since 1.5 替换obj.wait()。只能在lock与unLock之间使用。
Lock l = new ReentrantLock();
Condition c = l.newCondition();
l.lock();
try {
   while (!conditionReady)
      c.await();
} catch (InterruptedException e) {
  // interrupted
} finally {
   l.unlock();
}

obj.notify()

1.4 唤醒阻塞在obj上的一个线程。只能在obj同步块中使用。
synchronized(obj) {
    conditionReady = true;
    obj.notify();
}

condition.signal()

since 1.5 替换obj.nofify()。只能在lock与unLock之间使用。
Lock l = new ReentrantLock();
Condition c = l.newCondition();
l.lock();
try {
   c.singal();
} finally {
   l.unlock();
}

obj.notifyAll()

1.4 唤醒阻塞在obj上的所有线程,所有线程将竞争obj的锁,以进入同步块。只能在obj同步块中使用。
synchronized(obj) {
    conditionReady = true;
    obj.notifyAll();
}

condition.signalAll()

since 1.5 替换obj.nofifyAll()。只能在lock与unLock之间使用。
Lock l = new ReentrantLock();
Condition c = l.newCondition();
l.lock();
try {
   c.singalAll();
} finally {
   l.unlock();
}

Thread.sleep(long millis)

1.4

使当前线程睡眠一段时间。结束睡眠有两种方式

1,等待睡眠时间结束。

2,在睡眠线程对象上调用interrupt()方法。

非常注意的一件事情,sleep方法并不会释放同步锁。在实例中的第二个sleep方法上,

线程不会释放obj的锁,其它线程无法进入这个同步块。

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // interrupted
}
synchronized(obj) {
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    // interrupted
  }
}

Thread.yield()

1.4

释放cpu,是的cpu重现分配时间。

Thread.yield();

thr.join()

1.4

等待thr线程执行完毕。跳出join的方法有两个:

1,等待thr执行完毕。

2,在等待线程上调用interrupt()方法。

try {
  thr.join();
} catch (InterruptedException e) {
  // this thread interrupted.
}

thr.interrupt()

1.4

1,设置thr的interrupted状态。 

2,如果thr线程在sleep, wait, join等所有可以抛出InterruptedException方法上阻塞,则中断此线程。

无法中断在同步锁上阻塞的线程。

thr.interrupt();

并发集合

从java1.5开始,java对并发的支持都体现在java.util.concurrent包中。其中的并发集合,大大方便了我们的开发。最常见的则是BlockingQueue. BlockingDeque.

缺点 - 并发集合在单独使用时,是一个非常棒的工具。但当与synchronized和Lock结合使用时,则存在着死锁的可能。为什么呢?请看下面的代码示例:

// Thread 1
synchronized(obj) {
  blockingQuque.put(e);
}

// Thread 2
synchronized(obj) {
  e = blockingQuque.take();
}

上面的代码存在死锁。 同步块的锁与blockingQueue的锁并不是同一个锁,当blockingQueue队列满的时候,put方法会阻塞,阻塞会释放blockingQueue的锁,但并没有释放obj的锁,thread 2无法进入同步块,无法取出blockingQueue中的元素。

原子操作

Java关键字volatile解决了内存一致问题。voliatile使得寄存器和内存的变量值保持一致。但这并不能保证 i++原子操作。java.util.concurrent.atomic提供了很多原子操作的基本类型。

线程池

线程池是java1.5以后带来的新特性。线程的创建非常消耗资源,影响系统的性能。使用线程池,重用线程,可以优化此方面性能。想tomcat的connector, servlet容器,无一不用到线程池。

线程池的实现方式就像是生产者消费者方式。将要执行的Runnable对象放入BlockingQueue中,然后由线程池中的实际线程不停地从queue中取出Runnable的task,并直接执行task的run方法。

线程池两个最常用的interface为:

  1. Executor

  2. ExecutorService extends Executor

  3. ScheduledExecutorService extends ExecutorService

第二个比第一个多出的是schedule功能。常用的方法:

功能 Name From 描述
执行线程任务 void execute(Runnable command) Executor 向线程管理者提交一个任务。任务必须是一个继承了Runnable的实例。

执行线程任务

<T> Future<T> submit(Callable<T> task)

<T> Future<T> submit(Runnable task,T result)

<T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks)

<T> T invokeAny(Collection<? extends Callable<T>> tasks)

... ...

ExecutorService 向线程管理者提交有返回值的任务。

管理线程

void shutdown()

ExecutorService

Service不再接受submit或execute提交的任务,但之前已经提交到队列成功的任务会执行完毕

此方法存在缺陷,很容易造成永久等待。因为此方法只会对闲置的线程进行interrupt, 如果线程

正处于执行的wait状态,executor并不认为此线程是闲置的。所以不会对其执行interrupt, 这就

使此线程永远的wait。尝试将后面的例子改成shutdown,你会发现程序永远不会退出。

管理线程

void shutdownNow()

ExecutorService 立刻关闭线程,所有任务都被中断。

管理线程

boolean awaitTermination(long timeout,TimeUnit unit)

ExecutorService 等待线程是否完全停止。

执行线程任务

ScheduledFuture<?> schedule(Runnable command,

                            long delay,

                            TimeUnit unit)

ScheduledFuture<?> scheduleAtFixedRate(Runnable command,

                                       long initialDelay,

                                       long period,

                                       TimeUnit unit)


ScheduledExecutorService 在未来某时间端,或定期的执行任务。

了解上面的接口以及方法非常重要。java.util.concurrent包中的类实现了上面的接口,我们可以用new的方式初始化这些接口的实现类。但,最方便的做法是使用静态工厂模式,创建我们所需要的线程管理器。

java.util.concurrent.Executors中,提供了几个静态方法,可以构造ExecutorService与ScheduledExecutorService的实例。

public static ExecutorService newFixedThreadPool(int nThreads) 一个固定size的线程池
public static ExecutorService newCachedThreadPool() 一个自适应size的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 一个执行schedule的线程池

生产者消费者示例

代码解释

在代码案例中,我会使用线程池,并发集合,原子类型和锁机制。案例中,包括以下几个元素:

  1. 生产者线程池。有3个线程。

  2. 生产者。生产者将不停的向线程池提交生产任务。

  3. 消费者线程池。有2个线程。

  4. 消费者。消费者将不停的向线程池提交消费任务。

  5. 管理者定时单线程执行者。定时的执行管理者任务。

  6. 管理者任务。在到时间后,关闭生产者与消费者线程池。

代码

public class ProducerConsumer {
    // 初始化生产者线程池
    ExecutorService producerPool = Executors.newFixedThreadPool(3);
    // 初始化消费者线程池
    ExecutorService consumerPool = Executors.newFixedThreadPool(2);
    // 初始化管理者。
    ScheduledExecutorService supervisor = Executors.newSingleThreadScheduledExecutor();
    // 产品计件器
    AtomicInteger counter = new AtomicInteger(0);
    // 产品存放仓库。容量为5个。
    BlockingQueue<Integer> products = new LinkedBlockingQueue<Integer>(5);
    // 生产出的产品内容。从0递增。
    int product = 0;
    // 对生产过程上锁。
    Lock lock = new ReentrantLock();
    
    public void start() {
        // 提交管理任务,30秒以后执行。
    	supervisor.schedule(new CloseTask(), 30, TimeUnit.SECONDS);
    	// 开始生产
        new Producer().start();
        // 开始消费
        new Consumer().start();
    }
    
    // 生产者
    private class Producer extends Thread {
        // 初始化可重复的生产任务。
        Runnable task = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                    int i;
                    try {
                        lock.lock(); // 对生产过程上锁。
                        i = ++product; // 生产内容
                    } finally {
                        lock.unlock();
                    }
                    System.out.println("Producing "+i);
                    products.put(i); // 放入仓库。
                    counter.getAndIncrement(); // 计件
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        public void run() {
            // 每隔300毫秒,向线程池提交一个生产任务。间隔如果太短,会导致队列过大outofmemory.
            while(!producerPool.isShutdown()) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                producerPool.execute(task);
            }
        }
    }
    
    // 消费者
    private class Consumer extends Thread {
        // 初始化可重复的消费任务。
        Runnable task = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(800);
                    int i = products.take();
                    System.out.println("Consuming "+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        public void run() {
            // 每隔400毫秒,向线程池提交一个消费任务。间隔如果太短,会导致队列过大outofmemory.
            while(!consumerPool.isShutdown()) {
                try {
                    Thread.sleep(400);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                consumerPool.execute(task);
            }
        }
    }
    
    // 管理者任务
    private class CloseTask implements Runnable {
        @Override
        public void run() {
            System.out.println("Try to close all tasks.");
            // 立刻关闭生产线程池。
            producerPool.shutdownNow();
            try {
                while (!producerPool.awaitTermination(1000, TimeUnit.MILLISECONDS));
                // 循环等待线程池是否关闭
                System.out.println("Producer thread pool stops.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 等待生产仓库被清空。
            while (products.size()>0) {
            	try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            // 立刻关闭生产线程池
            consumerPool.shutdownNow();
            try {
                while (!consumerPool.awaitTermination(1000, TimeUnit.MILLISECONDS));
                // 循环等待是否关闭完成。
                System.out.println("Consumer thread pool stops.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(counter.get() + " products.");
            // 关闭管理者
            supervisor.shutdownNow();
        }
        
    }
    
    public static void main(String[] args) {
        new ProducerConsumer().start();
    }

}

监控线程状态

使用jvisualvm查看线程状态,如下图。pool-1对应ProducerPool, pool-2对应ConsumerPool, pool-3对应supervisor.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!