一:并发编程
1. 基本概念
(1)同步和异步
- 同步:同步方法一旦调用,调用者必须等待方法调用返回后,才能继续后面的行为
- 异步:异步方法在另外一个线程执行,方法调用立即返回,调用者可以继续后面的操作,操作完成后通知调用者,返回结果
(2)并发与并行
- 并发(1个CPU):多个任务交替进行(可能串行)
- 并行(多个CPU):多个任务同时进行
(3)临界区
表示一个公共资源,可以被多个线程使用。同一时刻只能被一个线程使用。
(4)阻塞与非阻塞
- 阻塞:线程等待临界区资源,被挂起
- 非阻塞:所有线程都会尝试不断前行
(5)死锁与活锁
- 死锁:线程之间彼此占有对方需要的资源,却不释放,还去请求对方的资源,导致多个线程无法获取完整资源而等待
- 活锁:线程之间彼此占有对方需要的资源,都想要释放,并且去请求对方的资源,导致多个线程无法获取完整资源而等待
- 饥饿:线程由于某些原因无法获取资源,导致一直无法执行
2. 并发级别
(1)阻塞(悲观策略)
在其他线程释放资源之前,当前线程无法继续执行
(2)无饥饿
- 对于非公平锁:系统允许高优先级的线程先执行,导致低优先级线程饥饿
- 对于公平锁:按照先来先服务原则,所有线程都有机会执行
(3)无障碍(乐观策略)
认为线程之间不会发生冲突,多个线程可以没有障碍的进入临界区。如果检测到数据异常,则回滚自己的操作,确保数据安全(可能导致没有线程离开临界区)
一致性标记
- 线程在操作之前,读取并保存标记
- 如果需要更改临界区数据,则需要更新该标记,告诉其他线程不安全
- 操作完成后,再次读取,检查标记是否被改变。如果一致则证明没有冲突,如果不一致则证明有冲突需要重试
(4)无锁
在无障碍的基础上,要求必然有一个线程能够在有限时间内离开临界区(可能导致重试的线程出现饥饿)
(5)无等待
在无锁的基础上,要求所有线程都能够在有限时间内离开临界区
RCU
- 读操作:不加控制
- 写操作:通过修改数据的副本,在合适的时机写回数据
3. 并发定律
(1)Amdahl定律
为了提高系统速度,需要在增加CPU处理器的同时,提高系统内可并行化的模块比重
(2)Gustafson定律
如果串行化比例很小,并行化比例很大,则增加CPU处理器就能提高系统的速度
4. Java的内存模型JMM
(1)原子性
一个操作是不可中断的,不会被其他线程干扰。
(对于32位虚拟机,如果对64位的数据进行操作,则不是原子性的)
(2)可见性
当一个线程修改了一个共享变量的值,其他线程立即知道这个修改
(缓存优化、硬件优化、指令重排、编辑器优化可能导致操作不可见)
(3)有序性
程序按顺序执行
指令重排
可以保证串行语义的一致性,但无法保证多线程语义的一致性
可以减少中断流水线的次数
Happen-Before原则
- 程序顺序原则(单个线程内)
- volatile原则(写先于读)
- 锁原则(解锁先于随后的加锁)
- 传递性
- 线程的start() 先于它的每一个动作
- 线程的每一个动作先于它的终结
- 线程的中断interrupt()先于被中断线程的代码
- 对象的构造函数先于它的finalize()
二:Java的并行设计
1. 线程的生命周期
- 新建 New:创建完成,还没有启动
- 运行 Runable:就绪或正在执行
- 无限等待 Waiting:等待被其他线程唤醒
- 限期等待 Timed Waiting:等待一段时间后自动唤醒
- 阻塞 Blocked:等待获取排他锁
- 结束 Terminated:线程结束执行
2. 线程的基本操作
(1)新建
不使用thread.run()方法开启线程,它只会串行执行run()方法。
应该使用thread.start();方法新建线程
通过继承创建:
public class MyThread{
static class Thread1 extends Thread {
@Override
public void run() {
System.out.println("通过继承的方法创建线程");
}
}
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
thread1.start();
}
}
通过匿名内部类创建:
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("通过匿名内部类的方法创建线程");
}
};
thread.start();
}
通过实现接口创建:
public class MyThread{
static class Thread2 implements Runnable{
@Override
public void run() {
System.out.println("通过接口的方法创建线程");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new Thread2());
thread.start();
}
}
(2)睡眠
通过Thread.sleep(时间),让线程休眠若干时间,如果在休眠中被中断,则会抛出InterruptedException
睡眠中仍然持有资源,无法被其他线程唤醒!
(3)终止
不用使用thread.stop()方法,会直接结束线程,导致数据不一致。
需要自己决定何时让线程结束(状态量)
public class MyThread {
static class Thread1 extends Thread {
//标志结束的信号
private boolean stop;
public void stopMe() {
stop = true;
}
@Override
public void run() {
while (true) {
System.out.println("实现线程");
try {
//让线程慢一点,容易观察
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//接收到结束信号,停止循环
if (stop) {
System.out.println("结束了");
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread = new Thread1();
thread.start();
// thread.stop();
//等待1秒后,让线程停止
Thread.sleep(1000);
thread.stopMe();
}
}
(4)中断
告诉系统应该退出,由系统决定
-
设置中断标志位:thread.interrupt(); (类似thread.stopMe();)
-
检查中断标志位:Thread.currentThread().isInterrupted();(类似判断语句if(stop))
-
检查中断标志位并清零:Thread.interrupted();
特别之处:
中断方法不仅提醒系统结束,也能处理睡眠或等待的特殊情况
public class MyThread {
static class Thread1 extends Thread {
private boolean stop;
public void stopMe() {
stop = true;
}
@Override
public void run() {
while (true) {
System.out.println("实现线程");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//抛出异常的同时会清除中断标记
System.out.println("我在睡觉你还中断我?");
//再次设置标记
Thread.currentThread().interrupt();
}
if (Thread.currentThread().isInterrupted()) {
System.out.println("结束了");
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread = new Thread1();
thread.start();
// thread.stop();
Thread.sleep(1000);
// thread.stopMe();
thread.interrupt();
}
}
(5)等待和通知
- 线程A获取对象xx的监视器,调用了xx.wait()方法,则线程A停止运行,进入xx对象的等待队列,释放对象xx的监视器
- 其他线程获取对象xx的监视器,调用了xx.notify()方法,则从xx对象的等待队列中随机选择线程并唤醒,释放对象xx的监视器
- 其他线程获取对象xx的监视器,调用了xx.notifyAll()方法,则唤醒xx对象的等待队列中的所有线程,释放对象xx的监视器
等待时会释放所持资源,可以被其他线程唤醒!
public class WaitAndNotify {
final static Object object = new Object();
static class ThreadA extends Thread{
private String name;
public ThreadA(String name) {
super(name);
this.name = name;
}
@Override
public void run() {
synchronized (object){
System.out.println(name+"持有了对象");
try {
System.out.println("对象想等待了");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"又持有了对象");
}
}
}
static class ThreadB extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println("B持有了对象");
System.out.println("对象准备好了");
object.notifyAll();
}
}
}
public static void main(String[] args) {
//一个线程等待
// ThreadA a = new ThreadA("A");
// a.start();
// ThreadB b = new ThreadB();
// b.start();
//多个线程等待
ThreadA[] as = new ThreadA[5];
for (int i = 0; i < 5; i++) {
as[i] = new ThreadA("A"+i);
as[i].start();
}
ThreadB b = new ThreadB();
b.start();
}
}
(6)挂起和继续执行
- 不应该使用thread.suspend()挂起线程,因为资源无法被释放
- 不应该使用thread.resume()唤醒线程,如果在挂起前唤醒,则线程将永久挂起
public class SuspendAndResume {
static class ThreadA extends Thread {
private boolean suspend = false;
public void suspendMe() {
System.out.println("挂起线程");
suspend = true;
}
//唤醒线程——通知
public void resumeMe() {
System.out.println("唤醒线程");
suspend = false;
synchronized (this) {
notify();
}
}
@Override
public void run() {
while (true) {
synchronized (this) {
//被挂起了——等待
while (suspend) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程A开始工作");
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA a = new ThreadA();
a.start();
Thread.sleep(1000);
a.suspendMe();
Thread.sleep(1000);
a.resumeMe();
}
}
(7)等待结束
通过thread.join(),一个线程需要使用等待依赖的线程thread执行完毕,才能继续执行
- join():无限等待,直到目标线程完成
- join(时间):等待一段时间,如果目标线程还没有完成,则继续进行
public class Join {
static volatile int i = 0;
static class ThreadA extends Thread{
@Override
public void run() {
for (i = 0; i < 10; i++) {
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA a = new ThreadA();
a.start();
a.join();
System.out.println(i);
}
}
(8)谦让
通过Thread.yield(),当前线程让出CPU,再重新进行争夺。
适合一个低优先级、占用CPU资源多的线程
3. Java的关键字
(1)volatile关键字
可以保证数据的可见性和有序性,不能保证原子性
(2)synchronized关键字
确保操作的原子性,线程之间的有序性和可见性
- 指定加锁对象:进入同步块之前需要获得给定对象的锁
- 作用于实例方法:进入同步块之前需要获得实例对象的锁
- 作用于静态方法:进入同步块之前需要获得当前类的锁
4. 线程的优先级
Java中使用1到10或者3个静态变量表示优先级,一般高优先级在竞争资源时更有优势
- Thread.MIN_PRIORITY
- Thread.NORM_PRIORITY
- Thread.MAX_PRIORITY
thread.setPriority(优先级);
5. 守护线程Daemon
- 守护线程:在后台完成系统性的任务(如果只剩下守护线程,JVM会自然退出)
- 用户线程:完成程序的业务操作
public class DaemonTest {
static class Daemon extends Thread{
@Override
public void run() {
while (true){
System.out.println("我在进行后台工作...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Daemon();
//设置守护线程
thread.setDaemon(true);
thread.start();
//休眠3秒后,主线程结束,所以守护线程也结束了
Thread.sleep(3000);
}
}
6. 线程组
如果线程数量多,并且功能明确,则可以把相同功能的线程放置在一个线程组中
创建
- 线程组:ThreadGroup group = new ThreadGroup(线程组名);
- 加入线程:Thread thread = new Thread(group,线程接口,线程名);
使用
- 获取活动线程个数: System.out.println(“当前活跃线程:”+group.activeCount());
- 获取线程组的线程个数:group.list()
public class ThreadGroupTest {
static class Group implements Runnable{
@Override
public void run() {
String group = Thread.currentThread().getThreadGroup().getName();
String name = Thread.currentThread().getName();
while (true){
System.out.println(group+"--"+name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("Test");
Thread t1 = new Thread(group,new Group(),"Thread1");
Thread t2 = new Thread(group,new Group(),"Thread2");
t1.start();
t2.start();
System.out.println("当前活跃线程:"+group.activeCount());
group.list();
}
}
7. Java的集合
用Vector代替ArrayList
用ConcurrentHashMap代替HashMap