了解多线程之前,对一些基本概念进行理解。
进程:系统进行资源分配和调度的一个独立单位,是代码在一个数据集合上的一次运行活动。我们启动的Java程序,就会产生一个java.exe的进程。
线程:系统能够进行运算调度的最小单位,包含在进程中,一个进程可以并发多个线程,每个线程可以执行不同的任务。线程本身只拥有的资源很少(计数器、寄存器、栈),同一线程中多个进程共享进程的资源。
并行:同一时间内多个任务在多个CPU上同时执行,真正意义上的同时执行。
并发:通过CPU调度算法,让用户感觉同一时间段内多个任务都在执行,且并没有执行结束。以单CPU为例,为多个线程分配CPU时间片运行,实际上CPU操作层面上并不是真正的同时执行,而是快速轮换执行。
线程安全性:并发情况下,线程调度顺序不影响程序执行结果,即为线程安全的;
并发情况下,线程调度顺序影响了执行结果,即为线程不安全的;
同步:排队,并发情况下,对于共享资源的读写访问时需要排队访问,才能保证其线程安全性。(synchronized、volatile、Reentrantlock)
一、多线程实现方式
多线程有两种实现方式,继承Thread类和实现Runnable / Callable 接口,通过源码我们可以看到Thread也是实现了Rannable接口,两者的区别就在于,使用Thread类方式创建新的线程,就不支持多继承了。本质上没有其他区别。
具体区别
Thread: 不支持多继承;代码不能被共享;
Runnable: 避免了单继承的局限性;适合多个相同个程序代码的县城区处理同一资源情况;不能返回值
Callable: 避免了单继承的局限性;可返回值(阻塞)
线程的几种状态
NEW:初始状态,刚被创建,但还未启动(未start);
RUNNABLE:线程正常运行中,当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等;
BLOCKED:阻塞状态,等待另一个线程的synchronized块的执行释放,也就是线程在等待进入的临界区;
WAITING: 等待状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify;
TIMED-WAITING:有时限的等待,例如sleep(10000),这10秒都为这个状态;
TERMINATED:线程run()方法执行完毕;
1.Thread类 (java.lang 包)
以下代码使用Thread类实现多线程,职位展示部分状态信息。
public class AppThreads extends Thread {
@Override
public void run() {
super.run();
//打印线程基本信息 Thread[Name 线程名,Priority 优先级,group 线程组]
System.out.println(this+";线程ID:"+getId());
System.out.println("【状态】"+getState()+";接下来10秒进入休眠;执行时间:"+new Date());
try {
sleep(10000); //休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("appThreads1线程执行结束"+";执行时间:"+new Date());
}
public static void main(String[] args) throws Exception {
AppThreads appThreads1 = new AppThreads(); //创建线程类
System.out.println("【状态】"+appThreads1.getState()+";执行时间:"+new Date());
appThreads1.start(); //启动线程
sleep(5000); //main方法等待5秒
System.out.println("第6秒线程【状态】"+appThreads1.getState()+";执行时间:"+new Date());
System.out.println("main主线程等待appThreads1线程结束"+";执行时间:"+new Date());
appThreads1.join(); //main主线程调用 appThreads1 线程的 join()方法,当前线程进入阻塞状态,直到 appThreads1 线程结束才回复运行
System.out.println("main主线程结束"+";执行时间:"+new Date());
System.out.println("【状态】"+appThreads1.getState()+";执行时间:"+new Date());
}
}
控制台打印结果
Thread[Thread-0,5,main];线程ID:12
main执行时间:Mon Apr 13 23:46:22 CST 2020;【状态】NEW
run 执行时间:Mon Apr 13 23:46:22 CST 2020;【状态】RUNNABLE;接下来10秒进入休眠
main执行时间:Mon Apr 13 23:46:27 CST 2020;第5秒线程【状态】TIMED_WAITING
main执行时间:Mon Apr 13 23:46:27 CST 2020;main主线程等待appThreads1线程结束
run 执行时间:Mon Apr 13 23:46:32 CST 2020;appThreads1线程执行结束
main执行时间:Mon Apr 13 23:46:32 CST 2020;main主线程结束
main执行时间:Mon Apr 13 23:46:32 CST 2020;【状态】TERMINATED
2.Runnable类 (java.lang 包)
模拟小明、小红、小蓝三人一起购买商品,并发情况下商品库存为10个。
package Test;
import java.util.Date;
class BuyGoods implements Runnable{
static int goodsNum = 10; //初始化10件商品
private String personName; //购买人
private int buyNum = 0; //购买数量
public BuyGoods(Person person){
this.personName = person.name;
this.buyNum = person.buyNum;
}
//获取当前库存
static int getGoodsNum(){
return goodsNum;
}
//购买商品
synchronized static void buyGoods(String threadName,int num) {
System.out.println("执行时间:"+new Date()+";"+threadName+" 准备购买 "+num+" 件商品,当前库存:"+getGoodsNum());
if(goodsNum==0){ //商品买完时不再出售
throw new NullPointerException();
}
goodsNum--;
System.out.println("执行时间:"+new Date()+";"+threadName+" 开始购买第 "+num+" 件商品");
System.out.println("执行时间:"+new Date()+";"+threadName+"购买第 "+num+" 件商品完成,当前库存:"+getGoodsNum());
System.out.println();
}
@Override
public void run() {
Thread thread = Thread.currentThread(); //Thread.currentThread() 获取当前对象线程的引用
for(int i=1;i<=this.buyNum;i++){
try{
buyGoods(this.personName,i);
}catch (NullPointerException e){
System.out.println("商品已经售空,"+this.personName+",没有购买到第 "+i+" 件商品");
break;
}
}
}
}
/**
* 购买人
*/
class Person{
String name; //姓名
int buyNum; //购买数量
public Person(String name,int buyNum){
this.name = name;
this.buyNum = buyNum;
}
}
public class AppRunnable{
public static void main(String[] args) throws Exception{
//创建三个购买人
Person personA = new Person("小明",5);
Person personB = new Person("小红",2);
Person personC = new Person("小蓝",6);
Thread thread1 = new Thread(new BuyGoods(personA));
Thread thread2 = new Thread(new BuyGoods(personB));
Thread thread3 = new Thread(new BuyGoods(personC));
thread1.start();
thread2.start();
thread3.start();
thread1.join(); //等待购买完成
thread2.join(); //等待购买完成
thread3.join(); //等待购买完成
System.out.println("所有人购买完成,当前库存:"+ BuyGoods.getGoodsNum());
}
}
控制台打印结果
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 1 件商品,当前库存:10
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【购买完成】第 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 1 件商品,当前库存:8
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 2 件商品,当前库存:8
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 2 件商品,当前库存:7
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 3 件商品,当前库存:7
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 3 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 3 件商品,当前库存:6
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 4 件商品,当前库存:6
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 4 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 4 件商品,当前库存:5
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 5 件商品,当前库存:5
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 5 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 5 件商品,当前库存:4
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 6 件商品,当前库存:4
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 6 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 6 件商品,当前库存:3
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【准备购买】 1 件商品,当前库存:3
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【购买完成】第 1 件商品,当前库存:2
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【准备购买】 2 件商品,当前库存:2
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【购买完成】第 2 件商品,当前库存:1
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 2 件商品,当前库存:1
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【购买完成】第 2 件商品,当前库存:0
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 3 件商品,当前库存:0
商品已经售空,小明,没有购买到第 3 件商品
所有人购买完成,当前库存:0
3.Callable类 (java.util.concurrent 包)
同样以购买商品为例,Callable可以通过Future.get()方法获取call方法返回值,并且call()方法中的异常,我们可以向外抛出,而不是像Runnable中的run()方法,自行处理。
package MyCode;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class BuyGoods2 implements Callable {
static int goodsNum = 10; //初始化10件商品
private String personName; //购买人
private int buyNum = 0; //购买数量
public BuyGoods2(Person2 person){
this.personName = person.name;
this.buyNum = person.buyNum;
}
//获取当前库存
static int getGoodsNum(){
return goodsNum;
}
//购买商品
synchronized static void buyGoods(String threadName,int num) {
System.out.println("执行时间:"+new Date()+";"+threadName+"【准备购买】 "+num+" 件商品,当前库存:"+getGoodsNum());
if(goodsNum==0){ //商品买完时不再出售
throw new NullPointerException();
}
System.out.println("执行时间:"+new Date()+";"+threadName+"【开始购买】第 "+num+" 件商品");
goodsNum--;
System.out.println("执行时间:"+new Date()+";"+threadName+"【购买完成】第 "+num+" 件商品,当前库存:"+getGoodsNum());
}
@Override
public Object call() {
int i = 0;
for(i=1;i<=this.buyNum;i++){
try{
buyGoods(this.personName,i);
}catch (NullPointerException e){
System.out.println("商品已经售空,"+this.personName+",没有购买到第 "+i+" 件商品");
return ""+this.personName+" 购买了 " + (i-1) + "件商品";
}
try {
Thread.sleep(100); //休息一下
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return this.personName+" 购买了 " + (i-1) + "件商品";
}
}
/**
* 购买人
*/
class Person2{
String name; //姓名
int buyNum; //购买数量
public Person2(String name,int buyNum){
this.name = name;
this.buyNum = buyNum;
}
}
public class AppCallable{
public static void main(String[] args) throws Exception{
//创建三个购买人
Person2 personA = new Person2("小明",4);
Person2 personB = new Person2("小红",2);
Person2 personC = new Person2("小蓝",5);
BuyGoods2 buyGoods1 = new BuyGoods2(personA);
BuyGoods2 buyGoods2 = new BuyGoods2(personB);
BuyGoods2 buyGoods3 = new BuyGoods2(personC);
// 创建一个执行任务的服务
ExecutorService es = Executors.newFixedThreadPool(3);
Future future1 = es.submit(buyGoods1);
Future future2 = es.submit(buyGoods2);
Future future3 = es.submit(buyGoods3);
// 如果调用get方法,当前线程会等待任务执行完毕后才往下执行
System.out.println("buyGoods1: " + future1.get());
System.out.println("buyGoods2: " + future2.get());
System.out.println("buyGoods3: " + future3.get());
System.out.println("所有人购买完成,当前库存:"+BuyGoods2.getGoodsNum());
// 停止任务执行服务
es.shutdownNow();
}
}
控制台执行结果
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 1 件商品,当前库存:10
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 1 件商品,当前库存:8
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【准备购买】 1 件商品,当前库存:8
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【购买完成】第 1 件商品,当前库存:7
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【准备购买】 2 件商品,当前库存:7
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【购买完成】第 2 件商品,当前库存:6
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 2 件商品,当前库存:6
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 2 件商品,当前库存:5
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 2 件商品,当前库存:5
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 2 件商品,当前库存:4
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 3 件商品,当前库存:4
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 3 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 3 件商品,当前库存:3
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 3 件商品,当前库存:3
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 3 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 3 件商品,当前库存:2
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 4 件商品,当前库存:2
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 4 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 4 件商品,当前库存:1
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 4 件商品,当前库存:1
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 4 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 4 件商品,当前库存:0
buyGoods1: 小明 购买了 4件商品
buyGoods2: 小红 购买了 2件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 5 件商品,当前库存:0
商品已经售空,小蓝,没有购买到第 5 件商品
buyGoods3: 小蓝 购买了 4件商品
所有人购买完成,当前库存:0
3.线程安全性
上面程序中,使用了synchronized进行方法同步,来保证线程是安全的,同时间只能有一个人进行购买操作。那就线程安全性问题,记录一些知识点。
三个特性
原子性:不可分(同生共死);互斥访问,同一时刻只能有一个线程进行操作。(atomic,synchronized)
可见性:一个线程修改了共享变量后,其他线程可以即时观察到。(synchroinzed,volatile)
有序性:一个线程观察另一个线程的执行顺序时,由于指令重排序,观察结果是无序的。
volatile 和 synchronized、ReentrantLock
特性的比较
volatile:具有有序性、可见性,不具有原子性,锁变量;
synchronized:具有原子性、有序性、可见性,锁对象和类;
ReentrantLock:具有原子性、有序性、可见性;
可重入锁:可重复可递归调用,线程获取了对象锁之后,可以再次获取对象的锁,儿其他线程不可以(ReentrantLock、syntronized)。通过计数器实现,调用一次计数器+1,释放一次计数器-1。
不可重入锁:不可递归调用,调用就死锁。
synchronized锁
对象锁:一个线程得到对象锁,其他线程可以访问没有进行同步的方法或者代码,同步方法和非同步方法互不影响。
类锁:一个线程得到了类锁,其他线程调用该类就会被阻塞。
ReentrantLock: ReentrantLock构造器提供了一个boolean值,来选择是一个公平锁(fair)还是不公平锁(unfair)。公平锁按照线程请求锁的顺序一次获得锁。
需要手动声明来加锁和释放。(finally中声明释放锁)
synchronized:不公平锁,不保证下一个次是哪个线程获得锁,并且由编译器去保证加锁和释放动作。