01 进程与线程的区别
进程:是正在执行的一段程序,一旦程序被载入内存准备执行,那就是一个进程。进程是表示资源分配的基本概念,又是调度运算的基本单位,是系统的并发执行单元。
线程:单个进程中执行的每个任务都是一个线程,线程是进程中执行运算的最小单位。
线程的意义:提高程序效率,充分发挥多和计算机的优势。
02 多线程创建
1,继承Thread类,实现run();
public static class MyThread extends Thread{
@Override
public void run() {
System.out.println("我的一个线程");
}
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
2,实现Runnable接口,将对象做为Thread的构造参数传入
public static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("我的一个线程");
}
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
还有比较灵活的写法:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类写法");
}
});
//JDK1.8版本以后
Thread t2 = new Thread(()->{
System.out.println("兰姆达表达式");
});
t1.start();
t2.start();
3,实现Callable接口的call(),允许线程执行完以后获取返回结果
public static class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
return "返回结果";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<String>(new MyCall());
Thread thread = new Thread(task);
thread.start();
System.out.println(task.get());
}
03 用户线程与守护线程
当JVM运行起来以后,会有两种线程:用户线程与守护线程。
JVM关闭的条件:当用户线程运行完毕后,即使存在守护线程,它也会关闭。所以不能绝对的说:用户线程和守护线程都运行完毕了JVM才关闭。
在Java里有个很出名的守护线程就是:GC垃圾回收线程。
做一个小测试:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:"+i);
}
});
//守护线程
thread1.setDaemon(true);
thread1.start();
//用户线程
Thread thread2 = new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2:"+i);
}
});
thread2.start();
}
输出结果:
线程2:94
线程2:95
线程1:25
线程2:96
线程2:97
线程2:98
线程1:26
线程2:99
Process finished with exit code 0
上面的thread1.setDaemon(true);设置成了守护线程,当用户线程结束后,守护线程也停止运行了。
04 线程的优先级
CPU内核同一时刻只能执行一条线程,内核靠线程调度器来分配时间片来执行线程。所以线程需要抢夺时间片来执行,那么优先级其实就是设置抢夺的概率。
public static void main(String[] args){
Thread thread1 = new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("线程1:"+i);
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("线程2:"+i);
}
});
thread1.setPriority(10);
thread2.setPriority(5);
thread1.start();
thread2.start();
}
输出结果:
线程1:97
线程1:98
线程1:99
线程2:0
线程2:1
线程2:2
线程2:3
线程2:4
可以看到线程1执行完毕后线程2才开始执行,说明线程1争夺时间片的概率大。
05 线程的生命周期
06 join方法
在线程执行的时候让别的线程调用join先插队,等别的线程执行完后再执行本线程。
public static void main(String[] args){
Thread t1 = new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("t1:"+i);
}
});
Thread t2 = new Thread(()->{
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("t2:"+i);
}
});
Thread t3 = new Thread(()->{
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("t3:"+i);
}
});
t1.start();
t2.start();
t3.start();
}
输出结果:
t1:0
t1:1
t1:2
t2:0
t2:1
t2:2
t3:0
t3:1
t3:2
Process finished with exit code 0
07 JMM内存模型
JMM(Java Memory Model)
线程是不能够直接修改主存内的数据的,而是先从主存中读取到自己的工作内存中创建副本,修改完成后写入到主内存,这就是JMM模型。
这也是为什么多线程并发访问修改数据的时候为什么出现安全问题。
08 并发编程的三大特性
原子性
一个操作或多个操作,要么全部执行并且执行过程不被打断,要么全部不执行(提供互斥访问,在同一时刻只有一个线程进行访问)
可以通过加锁的方式。
先看不加锁:
static int num = 100;
public static void main(String[] args){
Runnable runnable = ()->{
while (true){
if(num>0){
num--;
}else{
break;
}
System.out.println(num);
}
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
输出了诡异的结果:
1
0
98
94
Process finished with exit code 0
如果加锁:
static int num = 100;
public static void main(String[] args){
Object o = new Object();
Runnable runnable = ()->{
while (true){
synchronized (o){
if(num>0){
num--;
}else{
break;
}
System.out.println(num);
}
}
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
输出结果:
5
4
3
2
1
0
Process finished with exit code 0
加锁需要传入一个对象,本程序中该对象是唯一的,如果直接写new Object()和不加锁效果一样。
可见性
public static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
System.out.println("线程一号启动");
while (!flag){};
System.out.println("线程一号结束");
}).start();
Thread.sleep(1000);
new Thread(()->{
flag = true;
System.out.println("把flag修改为了true");
}).start();
}
显然对于一号线程而言,当二号线程修改了flag的值后,一号线程并没有及时获得flag,也就是说flag对于一号线程是不可见的。flag的值没有得到及时的更新。
有序性
JVM为了提高效率,会做出一些优化,对指令进行重排序(happens-before),在单线程的情况下没有问题,但是多线程编程需要考虑有序性问题。
volatile
1,保证可见性
2,屏蔽指令重排序
3,但是保证不了原子性
synchronized
JDK1.0开始提供的关键字,重量级的锁,能够保证某个代码块在被执行时,其他线程不访问执行。
synchronized必须使用对象做为锁,因为对象分为三部分:头部分里面有个锁的字段,synchronized就是利用该字段达到上锁的目的。
注意:假如有两个方法,要想实现调用fun1时fun2不能被访问,必须使用同一个对象加锁
public static Object o ;
public static void fun1(){
synchronized (o){}
}
public static void fun2(){
synchronized (o){}
}
Monitor
JVM 是通过进入、退出对象监视器(Monitor)来实现对方法,同步块的同步。
使用synchronized加锁定的代码块,在被编译后会形成对象监视器的入口(monitorenter)和出口(monitorexit)
使用对象Object做为锁的时候,当有线程访问同步代码块的时候,监视器入口(monitorenter)会去检查Object是否上锁,没有上锁,则让该线程访问,同时给该对象上锁,此时若有其他线程访问该代码块的时候,监视器入口通过对比Object,发现上锁了,就会让其他线程处于阻塞状态,当第条线程执行完毕退出(monitorexir)以后,会给该对象解锁,那么被阻塞的线程就可以接着访问,并保证了原子性。
public class Demo {
public static synchronized void get(){
//是把Demo.class(当前类的字节码文件)做为对象加锁
}
public synchronized void to(){
//是把this(当前this锁)做为对象枷锁
}
public static void main(String[] args) {
Demo demo = new Demo();
demo.to();
Demo.get();
}
}
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程,即使获取CPU的执行权,也进不去。
优点:解决了线程安全问题
缺点:多个线程需要判断锁,较为消耗资源,抢锁的资源
09 J.U.C之Lock
Lock
public static void main(String[] args) {
Lock lock = new ReentrantLock();
//Lock是代码实现的接口,而synchronized是JVM底层实现的
new Thread(()->{
lock.lock();
try{
//加锁代码块
}finally {
//必须手动释放
lock.unlock();
}
}).start();
}
trylock
上锁部分不被访问的时候,去做别的事情,而不是像synchronized那样必须阻塞。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo {
Lock lock = new ReentrantLock();
public void insert(Thread thread){
if(lock.tryLock()){
//抢到锁了,就调用这里的方法
try{
System.out.println(thread.getName()+"抢到锁啦");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(thread.getName()+"释放锁啦");
}
}else{
//如果没有抢到锁,就调用这里的方法
System.out.println(Thread.currentThread()+"没有抢到锁啦");
}
}
public static void main(String[] args) {
Demo d = new Demo();
new Thread(()->{
d.insert(Thread.currentThread());
}).start();
new Thread(()->{
d.insert(Thread.currentThread());
}).start();
}
}
Lock与synchronized的区别
1)Lock是一个接口,而synchronized是一个关键字,是Java内置的实现,它可以直接修饰方法和代码块,而lock只能修饰代码块。
2)synchronized在发生异常的时候,会自动释放线程占有的锁,因此不会导致死锁现象,而Lock发生异常以后,如果没有unLock()释放锁,很可能造成死锁现象,因此在使用Lock的时候需要在finally中释放锁。
3)Lock可以让等待的线程响应中断,去干别的事情,而synchronized会让线程一直阻塞下去。
4)通过Lock的trylock()可以知道有没有成功获取锁,而synchronized不行。
5)Lock可以提高多线程进行读操作的效率(提供读写锁)。
从性能上来说,如果竞争资源很激烈,Lock的性能远远大于synchronized。
10 线程通信
wait与notify
使用两个线程交替打印1-100,其中一条线程只打印奇数,另外一个线程只打印偶数。
public class Wait {
static class Num{
public int num = 1;//共享资源
}
static class J implements Runnable{
public Num numObj;
public J(Num n){
this.numObj = n;
}
@Override
public void run() {
synchronized (numObj){
while (numObj.num<=100){
if(numObj.num%2!=0){
System.out.println("奇数====>"+numObj.num);
numObj.num++;
}else {
try {
numObj.notify();
numObj.wait();//wait()要写在代码块里面,因为它的作用是释放锁,并且等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
static class O implements Runnable{
public Num numObj;
public O(Num n){
this.numObj = n;
}
@Override
public void run() {
synchronized (numObj){
while (numObj.num<=100) {
if (numObj.num % 2 == 0) {
System.out.println("偶数====>" + numObj.num);
numObj.num++;
} else {
try {
numObj.notify();
numObj.wait();//wait()要写在代码块里面,因为它的作用是释放锁,并且等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) {
Num numObj = new Num();
new Thread(new J(numObj)).start();//奇数线程
new Thread(new O(numObj)).start();//偶数线程
}
}
wait()的作用是,释放当前锁,并使当前线程处于等待状态,所以需要写在同步代码块里,notify()是唤醒一个处于等待状态的线程,本题只有两条线程,如果有多条线程,那么可以使用notifyAll()。
这三个方法最终调用的都是JVM级的native方法。
11 什么是线程池
线程池是Java的并发框架,几乎所有的并发执行程序或者异步操作都可以使用线程池。
1)降低资源消耗。通过重复使用已经创建好的线程降低创建和销毁的消耗。
2)提高响应速度。当任务到达时,不需要等到线程创建后就可以立即执行。
3)提高现成的可管理性。使用线程池可以进行统一的分配、调优、监控。
12 线程池工作原理
Executor是最基本的接口,其子接口:ExecutorService是工作中常用的接口
其中一个很重要的实现类是ThreadPoolExecutor,通过它new出来
import java.util.concurrent.*;
public class Pool {
static class MyRunnable implements Runnable{
String info = "";
public MyRunnable(String txt){
this.info = txt;
}
@Override
public void run() {
System.out.println(this.info);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// int corePoolSize 核心线程数
// int maximumPoolSize 最大线程数
// long keepAliveTime 保持存活得时间
// TimeUnit unit 时间单位
// BlockingQueue<Runnable> workQueue 任务队列
// RejectedExecutionHandler handler 饱和策略
ExecutorService es = new ThreadPoolExecutor(
2,//corePoolSize
5,//maximumPoolSize
10,//keepAliveTime
TimeUnit.SECONDS,//unit
new ArrayBlockingQueue<Runnable>(5),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= 20; i++) {
try{
es.execute(new MyRunnable("执行第"+i+"条线程"));
}catch (Throwable e){
System.out.println("丢弃线程"+i);
}
}
es.shutdown();
}
}
四个流程:
1)判断核心线程数
2)判断任务队列
3)判断最大线程数(备胎线程)
4)执行饱和策略
存活时间参数:当最大线程数(备胎线程)执行完毕,并且没有新任务的前提下,只能存活(keepAliveTime unit )的时间,超过时间就会被释放掉。
13 三种常见队列
SynchronousQueue
一次性只能装一个任务,其他任务处于阻塞状态,同时一次性只能取出一个任务,如果没有任务可以取出,也处于阻塞状态
import java.util.concurrent.SynchronousQueue;
public class Queue {
public static void main(String[] args) {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
queue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("装入数据====>"+i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("取出数据=====>"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
//执行结果
取出数据=====>0
装入数据====>0
隔了两秒
取出数据=====>1
装入数据====>1
隔了两秒
取出数据=====>2
装入数据====>2‘
隔了两秒
取出数据=====>3
装入数据====>3
LinkedBlockingQueue
瞬间装完所有的任务,然后慢慢取出
import java.util.concurrent.LinkedBlockingQueue;
public class Queue {
public static void main(String[] args) {
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
queue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("装入数据====>"+i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("取出数据=====>"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
//执行结果
装入数据====>18
装入数据====>19
瞬间装完20个数据
取出数据=====>0
隔了两秒
取出数据=====>1
隔了两秒
取出数据=====>2
隔了两秒
取出数据=====>3
ArrayBlockingQueue
设置填装大小,比如4,那么一次性装入4个,其余的取出一个就装一个
import java.util.concurrent.ArrayBlockingQueue;
public class Queue {
public static void main(String[] args) {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(4);
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
queue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("装入数据====>"+i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("取出数据=====>"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
//执行结果
装入数据====>0
装入数据====>1
装入数据====>2
装入数据====>3
瞬间完成
取出数据=====>0
装入数据====>4
隔了两秒
取出数据=====>1
装入数据====>5
隔了两秒
14 饱和策略
CallerRunsPolicy
不抛弃任务,调用线程池的线程,帮助执行任务
比如上面的main方法调用的线程池,16号线程就会在main中执行
演示代码:
import java.util.concurrent.*;
public class Pool {
static class MyRunnable implements Runnable{
String info = "";
public MyRunnable(String txt){
this.info = txt;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+this.info);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = new ThreadPoolExecutor(
2,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 1; i <= 20; i++) {
try{
es.execute(new MyRunnable("执行第"+i+"条线程"));
}catch (Throwable e){
System.out.println("丢弃线程"+i);
}
}
es.shutdown();
}
}
执行结果:
pool-1-thread-2:执行第2条线程
pool-1-thread-1:执行第1条线程
main:执行第16条线程
pool-1-thread-3:执行第13条线程
pool-1-thread-4:执行第14条线程
pool-1-thread-5:执行第15条线程
AbortPolicy(默认)
当最大线程数满了以后,抛出异常,抛弃任务
DiscardPolicy
连异常都不抛,直接把任务丢了
DiscardOldestPolicy
连异常都不抛,直接把任务丢了
15 线程池工具类
工具类:Executors 快速创建线程池
缓存线程池
newCachedThreadPool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Es {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
System.out.println("线程"+i);
executorService.execute(()->{
for (int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName()+":"+j);
}
});
}
executorService.shutdown();
}
}
源码的实现:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以发现:没有核心线程数,扔进去多少线程,就会创建多少线程,好处是线程复用以及可以及时释放线程,弊端就是,当任务量极大的时候,他就创建一大堆线程。
定长线程池
newFixedThreadPool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Es {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
System.out.println("线程"+i);
executorService.execute(()->{
for (int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName()+":"+j);
}
});
}
executorService.shutdown();
}
}
源码的实现:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
单例线程池
newSingleThreadExecutor
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Es {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
System.out.println("线程"+i);
executorService.execute(()->{
for (int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName()+":"+j);
}
});
}
executorService.shutdown();
}
}
可以发现单例线程池,会由一个线程完成所有的任务。
源码的实现:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
调度线程池
ScheduledExecutorService:newScheduledThreadPool
可以实现:延迟执行的线程池
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//延迟执行的线程池
scheduledExecutorService.schedule(()->{
System.out.println("过去5秒种啦");
},5, TimeUnit.SECONDS);//第二个参数是延迟时间,然后是时间单位
scheduledExecutorService.shutdown();
}
可以实现:周期执行的线程池
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//周期执行的线程池:5s之后开始执行任务,每隔2s重复执行一次
scheduledExecutorService.scheduleAtFixedRate(()->{
System.out.println("执行啦");
},5,2, TimeUnit.SECONDS);//参数:延迟时间 间隔时间 时间单位
//scheduledExecutorService.shutdown();这时候不要shutdown
}