并发
并发的多面性
使用并发需要解决的问题有多个,而实现并发的方式也有多种,并且在这两者之间没有明显的映射关系(通常只具有模糊的界限)。
更快的执行
- 并发通常是提升运行在单处理器上程序的性能。
- 上下文切换(在单处理器,好像多线程并没用,增加了上线文切换)
- 阻塞,特别是io的阻塞
- 事实上,从性能上来看,如果没有任务会阻塞,那么在单处理器上使用并发就没有任何意义。
- 进程相互隔离,它们不会互相干涉。使用并发对于进程,每个任务在其自己的地址空间执行,任务之间不可能相互干涉
改进代码设计
- java的线程机制是抢占式的,这表示调度机制会周期性的中断线程,将一个线程切换到另一个线程,从而为每个线程都提供时间片。
- 协作式系统的优势是双重的:上下文切换开销通常比抢占式系统低廉的多,并且对可以同时执行的线程数量理论上没有任何限制。当你处理大量的仿真元素时,这是一种理想的解决方案。
基本的线程机制
- 一个线程就是在进程中的一个单一的顺序控制流。底层机制是切分cpu时间,但通常你不需要考虑它。
定义任务
- 实现Runnable接口,并且重写run方法
- 继承Thread接口
使用Executor
- CachedThreadPool将会为每个任务都创建一个线程。
public class CachedThread {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
}
}
}
- FixedThreadPool使用了优先的线程集来执行所提交的任务
public class CachedThread {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
}
}
}
- SingleThreadExecutor就像是线程数量为1的FixedThreadPool。在一些我们需要顺序执行任务的场景,这将是很好的一个选择。
public class CachedThread {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
}
}
}
从任务中产生返回值
- 实现Callable接口,并且使用ExecutorService.submit()方法来提交它
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> submit = executorService.submit(() -> {
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
System.out.println("a" + i);
}
return "a";
});
使用submit.get()将会阻塞线程,使用isDone()方法将会检查该任务是否执行完毕。
休眠
- sleep()方法
优先级
- 你可以通过getPriority()获取当前线程的优先级,并且在任何时刻使用setPriority()来修改它
让步
- yield()方法
后台线程
- daemon 通过调用isDaemon方法来确定线程是否是一个后台线程,如果是一个后台线程,那么它创建的任何进程都会被自动设置为后台线程。
术语
- 执行的任务和驱动它的程序有一个明显的差异,这个差异在java內库中极为明显,因为你对thread没有任何的实际控制权。
- 在物理上,创建线程可能会代价高昂,因此你必须保存并管理它们
加入一个线程
- join方法,如果在某个线程调用此方法,那么该线程将会被挂起,直到插入者执行完才继续执行。
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1====" + i);
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(10);
if (i == 5) {
t1.join();
System.out.println("t2====" + i);
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
t1.start();
t2.start();
捕获异常
- 由于线程的本质,使得你不能捕获从线程中逃逸你异常。
public class Main {
public static void main(String[] args) {
try {
ExceptionThread exceptionThread = new ExceptionThread();
exceptionThread.start();
} catch (Exception e) {
// 此处不能捕获异常
System.out.println(111);
}
}
}
class ExceptionThread extends Thread {
@Override
public void run() {
try {
throw new RuntimeException("111");
} catch (RuntimeException e) {
// 此处可以捕获异常
System.out.println(444);
}
}
}
共享受限资源
不正确的访问资源
- 多线程访问同一个资源出现的问题,例如
public class Main {
public static void main(String[] args) {
new ShareThread().start();
new ShareThread().start();
}
}
class ShareThread extends Thread {
private static int a = 100;
@Override
public void run() {
while (a != 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
a--;
System.out.println(a);
}
}
}
- 执行上述的代码出线负数
解决共享资源竞争问题
- 加锁,在需要同步的地方加上锁,这是最保险的方式,关键字synchronized
public class Main {
public static void main(String[] args) {
new ShareThread().start();
new ShareThread().start();
}
}
class ShareThread extends Thread {
private static int a = 100;
@Override
public void run() {
synchronized (ShareThread.class){
while (a != 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
a--;
System.out.println(a);
}
}
}
}
- 使用并发时,将域设置为private是非常重要的,否则synchronized就不能阻止其他地方访问。
- 针对每一个类,都以一个锁(作为每个类的Class对象的一部分)
- 同比不规则:如果你正在写一个变量,它可能接下来被另一个线程读取,或者正在读取上一次已经被另一个线程修改过的变量,那么你必须使用同步,并且读写线程必须使用相同的监视锁同步。
使用显式的Lock对象
- lock和unlock方法
public class Main {
public static void main(String[] args) {
new ShareThread().start();
new ShareThread().start();
new ShareThread().start();
new ShareThread().start();
}
}
class ShareThread extends Thread {
private volatile static int a = 100;
static Lock lock = new ReentrantLock();
@Override
public void run() {
try {
lock.lock();
while (a != 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
a--;
System.out.println(a);
}
} finally {
lock.unlock();
}
}
}
- 使用该方法的好处在于,我们对于同步的控制能力增强了,在finally的地方,我们可以自己释放锁。
原子性与易变性
- 原子性是可以应用除了double和long之外的所有基本类型操作。
- volatile关键字确保了变量的可视性,如果你讲一个域声明为volatile,那么只要对这个域进行了写操作,那么所有的读操作都可以看到这个更改。即使使用了本地缓存,情况也会如此,volatile域会被立即写入主内存中,而读操作就发生在主内存中。
原子类
- AtomicInteger AtomicLong ….
临界区
- 很多时候我们需要同步的并不是所有的线程,而是共享同一类资源的线程进行并发访问。例如,用户修改自己的金额,我们只需要对用户加锁就行了。
线程本地变量存储
- ThreadLocal的使用
终结任务
线程的状态
- 新建,当线程被创建时,它只会短暂的处于这种状态。此时它已经分配到了必须的系统资源,并执行了初始化
- 就绪,在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。
- 阻塞:线程能够运行,但是有某个条件阻止它的运行,当线程处于阻塞状态时,调度器将忽略线程,不会分配线程任何cpu时间。
- 死亡:处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到cpu时间
进入阻塞状态
- sleep
- 通过wait方法将线程挂起
- 任务在等待某个输入、输出完成
- 调用方法的锁被另外线程使用。
中断
- Thread.interrupt()方法中断
public class InterruptTest {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
System.out.println(i);
if (i == 5) {
List<Runnable> runnables = service.shutdownNow();
System.out.println(runnables);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(()->{
System.out.println("执行");
});
service.execute(()->{
System.out.println("任务3");
});
}
}
- 中断并不能立即打断程序运行
- 停止接收外部的任务(第二个任务并不能运行)
- 忽略正在等在执行的任务
返回未执行的任务列表
- 被reentranlock阻断的任务具有被中断的能力,这个方式并不实用
检查中断
当你在线程上调用interrupt方法时,中断发生唯一时刻是任务要进入道阻塞操作中,或者是已经在阻塞操作内部时.
线程之间的协作
wait()和notifyAll()
public class WaitThreadTest {
public static Object lock = new Object();
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
lock.notifyAll();
}
}
}
}.start();
new Thread() {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
lock.notifyAll();
}
}
}
}.start();
}
}
- 此方法有巨大的弊端,他每次都必须唤醒所有的线程
- 如果是三个或者以上的线程通信这个并不合适
- 调用该方法的对象必须是锁对象
- 在多个任务实用同一个锁的时候,多个任务各自等待锁的释放
- 惯用的方法实用while就可以编写这种变化
notify和notifyAll的区别
- 实用notify唤醒众多任务中的一个
- notifyAll唤醒所有wait的任务
释放锁的条件
- 占有锁的线程执行完代码,然后释放对锁的占有
- 线程执行发生异常,此时jvm将会自动释放锁
- 调用wait方法在等待的时候立即释放锁,方便其他线程使用锁
使用显式的lock和condition对象
- 需求:我们在0到100中,我们依次输出 我和你
public class ConditionTest {
static Lock lock = new ReentrantLock();
static Condition c0 = lock.newCondition();
static Condition c1 = lock.newCondition();
static Condition c2 = lock.newCondition();
volatile static int i = 0;
public static void main(String[] args) {
new Thread(() -> {
try {
lock.lock();
for (; i < 100; ) {
int value = i % 3;
if (value != 0) {
c0.await();
}
System.out.println("我");
i++;
c1.signal();
}
} catch (Exception e) {
System.out.println(e);
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
for (; i < 100; ) {
int value = i % 3;
if (value != 1) {
c1.await();
}
System.out.println("和");
i++;
c2.signal();
}
} catch (Exception e) {
System.out.println(e);
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
for (; i < 100; ) {
int value = i % 3;
if (value != 2) {
c2.await();
}
System.out.println("你");
i++;
c0.signal();
}
} catch (Exception e) {
System.out.println(e);
} finally {
lock.unlock();
}
}).start();
}
}
多线程死锁
- 多线程通信的时候采用了嵌套synchronized或者其他情况,只要是一个线程没有执行完代码,被阻塞住了,就非常容易产生死锁的情况
public class DealLockTest {
static String a = "1";
static String b = "2";
static String c = "3";
public static void main(String[] args) {
new Thread(() -> {
while (true) {
synchronized (a) {
System.out.println("拿到a等待b");
synchronized (b) {
System.out.println("a和b都拿到了开始吃饭");
}
}
}
}).start();
new Thread(() -> {
while (true) {
synchronized (c) {
System.out.println("拿到c等待a");
synchronized (a) {
System.out.println("c和a都拿到了开始吃饭");
}
}
}
}).start();
new Thread(() -> {
while (true) {
synchronized (b) {
System.out.println("拿到b等待c");
synchronized (c) {
System.out.println("b和c都拿到了开始吃饭");
}
}
}
}).start();
}
}
- 在上述例子中, 使用了嵌套锁,这样的锁不能及时的释放,而其他方法又使用了这个锁
新类库的构件
CountDownLatch
需求:假设我们主线程需要等待两个线程执行完毕才执行下面的方法,这个时候我们可以考虑使用CountDownLatch
它用来同步一个或者多个任务,强制它们等待有其他任务执行的一组操作完成
public class CountDownTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
System.out.println("thread1");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("thread2");
latch.countDown();
}).start();
latch.await();
System.out.println(latch.getCount());
}
}
CyclicBarrier
- 同步多个线线程的操作,假如有多个任务它们需要执行的时候进行等待,那么此方法适合
public class CyclicBrarric {
public static void main(String[] args) throws InterruptedException {
int n = 4;
CyclicBarrier barrier = new CyclicBarrier(n);
for (int i = 0; i < n; i++) {
new Thread(() -> {
try {
System.out.println("回教室");
barrier.await();
System.out.println("坐下");
barrier.await();
System.out.println("听课");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
- 其中的await方法可以设置等待时间,如果超过await方法设置的等待时间,那么线程组也会继续执行下去。
public class CyclicBrarric {
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
int n = 4;
CyclicBarrier barrier = new CyclicBarrier(n);
for (int i = 0; i < n; i++) {
new Thread("t" + i) {
@Override
public void run() {
try {
System.out.println("回教室");
barrier.await();
System.out.println("坐下");
if (!Thread.currentThread().getName().equals("t3")) {
Thread.sleep(2000);
}
barrier.await(1, TimeUnit.SECONDS);
System.out.println("听课");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
- 注意:这个await的时间是从第一个执行到这里的线程开始计时的,如果程序执行超过了这个时间,所有线程都抛出异常,进行终止
DelayQueue
- 这是一个无界的BlockingQueue,用于放置视线了Delayed接口的对象,其中的对象只能在其到期才能取走。这种队列是有序的,即队头对象的延迟到期时间最长。
public class DelayTest {
public static void main(String[] args) throws InterruptedException {
DelayQueue<DelayTask> queue = new DelayQueue<>();
queue.add(new DelayTask("1", 1000L, TimeUnit.MILLISECONDS));
queue.add(new DelayTask("2", 2000L, TimeUnit.MILLISECONDS));
queue.add(new DelayTask("3", 3000L, TimeUnit.MILLISECONDS));
while (!queue.isEmpty()) {
try {
DelayTask task = queue.take();
System.out.println(task.name + ":" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class DelayTask implements Delayed {
// 名字
String name;
// 延迟执行时间
Long time;
//执行时间单位
TimeUnit timeUnit;
DelayTask(String name, Long time, TimeUnit timeUnit) {
this.name = name;
this.time = time + System.currentTimeMillis();
this.timeUnit = timeUnit;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(time - System.currentTimeMillis(), timeUnit);
}
@Override
public int compareTo(Delayed o) {
long value = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
if (value > 0) {
return 1;
} else if (value < 0) {
return -1;
}
return 0;
}
}
PriortyBlockingQueue
- 这是一个很基础的优先级队列,它具有可阻塞的读取操作。
public class PriorityBlockingQueueTest {
public static void main(String[] args) {
PriorityBlockingQueue<Integer> queue =new PriorityBlockingQueue<>(10, (o1, o2) -> o2-o1);
queue.add(1);
queue.add(2);
queue.add(3);
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
ScheduledThreadPoolExecutor对象
- 延迟任务
public class ScheduledThreadTest {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
executor.schedule(()->{
System.out.println(111);
},5000, TimeUnit.MILLISECONDS);
System.out.println(2222);
}
}
Semaphore
Semaphore也叫做信号量,可以用来控制同时访问特定资源的线程的数量通过协调各个线程,以保证合理的使用资源
访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程一直阻塞,直到获得许可为止
- 访问资源后,使用release释放资源
public class SemaphoreTest {
public static void main(String[] args) {
new SemRunnable().start();
new SemRunnable().start();
new SemRunnable().start();
new SemRunnable().start();
new SemRunnable().start();
}
}
class SemRunnable extends Thread {
static Semaphore semaphore = new Semaphore(1);
@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行等待");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
semaphore.release();
}
}
- 可以控制同时执行的有多少个线程,如果设置为1,那么相当于使用synchronized
Exchanger
- 如果两个线程需要进行数据交换,那么Exchanger将是一个很好的选择
public class ExchangerTest {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
try {
String a = "a";
String exchange = exchanger.exchange(a);
System.out.println("a"+exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
String b = "b";
String exchange = exchanger.exchange(b);
System.out.println(exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
- 上述代码的字符串将会进行交换