并发
1、线程状态
创建、就绪、阻塞、限时等待、运行、死亡
2、创建线程的几种方法
- 实现Runnable接口
- 是继承Thread类
- 实现Callable接口
Callable创建线程(传给FutureTask,FutureTask再传给Thread)
public class MyCallable implements Callable<Integer>
{
public Integer call()
{
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException
{
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new
FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
3、Executor框架
Executor框架实现的就是线程池的功能。
1、Executor框架包括3大部分:
- 任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;
- 任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。
- 异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。
主要有三种Executor: - CachedThreadPool:一个任务创建一个线程
- FixedThreadPool:所有任务只能创建固定大小的线程
- SingleThreadExecutor:相当于大小为1的FixedThreadPool
public static void main(String[] args)
{
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
{
executorService.execute(new MyRunnable());//执行
}
executorService.shutdown();
}
守护线程(Daemon)
- 守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
- 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main() 属于非守护线程。
使用 setDaemon() 方法将一个线程设置为守护线程。
public static void main(String[] args)
{
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}
yield()
对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
4、中断
interrupt(设置标志位,设完interrupted()返回true)
interrupt()可以用来中断线程,如果该线程处于阻塞、限期等待或者无限期等待状态,就会抛出interruptedException,从而提前结束该线程。
调用interrupt之后,会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。
public class InterruptExample
{
private static class MyThread2 extends Thread
{
@Override
public void run()
{
while (!interrupted())
{
// ..
}
System.out.println("Thread end");
}
}
}
public static void main(String[] args) throws InterruptedException
{
Thread thread2 = new MyThread2();
thread2.start();
thread2.interrupt();
}
}
Executor 的中断操作
- 中断多个线程:调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。
- 中断一个线程:如果只想中断Executor中的一个线程,可以通过使用submit方法提交一个线程,返回Future对象,通过该对象的cancle(true)方法可以中断线程
Future future = executorService.submit(()->{
//...
});
future.cancle(true);
5、互斥同步
同步同一个对象
- 同步代码块,比如synchronized(this){}
synchronized(this) //作用于一个对象,如果是两个对象的同步代码块,就不会同步。
{
//.......
}
- 同步一个普通方法
public synchronized void func()
{
//.......
}
同步一个类
- synchronized(Test.class){}
- 同步一个静态方法
public synchronized static void fun()
{
//......
}
6、synchronized和ReentrantLock的比较
- 1、synchronized是JVM实现的,ReentrantLock是JDK实现的。
- 2、中断:ReentrantLock可以中断,而synchronized不可以中断
- 3、公平:ReentrantLock既可以实现公平锁,也可以实现非公平锁。而synchronized只能实现非公平锁。
- 4、一个ReentrantLock可以同时绑定多个Condition对象。
7、线程之间的协作
join()
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
比如虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继 续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
wait() notify() notifyAll()
它们都属于 Object 的一部分,而不属于 Thread。
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException。
wait()挂起的时候,线程会释放锁。
await() signal() signalAll()
ReentrantLock通过Condition类来实现线程之间的协调,可以在Condition上调用await()方法使线程等待,其他线程调用signal或者signalAll方法唤醒等待的线程。
相比于wait,await可以指定等待的线程,因此更加灵活。
wait和sleep的区别
- wait是Object的方法,而sleep()是Thread的静态方法
- wait()会释放锁,sleep不会
8、AQS
JUC的核心
CountDownLatch
用来控制一个线程等待多个线程。
维护一个计数器,每次调用countDown()方法计数器的值就会减去1,减到0的时候,那写因为await()方法而等到的线程就会被唤醒。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
class Student
{
CountDownLatch countDownLatch;
Student(CountDownLatch countDownLatch)
{
this.countDownLatch = countDownLatch;
}
public void output()
{
countDownLatch.countDown();
System.out.println(111);
}
}
public class Test2
{
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
Student student = new Student(countDownLatch);
for(int i = 0;i<6;i++)
{
new Thread(new Runnable() {
@Override
public void run() {
student.output();
}
}).start();
}
countDownLatch.await();
System.out.println("结束");
}
}
输出为
111
111
111
111
111
111
结束
CyclicBarrier
用来控制多个线程之间相互等待,只有当多个线程都到达的时候,这些线程才会继续执行。
CyclicBarrier也会维护一个计数器,线程执行await()方法之后计数器的值就会减去1,并进行等待,直到为0,所有调用await()方法而在等待的线程才会继续执行。
CyclicBarrier和CountDownLatch的一个区别是:CyclicBarrier的计数器可以通过reset()方法循环使用,而CountDownLatch不可以。
Semaphore信号量
Semaphore类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
ForkJoin
主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。
ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线程都维护了一个双端队列,用来存储需要执行的任 务。工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。窃取的任务必须是最晚的任务,避 免和队列所属线程发生竞争。例如下图中,Thread2 从 Thread1 的队列中拿出最晚的 Task1 任务,Thread1 会拿出 Task2 来执行,这样就避免发生竞争。但是如果队列中只有一个任务时还是会发生竞争。