【备战秋招】Java并发

线程

状态转换

在这里插入图片描述

  • 新建(New)
    创建后尚未启动。
  • 可运行(Runnable)
    可能正在运行,也可能正在等待 CPU 时间片
    包含了操作系统线程状态中的 Running 和 Ready。
  • 阻塞(Blocked)
    等待获取一个排它锁,如果其线程释放了锁就会结束此状态。
  • 无限期等待(Waiting)
    在这里插入图片描述
    等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
  • 限期等待(Timed Waiting)
    无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
    调用 Thread.sleep()方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
    调用Object.wait()方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
    阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep()Object.wait()等方法进入。
  • 死亡(Terminated)
    可以是线程结束任务之后自己结束,或者产生了异常而结束。

线程使用

  • 实现Runnable接口
public class Thread01 implements Runnable{
    @Override
    public void run() {
        System.out.println("Implements Runnable,Override run");
    }
    
    public static void main(String[] args) {
        System.out.println("Main Start");
        new Thread(()->{
             System.out.println("Lambda Start");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Lambda End");
         }).start();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main End");
    }
}
Main Start
Lambda Start
Lambda End
Main End
  • 实现Callable接口
public class Thread02 implements Callable<Boolean> {
    @Override
    public Boolean call() throws Exception {
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Thread01 t0 = new Thread01();
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<Boolean> future = executor.submit(t0);
        Object result = future.get();
        System.out.println(result);
        executor.shutdown();
    }
}
  • 继承Thread类
public  class Thread03  extends Thread{
    @Override
    public void run() {
        System.out.println("extends Thread,Override RUN");
   }

    public static void main(String[] args) {
        Thread03 thread = new Thread03();
        thread.start();
        thread.setPriority(1);
        System.out.println(thread.getState().toString());
    }
}

基础线程机制

Executor

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

  • CachedThreadPool:一个任务创建一个线程;
  • FixedThreadPool:所有任务只能使用固定大小的线程;
  • SingleThreadExecutor:相当于大小为 1 的FixedThreadPool

submit()提交一个线程,它会返回一个 Future<?>对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

调用 Executorshutdown()方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow()方法,则相当于调用每个线程的 interrupt()方法。

Daemon

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

public static void main(String[] args) { 
	Thread thread = new Thread(new MyRunnable());      
	thread.setDaemon(true); 
} 

sleep()

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec单位为毫秒。
sleep()可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞限期等待或者无限期等待状态,那么就会抛出InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞synchronized 锁阻塞

public class InterruptExample { 
	private static class MyThread1 extends Thread { 
		@Override 
		public void run() { 
		   try { 
			 Thread.sleep(2000); 
		     System.out.println("Thread run"); 
		    } catch (InterruptedException e) { 
		      e.printStackTrace(); 
            } 
      } 
    } 
 }
public static void main(String[] args) throws InterruptedException {
	Thread thread1 = new MyThread1(); 
	thread1.start(); 
	thread1.interrupt(); 
	System.out.println("Main run"); 
}

yield()

对静态方法 Thread.yield()的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

public void run() { 
	Thread.yield(); 
}

interrupted()

调用interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回true。因此可以在循环体中使用interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

public class InterruptExample { 
	private static class MyThread2 extends Thread { 
		@Override
		public void run() { 
		  while (!interrupted()) { 
				// .. 
		 }
		System.out.println("Thread end"); 
		} 
	} 
}

互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是JDK 实现的 ReentrantLock

synchronized

同步代码块
只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

public void func() { 
	synchronized (this) { 
	// ... 
	} 
}

同步方法

public synchronized void func () { 
	// ... 
}

同步类
作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。

public void func() { 
	synchronized (SynchronizedExample.class) { 
		// ... 
	} 
}

同步静态方法
作用于整个类

public synchronized static void fun() { 
    // ... 
}

ReentrantLock

public class LockExample { 
      private Lock lock = new ReentrantLock(); 
      public void func() { 
           lock.lock(); 
           try {
              for (int i = 0; i < 10; i++) { 
                  System.out.print(i + " "); 
              } 
          } finally { 
               lock.unlock(); 
              // 确保释放锁,从而避免发生死锁。 
          } 
    }
}

除非需要使用ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一
种锁机制,JVM 原生地支持它,而ReentrantLock不是所有的 JDK 版本都支持。

synchronizedReentrantLock
JVM 实现JDK 实现
优化:自旋锁synchronized 与 ReentrantLock 大致相同
synchronized 不行ReentrantLock 可中断
非公平默认情况下也是非公平的,但是也可以是公平的
ReentrantLock 可以同时绑定多个 Condition 对象

线程协作

join()

在线程中调用另一个线程的 join()方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify()或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify()或者 notifyAll() 来唤醒挂起的线程,造成死锁。

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而sleep() 是 Thread 的静态方法;
  • wait()会释放锁,sleep() 不会。

