并发编程
1.进程与线程
1.1进程与线程
进程
- 进程是一个具有一定独立功能的程序在一个数据集合上依次动态执行的过程。
- 进程是一个正在执行的程序的实例,包括程序计数器、寄存器和程序变量的当前值
- 进程作为资源分配的最小单位
进程具有的特征
- **动态性:**进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的.
- **并发性:**任何进程都可以同其他进程一起并发执行的
- **独立性:**进程是系统进行资源分配和调度的一个独立单位
- **结构性:**进程由程序,数据和进程控制块三部分组成
线程
- 一个进程内可以分为多个线程
- 一个线程就是一个指令流,将指令流中一条条指令以一定的顺序交给CPU执行
- Java中,线程作为最小调度单位
1.2并行和并发
并发
- 一般会将这种线程轮流使用CPU的做法称为并发
并行
- 多核CPU下,每个核都可以调度运行线程,这时候线程可以是并行的
总结
- 并发就是同一时间应对多件事情的能力
- 并行就是同一时间做多件事情的能力
2.Java线程
2.1 创建和运行线程
方法一,继承Thread
@Slf4j
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
log.info(Thread.currentThread().getName()+"在执行");
}
};
thread.start();
System.out.println(Thread.currentThread().getName());
}
}
方法二,使用Runnable配合Thread
@Slf4j
public class ThreadDemo1 {
public static void main(String[] args) {
Runnable runnable = () -> log.info(Thread.currentThread().getName()+"runnable线程正在执行");
Thread thread = new Thread(runnable);
thread.start();
}
}
方法三,使用FutureTask配合Thread
FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况
@Slf4j
public class ThreadDemo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask(() -> {
log.info("{}", Thread.currentThread().getName());
return 1;
});
Thread thread = new Thread(futureTask, "t1");
thread.start();
System.out.println(futureTask.get());
}
}
2.2查看线程
- ps -ef | grep java 查看java进程
- top -H -p pid 查看java中所有进程的线程信息
- jstack 查看某个java进程中线程的运行情况
2.3run和start
run调用
@Slf4j
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread() {
@Override
public void run() {
log.info("{}", Thread.currentThread().getName() + "正在运行");
}
};
thread1.run();
log.info("{}","主线程运行完了");
}
}
输出
14:50:42.475 [main] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo2 - main正在运行
14:50:42.478 [main] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo2 - 主线程运行完了
程序输出主线程名字在调用
start调用
@Slf4j
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread() {
@Override
public void run() {
log.info("{}", Thread.currentThread().getName() + "正在运行");
}
};
thread1.start();
log.info("{}","主线程运行完了");
}
}
输出
14:52:14.939 [main] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo2 - 主线程运行完了
14:52:14.939 [Thread-0] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo2 - Thread-0正在运行
总结:
- 调用run()方法并不是启动一个线程,而是以主线程调用的方法
- 使用start()启动一个新线程,通过新线程间接的运行run方法中的代码
2.4join方法
作用:join()方法的作用就是叫主线程等待子线程执行结束之后在运行主线程
案例
@Slf4j
public class ThreadDemo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
log.info("线程一正在运行");
for (int i = 0; i < 10; i++) {
log.info("线程一正在执行"+i);
}
});
Thread thread1 = new Thread(() -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程二正在运行");
for (int i = 0; i < 10; i++) {
log.info("线程二正在执行"+i);
}
});
thread.start();
thread1.start();
thread1.join();
log.info("main线程运行完毕");
}
}
输出
15:51:55.496 [Thread-0] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程一正在运行
15:51:55.498 [Thread-0] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程一正在执行0
15:51:55.498 [Thread-0] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程一正在执行1
15:51:55.498 [Thread-0] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程一正在执行2
15:51:55.498 [Thread-0] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程一正在执行3
15:51:55.498 [Thread-0] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程一正在执行4
15:51:55.498 [Thread-1] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程二正在运行
15:51:55.498 [Thread-1] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程二正在执行0
15:51:55.498 [Thread-1] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程二正在执行1
15:51:55.498 [Thread-1] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程二正在执行2
15:51:55.498 [Thread-1] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程二正在执行3
15:51:55.498 [Thread-1] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - 线程二正在执行4
15:51:55.498 [main] INFO com.example.lhdemojuc.demoNewThread.ThreadDemo3 - main线程运行完毕
2.5主线程和守护线程
定义:
守护线程是指当所有的非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束
@Slf4j
public class ThreadDemo5 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("{}", "守护线程正在运行");
}
});
thread.setDaemon(true);
thread.start();
Thread.sleep(1000);
log.info("{}", "主线程运行完了");
}
}
应用
- 垃圾回收器线程就是一种守护线程
2.6线程的五种状态
- 新建:就是使用new方法,new出来的线程
- 就绪:就是调用start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢到CPU资源,谁开始执行
- 运行状态:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义线程的功能
- 阻塞状态:在运行状态可能因为某些原因导致运行状态的线程变成了阻塞状态,比如 sleep(),wait()之后线程进入阻塞状态,这时候需要其他机制(调用notify或者notifyAll()方法)将处于阻塞状态的线程唤醒,唤醒的线程并不会立刻执行,需要等待cpu分配新的资源
- 终止状态:线程可能正常执行完毕或者出现异常导致结束,那么线程就要被销毁,释放资源.
3.共享模型之管程
1.共享问题
当多个线程同时修改一个变量时,线程A对数据做修改没有写入主内存时,此时线程B得到CPU的使用权对数据做修改并写入主内存,A线程在把它修改的数据写入主内存,此时数据则是A写入的,并不是线程B写入的数据,这时就是线程安全问题和共享数据的问题.
临界区
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码为临界区
2.synchronized
作用:
- **原子性:**一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就不都执行.
- **可见性:**可见性是指多个线程访问一个资源时,该资源的状态,值信息等对于其他线程都是可见的.(synchronized对一个类或者对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对其他线程是可见的)
- **有序性:**有序性指程序执行的顺序按照代码先后执行.
使用:
-
作用于方法上:锁的是当前对象实例的锁
class Ranmo { private int count = 0; //锁的是当前对象的锁 public synchronized void addCount() { count++; } public synchronized void subtractCount() { count--; } public synchronized int getCount(){ return count; } }
-
作用于静态方法上:锁的当前类的锁,当前类的所有对象实例,获得class类的锁
class Ranmo { //锁的是当前类的所有对象实例的锁 public synchronized static void addCount() { } }class Ranmo { //锁的是当前类的所有对象实例的锁 public synchronized static void addCount() { } }
-
修饰代码块:指定加锁对象,对给定对象/类加锁,synchronized(this/Object)表示进入同步代码库前要获得给定对象的锁,synchronized(类.class)进入同步代码块获得当前类class的锁
public void addCount(){ //锁住的当前对象的锁 synchronized (this){ } //锁住当前类对象的锁 synchronized (Ranmo.class){ } }
总结:
- **synchronized:**用到statis静态方法和synchronized(class)代码块上都是给Classl类加锁
- **synchronized:**加到实例方法是给当前对象实例上锁
3.变量的线程安全问题
成员变量和静态变量是否是线程安全?
- 如果没有共享,则是线程安全的
- 如果共享了,状态是否能改变,分两种情况
- 只读状态,线程安全
- 读写操作,这段代码是临界区,需要考虑线程不安全
局部变量是否是线程安全?
- 局部变量是线程安全的
- 局部变量引用的对象未必是
- 该对象没有逃离方法的作用访问,则是线程安全的
- 该对象逃离方法的作用访问,比如子线程继承父类,重写了父类的方法,则线程不是安全的
4.Monitor
5.wait notfiy
wait
作用
Object o = new Object();
o.wait();
//让正在o对象上活动的线程进入等待状态,并且释放之前占有的o对象的锁
//无期限等待,直到被唤醒为止
notfiy
作用
Object o = new Object();
o.notify();
//唤醒正在o对象上等待的线程。只会通知,不会释放之前占有的o对象的锁
线程通信案列
public class ThreadDemo8 {
public static void main(String[] args) {
Test21 test21 = new Test21();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
test21.shengcheng();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
test21.xiaofei();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
class Test21 {
// Boolean flag =false ;
// //生成者
// public synchronized void shengcheng() throws InterruptedException {
// if (flag) {
// this.wait();
// }
// System.out.println("生产了一个苹果");
// flag = true;
// this.notify();
// }
//
// //消费者
// public synchronized void xiaofei() throws InterruptedException {
// if (!flag) {
// this.wait();
// }
// System.out.println("消费了一个苹果");
// flag = false;
// this.notify();
// }
Boolean flag =true;
//生成者
public synchronized void shengcheng() throws InterruptedException {
if (!flag) {
this.wait();
}
System.out.println("生产了一个苹果");
flag = false;
this.notify();
}
//消费者
public synchronized void xiaofei() throws InterruptedException {
if (flag) {
this.wait();
}
System.out.println("消费了一个苹果");
flag = true;
this.notify();
}
}
6.Park & Unpark
LockSupport类中的方法
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
7.死锁
概念
是指多个进程在运行过程中因争夺资源而造成的一种僵局,处于这种状态,无外力作用,它们都将无法再向前推进。
产生的条件
- 互斥条件:当资源被一个线程使用时,别的线程不能使用
- 请求和保持条件:当资源请求者在请求其他资源的同时保持对原有资源的占有
- 不可剥夺:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
- 环路等待:存在一个等待队列,即P1占有p2,p2占有p3,p3占有p1.
8.ReentrantLock
特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
- 可重入
可中断
@Slf4j
public class ReentranThreaD1 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
log.debug("尝试获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("获取锁失败,被打断");
}
});
lock.lock();
t1.start();
log.debug("main线程获取到了锁");
t1.interrupt();
}
}
超时时间
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
if (!lock.tryLock(6, TimeUnit.SECONDS)) {
log.debug("获取锁失败");
return;
}
log.debug("获取锁成功");
return;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
lock.lock();
t1.start();
Thread.sleep(1);
lock.unlock();
}