await() signal() signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在Condition上调用await()方法使线程等待,其它线程调用 signal()signalAll() 方法唤醒等待的线程。
相比于 wait()这种等待方式,await() 可以指定等待的条件,因此更加灵活。

使用 Lock 来获取一个 Condition 对象。

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
try{
   condition.await();
}catch (InterruptedException e) {
   e.printStackTrace();
}finally { 
   lock.unlock(); 
}

JUC

java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J.U.C 的核心。

CountDownLatch

用来控制一个线程等待多个线程。

维护了一个计数器cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方
法而在等待的线程就会被唤醒
在这里插入图片描述
在这里插入图片描述

CyclicBarrier

用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行.

线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。

CyclicBarrierCountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。

public CyclicBarrier(int parties, Runnable barrierAction) { 
	if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; 
	this.count = parties; 
	this.barrierCommand = barrierAction; 
}

public CyclicBarrier(int parties) {
	 this(parties, null); 
 }

在这里插入图片描述

Semaphore

Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数
在这里插入图片描述

FutureTask

在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future 进行封装。FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。

public interface RunnableFuture<V> extends Runnable, Future<V>

FutureTask 可用于异步获取执行结果或取消执行任务的场景。

FutureTask<Integer> futureTask = new FutureTask<Integer>(
	new Callable<Integer>() { 
	@Override public Integer call() throws Exception { 
		int result = 0;
		for (int i = 0; i < 100; i++) { 
			Thread.sleep(10); 
			result += i; 
		}
		return result; 
	} 
}); 
	Thread computeThread = new Thread(futureTask); 
	computeThread.start();
	System.out.println(futureTask.get()); 
	} 
}

BlockingQueue

java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现:

  • FIFO 队列LinkedBlockingQueueArrayBlockingQueue(固定长度)
  • 优先级队列PriorityBlockingQueue
    提供了阻塞的 take()put()方法:如果队列为空 take() 将阻塞,直到队列中有内容;如果队列为满 put() 将阻塞,直到队列有空闲位置。

ForkJoin

主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。

	ForkJoinExample leftTask = new ForkJoinExample(first, middle); 
	ForkJoinExample rightTask = new ForkJoinExample(middle + 1, last); 
	leftTask.fork(); 
	rightTask.fork(); 
	result = leftTask.join() + rightTask.join();

main()

public static void main(String[] args) throws ExecutionException, InterruptedException { 
	ForkJoinExample example = new ForkJoinExample(1, 10000); 
	ForkJoinPool forkJoinPool = new ForkJoinPool(); 
	Future result = forkJoinPool.submit(example); 
	System.out.println(result.get()); 
}

ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数。
在这里插入图片描述

JMM

处理器上的寄存器的读写的速度比内存快几个数量级,为了解决这种速度矛盾,在它们之间加入了高速缓存

加入高速缓存带来了一个新的问题:缓存一致性。如果多个缓存共享同一块主内存区域,那么多个缓存的数据可能会不一致,需要一些协议来解决这个问题.

在这里插入图片描述
内存间交互操作
在这里插入图片描述
内存模型三大特性

  • 原子性
    AtomicInteger 能保证多个线程修改的原子性。
    在这里插入图片描述

  • 可见性
    可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的
    volatile
    synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。
    final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。

  • 有序性
    有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因
    为发生了指令重排序。
    volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。

线程安全

不可变

不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态

  • final 关键字修饰的基本数据类型
  • String
  • 枚举类型
  • Number 部分子类,如 LongDouble 等数值包装类型,BigInteger 和 BigDecimal 等大数据类型。但同为Number 的原子类 AtomicIntegerAtomicLong 则是可变的。

互斥同步

悲观
synchronizedReentrantLock

非阻塞同步

  • CAS
    先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止).
    比较并交换Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。
  • AtomicInteger
public final int incrementAndGet() { 
  return unsafe.getAndAddInt(this, valueOffset, 1) + 1; 
}

在这里插入图片描述
var1 指示对象内存地址,var2 指示该字段相对对象内存地址的偏移,var4 指示操作需要加的数值,

  • ThreadLocal
    在这里插入图片描述
    每个 Thread 都有一个 ThreadLocal.ThreadLocalMap对象。

锁优化

这里的锁优化主要是指 JVM 对 synchronized 的优化。

自旋锁
自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态。

锁消除
锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除。
锁消除主要是通过逃逸分析来支持,如果堆上的共享数据不可能逃逸出去被其它线程访问到,那么就可以把它们当成私有数据对待,也就可以将它们的锁进行消除。

锁粗化
如果一系列的连续操作都对同一个对象反复加锁和解锁,频繁的加锁操作就会导致性能损耗。。如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部。

轻量级锁
JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:无锁状态(unlocked)偏向锁状态(biasble)轻量级锁状态(lightweight locked)重量级锁状态(inflated)

轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁

偏向锁
偏向锁的思想是偏向于让第一个获取锁对象的线程,这个线程在之后获取该锁就不再需要进行同步操作,甚至连 CAS操作也不再需要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值