- 多线程
高可用、高性能、高并发
- 概念
方法间的调用:普通方法的调用,从哪来到哪里去,闭合的一条路径
多线程使用:开辟多条路径
程序、进程和线程
程序指的是静态的代码;进程指的是操作系统调用程序产生,与程序一一对应;线程指的是进程中的多个路径,充分利用进程的资源。
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会存在多个线程,如gc线程、主线程。
- main()称之为主线程,为系统的入口点,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为干预的
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,加载和存储主内容控制不当会造成数据不一致
- 创建
少用extends 多用implement,因为java类只能单继承,但是可以实现多个接口
线程优先级:指的是执行概率的高低。
守护线程、用户线程:程序必须等用户线程执行完才能停止而不用等守护线程执行结束
- 继承Thread类
1、创建:继承Thread+重写run()
2、启动:创建子类对象+调用start()
/**
* 创建线程方式一
* 1、创建:继承Thread+重写run()
* 2、启动:创建子类对象+start()方法
*
*/
public class StartThread extends Thread {
/**
* 线程的入口点
*/
@Override
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌>>>>");
}
}
public static void main(String[] args) {
//创建子类对象
StartThread st=new StartThread();
//启动
st.start();//开启新的线程,不保证立即运行,由cpu调用
// st.run();//直接调用,只是普通方法的调用
for(int i=0;i<20;i++) {
System.out.println("一边学习>>>>");
}
}
}
- 实现Runnable
推荐,避免java类单继承的局限性。方便共享资源
1、创建:实现Runnable+重写run()
2、启动:创建实现类对象+Thread对象+调用start()
public class StartRun implements Runnable {
/**
* 线程的入口点
*/
@Override
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌>>>>");
}
}
public static void main(String[] args) {
//启动
//对象只使用一次,可以使用匿名,不声明对象的引用
new Thread(new StartRun()).start();;//开启新的线程,不保证立即运行,由cpu调用
for(int i=0;i<20;i++) {
System.out.println("一边学习>>>>");
}
}
}
资源共享1(以下代码存在并发问题)
/**
* 共享资源,并发(线程安全)
* @author yinqq
*
*/
public class Web12306 implements Runnable {
//票数
private int ticketNums=99;
@Override
public void run() {
while(true) {
if(ticketNums<0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+ticketNums--);
}
}
public static void main(String[] args) {
Web12306 w=new Web12306();
//一份资源,多个代理
new Thread(w,"代理1").start();
new Thread(w,"代理2").start();
new Thread(w,"代理3").start();
new Thread(w,"代理4").start();
}
}
资源共享2
/**
* 模拟龟兔赛跑
*
*
*/
public class Racer implements Runnable {
//胜利者
private String winner;
@Override
public void run() {
for(int steps=1;steps<=100;steps++) {
System.out.println(Thread.currentThread().getName()+"--->"+steps);
//比赛是否结束
boolean flag=gameOver(steps);
if(flag) {
break;
}
}
}
private boolean gameOver(int steps) {
if(winner!=null) {
return true;
}else {
if(steps==100) {
winner=Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Racer racer=new Racer();
new Thread(racer,"tortoise").start();
new Thread(racer,"rabbit").start();
}
}
- 实现Callable
了解,高级并发编程juc中使用:实现Callable接口+重写call()方法
1、call方法可以throws异常
2、call有返回值
3、使用起来复杂
//启动三个线程
//创建执行服务 1个线程
ExecutorService ser=Executors.newFixedThreadPool(1);
//提交执行 获取未来结果 cd1为实现Callable的实现类对象
Future<Boolean> result1=ser.submit(cd1);
//获取结果
boolean r1=result1.get();
System.out.println(r1);
//关闭服务
ser.shutdown();
- 静态代理设计模式
使用Runnable启动多线程时需使用Thread对象,该对象称之为静态代理对象。
/**
* 静态代理
* 接口:
* 1、真实角色
* 2、代理角色
*
*
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
}
}
interface Marry {
void happyMarry();
}
class You implements Marry {
@Override
public void happyMarry() {
System.out.println("together>>>>>");
}
}
//代理角色
class WeddingCompany implements Marry {
// 真实角色
private Marry target;
public WeddingCompany(Marry target) {
super();
this.target = target;
}
@Override
public void happyMarry() {
ready();
this.target.happyMarry();
after();
}
private void ready() {
System.out.println("布置场地>>>>>>>");
}
private void after() {
System.out.println("祝贺新婚>>>>>>>");
}
}
- lambda表达式
作用:避免匿名内部类定义过多。实质是函数式编程的概念
1、一般简单的线程类使用lambda表达式
2、接口或父类,只能是一个方法。
3、lambda推导必须存在类型,形参或是引用变量。
推导过程:
public class LambdaTest01 {
// 2、静态内部类 LambdaTest01调用时才会编一个该内部类
static class Like2 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda2……");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//3、方法内部类
class Like3 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda3……");
}
}
like = new Like3();
like.lambda();
// 4、匿名内部类
new ILike() {
public void lambda() {
System.out.println("I like lambda4……");
}
}.lambda();
// 5、lambda表达式 需借助接口或父类
like = () -> {
System.out.println("I like lambda5……");
};
like.lambda();
}
}
interface ILike {
void lambda();
}
//1、外部类
class Like implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda1……");
}
}
- 线程状态
线程进入就绪状态的原因
1、调用start方法
2、解除阻塞状态
3、线程执行过程中调用yield 让出cpu的调度
4、JVM根据自己的算法将cpu从本地线程切换到其他线程
线程进入阻塞状态的原因
1、sleep 不释放资源:
2、wait 不占用资源
3、join 合并插队:
4、IO操作:read/writer
线程进入死亡状态的原因
1、线程正常执行结束
2、强制终止:相应方法有stop\destroy ,但不推荐使用。可自己设置结束标识
- 线程的停止
1、不使用JDK提供的stop、destroy方法
2、提供一个boolean型的终止变量,当这个变量值位false时,则终止线程执行
/**
* 终止线程的两种方式
* 1、线程正常执行完毕--->次数限制
* 2、外部干涉--->加入标志位
*
*
*/
public class TerminateThread implements Runnable {
// 1、加入标识 标记线程体是否可以运行
private boolean flag = true;
private String name;
public TerminateThread(String name) {
this.name = name;
}
@Override
public void run() {
int i = 0;
// 2、关联标识
while (flag) {
System.out.println(Thread.currentThread().getName()+"------>" + i++);
}
}
//3、对外提供方法 改变标识
public void terminate() {
this.flag=false;
}
public static void main(String[] args) {
TerminateThread tt=new TerminateThread("Thread11");
new Thread(tt).start();
for(int i=0;i<=99;i++) {
System.out.println("main---->"+i);
if(i==88) {
//4、线程的终止
tt.terminate();
System.out.println("线程结束……");
}
}
}
}
- 线程的阻塞
- sleep
☞当前线程阻塞的毫秒数
☞存在异常InterruptedException,只能使用try-catch因为run方法不能抛出异常
☞时间到达后线程进入就绪状态
☞可以模拟网络延迟、倒计时等
☞每个对象都有一个锁,sleep不会释放锁
模拟倒计时:
public class BlockedSleep03 {
public static void main(String[] args) throws InterruptedException {
// 倒计时10秒
Date endTime = new Date(System.currentTimeMillis() + 1000 * 10);
long end = endTime.getTime();
while (true) {
System.out.println(new SimpleDateFormat("hh:mm:ss").format(endTime));
Thread.sleep(1000);
endTime = new Date(endTime.getTime() - 1000);
if (end - 1000 * 10 > endTime.getTime())
break;
}
}
public static void test() throws InterruptedException {
// 倒计10个数 1秒一个
int num = 10;
while (true) {
System.out.println("倒计时----->" + num--);
Thread.sleep(1000);
if (num <= 0) {
break;
}
}
}
}
- join
1、合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞(插队线程)
2、成员方法
public class BlockJoin02 {
public static void main(String[] args) throws InterruptedException {
System.out.println("爸爸和儿子买烟的故事……");
new Thread(new Father()).start();
new Thread().start();
}
}
class Father extends Thread{
@Override
public void run() {
System.out.println("想抽烟,发现没了");
System.out.println("让儿子去买烟");
Thread t=new Thread(new Son());
t.start();
try {
t.join();//father被阻塞
System.out.println("老爸接过烟,把零钱给了儿子");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("孩子走丢了,老爸出去找孩子……");
}
}
}
class Son extends Thread{
@Override
public void run() {
System.out.println("接过老爸的钱,出去了");
System.out.println("路边有个游戏厅玩了10s");
for(int i=1;i<=10;i++) {
System.out.println(i+"s过去了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("赶紧买烟去……");
System.out.println("手拿中华回家去");
}
}
- 线程的就绪
- yield
1、礼让线程,当当前正在执行线程暂停
2、不是阻塞线程,而是将线程从运行状态转入就绪状态
3、让cpu调度器重新调度
4、静态方法,指向当前的线程
/**
* yield让出cpu的调度,避免占用cpu过长,暂停线程 直接进入就绪状态不是阻塞状态
*
*/
public class YieldDemo01 {
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<100;i++) {
System.out.println("lambda……"+i);
}
}).start();
for(int i=0;i<100;i++) {
if(i%10==0) {
Thread.yield();//main礼让
}
System.out.println("main……"+i);
}
}
}
- 线程状态的观察
1、通过state()成员方法,返回线程状态
2、静态方法active返回当前活跃的线程数
- 线程优先级
priority
1、Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行
2、线程优先级用数字表示:1-10。
3、成员方法设置或获得线程对象的优先级:setPriority()\getPriority()
4、优先级的设定建议在start()调用前
5、优先级低只意味着获得调度的概率低。并不是绝对先调用优先级高的再调用优先级低的线程。
- 守护线程
daemon
1、线程分为用户线程和守护线程
2、虚拟机必须确保用户线程执行完毕
3、虚拟机不用等待守护线程执行完毕
4、如后台日志记录、监控内存使用等
- 线程同步
并发:同一个对象多个线程同时操作
1、线程同步其实就是一种等待机制,多个需要同时访问同一对象的线程进入该对象的等待池形成队列,等待前面的线程使用完毕之后,下一个线程再使用
2、对象加上锁机制,为保证数据在方法中被访问时的正确性,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在一下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
线程不安全1:操作容器
public class UnsafeTest03 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
for(int i=0;i<100;i++) {
new Thread(()->{
System.out.println(Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list.add(Thread.currentThread().getName());
}).start();
}
}
}
线程不安全1:取钱
public class UnsafeTest02 {
public static void main(String[] args) {
Account account=new Account(100,"结婚礼金");
Drawing you=new Drawing(account,80,"你");
Drawing wife=new Drawing(account,90,"对象");
you.start();
wife.start();
}
}
//账户
class Account {
int money;//金额
String name;//名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟取钱
class Drawing extends Thread{
Account account;//取钱的账户
int drawingMoney;//取得钱数
int packetTotal;//口袋的钱
public Drawing(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
if(account.money-drawingMoney<0)
return;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
account.money-=drawingMoney;
packetTotal+=drawingMoney;
System.out.println(this.getName()+"---->账户余额为:"+account.money);
System.out.println(this.getName()+"---->口袋的钱为:"+packetTotal);
}
}
- synchronized
由于可以通过private关键字保证数据对象只能被方法访问,所以只需要针对方法提出一套机制,就是synchronized关键字,包括两种用法:synchronized方法和synchronized块
1、锁的范围尽可能小保证线程安全的同时以达到性能最优
2、synchronized+成员方法:实际锁的是调用该方法的对象
- synchronized方法
public class SafeTest01 {
public static void main(String[] args) {
SateWeb12306 w = new SateWeb12306();
// 一份资源,多个代理
new Thread(w, "代理1").start();
new Thread(w, "代理2").start();
new Thread(w, "代理3").start();
new Thread(w, "代理4").start();
}
}
class SateWeb12306 implements Runnable {
// 票数
private int ticketNums = 100;
private boolean flag=true;
@Override
public void run() {
while (flag) {
try {
Thread.sleep(1000);
test();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//线程安全
public synchronized void test() {
if (ticketNums <= 0) {
flag=false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
}
}
- synchronized块
Java中存在的四种块:
- 方法中的块☞局部快,解决变量作用域,快速释放内存
- 类中的块☞构造块,用于初始化对象信息
- 类中的块+static☞静态块,用于初始化类的,加载一次,先于构造块执行
- 同步块,解决线程安全问题
格式:synchronized(obj){},obj称之为同步监视器 - obj可以是任何对象,推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this本身,或class即类的模子
- 力度更小的解决线程安全问题
同步监视器的执行过程 - 第一个线程访问,锁定同步监视器,执行其中代码
- 第二次线程访问,发现同步监视器被锁定,无法访问
- 第一个线程执行完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁,锁定并访问
public class SynBlockTest01 {
public static void main(String[] args) {
Account account = new Account(100, "结婚礼金");
SynDrawing you = new SynDrawing(account, 80, "你");
SynDrawing wife = new SynDrawing(account, 90, "对象");
you.start();
wife.start();
}
}
//模拟取钱
class SynDrawing extends Thread {
Account account;// 取钱的账户
int drawingMoney;// 取得钱数
int packetTotal;// 口袋的钱
public SynDrawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
test();
}
// 目标锁定account
private void test() {
if(account.money<=0) {//此代码可以提升性能
return;
}
synchronized(account) {
if(account.money-drawingMoney<0) {
System.out.println("【"+Thread.currentThread().getName()+"】"+"正在取钱……"+"余额不足!!!!!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
account.money-=drawingMoney;
packetTotal+=drawingMoney;
System.out.println(this.getName()+"---->账户余额为:"+account.money);
System.out.println(this.getName()+"---->口袋的钱为:"+packetTotal);
}
}
}
- 同步块与同步方法的区别:推荐尽可能使用同步块-更细粒度的控制
public class SynBlockTest03 {
public static void main(String[] args) {
SynWeb12306 w = new SynWeb12306();
// 一份资源,多个代理
new Thread(w, "代理1").start();
new Thread(w, "代理2").start();
new Thread(w, "代理3").start();
new Thread(w, "代理4").start();
}
}
class SynWeb12306 implements Runnable {
// 票数
private int ticketNums = 10;
private boolean flag = true;
@Override
public void run() {
while (flag) {
try {
Thread.sleep(1000);
test5();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 线程安全
public synchronized void test1() {
if (ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
}
// 线程安全 范围太大-->效率低下
public void test2() {
synchronized (this) {
if (ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
}
}
// 线程不安全 ticketNums对象在变 地址一直在变
public void test3() {
synchronized ((Integer) ticketNums) {
if (ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
}
}
// 线程不安全 范围太小 锁不住
public void test4() {
synchronized (this) {
if (ticketNums <= 0) {
flag = false;
return;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
}
//线程安全 尽可能锁定合理的范围(不是指代码 指数据的完整性)
//double checking只要考虑临界值的问题
public void test5() {
if (ticketNums <= 0) {// 考虑没有票的情况
flag = false;
return;
}
synchronized (this) {
if (ticketNums <= 0) {// 考虑最后一张票
flag = false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
}
}
}
- 线程同步实例1
public class HappyCinema2 {
public static void main(String[] args) {
List<Integer> avaiable=new ArrayList<>();
for(int i=1;i<=10;i++) {
avaiable.add(i);
}
HCinema hcinema=new HCinema(avaiable,"HappyCinema");
//顾客1需要的位置
List<Integer> ava1=new ArrayList<>();
ava1.add(8);
ava1.add(2);
new Thread(new HappyCustomer(hcinema,ava1),"情侣").start();
//顾客2需要的位置
List<Integer> ava2=new ArrayList<>();
ava2.add(1);
ava2.add(5);
ava2.add(6);
new Thread(new HappyCustomer(hcinema,ava2),"单身").start();
}
}
class HappyCustomer implements Runnable{
HCinema cinema;
List<Integer> seats;
public HappyCustomer(HCinema cinema, List<Integer> seats) {
this.cinema = cinema;
this.seats = seats;
}
@Override
public void run() {
synchronized(cinema) {
boolean flag=cinema.bookTickets(seats);
if(flag) {
System.out.println("出票成功……"+"恭喜:"+Thread.currentThread().getName()+"座位:"+seats);
}else {
System.out.println("出票失败……"+"抱歉:"+Thread.currentThread().getName()+"余票:"+cinema.avaiable);
}
}
}
}
class HCinema {
List<Integer> avaiable;// 可用的座位
String name;
public HCinema(List<Integer> avaiable, String name) {
super();
this.avaiable = avaiable;
this.name = name;
}
// 购票
public boolean bookTickets(List<Integer> seats) {
System.out.println("欢迎光临:"+name+",可用座位为:" + avaiable);
List<Integer> copy=new ArrayList<>();
copy.addAll(avaiable);
//相减
copy.removeAll(seats);
//比较大小
if(avaiable.size()-copy.size()!=seats.size()) {
return false;
}
avaiable=copy;
return true;
}
}
- 线程同步实例2
- 乘客继承Thread 做代理,并可加入自己的属性
- 将购票的行为添加到Web12306中,并实现其线程化
- 通过乘客代理实现 乘客与Web12306之间的联系
- 也可将购票行为放在乘客中,通过同步块实现线程同步
- 总结:继承Thread可以继承线程的代理并具有多线程的能力,实现Runnable可以具有多线程的能力
public class Happy12306 {
public static void main(String[] args) {
Web12306 c = new Web12306(6, "HappyWeb12306");
new Passenger(c, "小寒", 2).start();
new Passenger(c, "小虎", 4).start();
}
}
//乘客继承Thread 做代理,并可加入自己的属性
//将购票的行为添加到Web12306中,并实现其线程化
//通过乘客代理实现 乘客与Web12306之间的联系
//也可将购票行为放在乘客中,通过同步块实现线程同步
//总结:继承Thread可以继承线程的代理并具有多线程的能力,实现Runnable可以具有多线程的能力
class Passenger extends Thread {
int seats;
public Passenger(Runnable target, String name, int seats) {
super(target, name);
this.seats = seats;
}
}
//火车票网
class Web12306 implements Runnable {
int avaiable;// 可用的座位
String name;
public Web12306(int avaiable, String name) {
super();
this.avaiable = avaiable;
this.name = name;
}
public void run() {
Passenger p = (Passenger) Thread.currentThread();
boolean flag = this.bookTickets(p.seats);
if (flag) {
System.out.println("出票成功……" + "恭喜:" + Thread.currentThread().getName() + "座位:" + p.seats);
} else {
System.out.println("出票失败……" + "抱歉:" + Thread.currentThread().getName() + "余票:" + avaiable);
}
}
// 购票
public synchronized boolean bookTickets(int seats) {
System.out.println("可用座位为:" + avaiable);
if (seats > avaiable) {
return false;
}
avaiable -= seats;
return true;
}
}
- 线程同步拓展-并发容器
public class SynContainer {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
while (Thread.activeCount() != 1) {
System.out.println("等待中……");
}
System.out.println(list.size());
}
}
- 死锁
- 多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能进行,而导致两个或多个线程都在等待对方资源释放,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能发生死锁的问题
- 解决:通过一个同步块中不要同时持有多个对象的锁
public class DeadLock {
public static void main(String[] args) {
Markup g1 = new Markup(1, "大丫");
Markup g2 = new Markup(0, "二丫");
g1.start();
g2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
//化妆
class Markup extends Thread {
// 一个镜子 一个口红 静态属性,不论创建多少对象只有一份资源
static Lipstick stick = new Lipstick();
static Mirror mirror = new Mirror();
// 选择 0-先获得口红 再获得镜子 1-与0相反
int choice;
// 名字
String girl;
public Markup(int choice, String girl) {
this.choice = choice;
this.girl = girl;
}
@Override
public void run() {
// 化妆
markup2();
}
// 相互持有对方的对象锁-->可能造成死锁
private void markup() {
if (choice == 0) {
synchronized (stick) {// 获得口红的锁
System.out.println(this.girl + "--->" + "涂口红!!");
// 1s后想拥有镜子的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(this.girl + "--->" + "照镜子!!");
}
}
} else {
synchronized (mirror) {// 获得镜子的锁
System.out.println(this.girl + "--->" + "照镜子!!");
// 2s后想拥有口红的锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (stick) {
System.out.println(this.girl + "--->" + "涂口红!!");
}
}
}
}
// 将对象的锁拆分出来,死锁消失
private void markup2() {
if (choice == 0) {
synchronized (stick) {// 获得口红的锁
System.out.println(this.girl + "--->" + "涂口红!!");
// 1s后想拥有镜子的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
synchronized (mirror) {
System.out.println(this.girl + "--->" + "照镜子!!");
}
} else {
synchronized (mirror) {// 获得镜子的锁
System.out.println(this.girl + "--->" + "照镜子!!");
// 2s后想拥有口红的锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
synchronized (stick) {
System.out.println(this.girl + "--->" + "涂口红!!");
}
}
}
}
- 并发协作
并发中线程通讯
- 生产者消费者模式
- 实现方式1-管程法
public class CoTest01 {
public static void main(String[] args) {
SynContainer container=new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread {
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
@Override
public void run() {
// 生产
for (int i = 0; i < 100; i++) {
System.out.println("生产--->"+i+"个馒头!");
container.push(new Steamedbun(i));
}
}
}
//消费者
class Consumer extends Thread {
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
//消费
for (int i = 0; i < 101; i++) {
System.out.println("消费--->"+container.pop().id+"个馒头!");
}
}
}
//缓冲区
class SynContainer extends Thread {
Steamedbun[] buns = new Steamedbun[10];// 存储容器
int count = 0;// 计数器
// 存储 生产
public synchronized void push(Steamedbun bun) {
//何时能生产 容器存在空间
//不能生产 只能等待
if(count==buns.length) {
try {
this.wait();//线程阻塞 消费者通知消费解除阻塞
} catch (InterruptedException e) {
}
}
//存在空间 可以生产
buns[count++] = bun;
this.notifyAll();//存在数据,可以通知消费了
}
// 获取 消费
public synchronized Steamedbun pop() {
//何时消费 容器中是否存在数据
//没有数据 只有等待
if(count==0) {
try {
this.wait();//线程阻塞 生产者通知消费解除阻塞
} catch (InterruptedException e) {
}
}
//存在数据 可以消费
Steamedbun bun = buns[--count];
this.notifyAll();//存在空间,可以唤醒对方生产了
return bun;
}
}
//馒头
class Steamedbun {
int id;
public Steamedbun(int id) {
this.id = id;
}
}
- 实现方式2-信号灯法
//借助标志位
public class CoTest02 {
public static void main(String[] args) {
Tv tv=new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者 演员
class Player extends Thread {
Tv tv;
public Player(Tv tv) {
super();
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("奇葩说!!");
} else {
this.tv.play("广告时间!!");
}
}
}
}
//消费者 观众
class Watcher extends Thread {
Tv tv;
public Watcher(Tv tv) {
super();
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
//同一个资源 电视
class Tv {
String voice;
// 信号灯
// T 表示演员表演 观众等待
// F 表示观众观看 演员等待
boolean flag = true;
// 表演
public synchronized void play(String voice) {
// 演员等待
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 表演
System.out.println("表演了:" + voice);
this.voice = voice;
// 唤醒
this.notifyAll();
//切换标识
this.flag = !this.flag;
}
// 观看
public synchronized void watch() {
// 观众等待
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 观看
System.out.println("听到了:" + voice);
// 唤醒
this.notifyAll();
//切换标识
this.flag = !this.flag;
}
}
- 高级主题
- 定时调度
public class TimerTest01 {
public static void main(String[] args) {
Timer timer=new Timer();
//执行安排
//timer.schedule(new MyTask(), 1000,200);//执行多次
Calendar cal=new GregorianCalendar(2020,7,11,21,51,03);
Calendar cal1=new GregorianCalendar();
System.out.println(cal1);
timer.schedule(new MyTask(), cal.getTime(),200);//
}
}
//任务类
class MyTask extends TimerTask{
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("Hello World!");
}
System.out.println("--------end-----------");
}
}
- 任务调度框架QUARTZ
组成部分:
- Scheduler:调度器,控制所有的调度
- Trigger:触发条件,采用DSL模式
- JobDetail:需要处理的Job
- Job:执行逻辑
- HappenBefore
- 代码很可能没有按照期望的顺序执行,因为编译器和CPU会尝试重排指令使得代码更快地运行
- 一般发生在代码于代码之间没有相互的直接联系
- 多线程环境下会出现问题
- 目的提高性能
指令执行的过程:
1、从内存获取需要执行的下一个需要执行的指令
2、指令解码翻译,从寄存器中取值
3、操作
4、操作结果写回到寄存器中 可能比较慢,存在指令重排
多线程下指令重排引起的问题:
public class HappenBefore {
//变量1
private static int a=0;
//变量2
private static boolean flag=false;
public static void main(String[] args) throws InterruptedException {
//线程1 修改数据
Thread t1=new Thread(()->{
a=1;
flag=true;
}) ;
//线程2 读取数据
Thread t2=new Thread(()->{
if(flag) {
a*=1;
}
//上面代码执行 a不可能等于0
if(a==0) {
System.out.println("Happen Before!! a->"+a);
}
}) ;
t1.start();
t2.start();
//合并线程
t1.join();
t2.join();
}
}
- volatile
//volatile用于保证数据的同步,也就是可见性
public class VolatileTest {
private static int num=0;//加上volatile 就不会存在问题
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0) {//此处不要编写代码 让cpu忙的来不及检查num是否改变
}
}) .start();
Thread.sleep(1000);
num=1;
}
}
- 单例设计模式-DCL实现
- double-checking
- volatile
/**
* 单例模式:懒汉式套路基础上加入并发控制,保证在多线程环境下,对外存在一个对象
* 1、构造器私有化---->避免外部new构造器
* 2、提供私有的静态属性--->存储对象地址
* 3、提供公共的静态方法--->获取属性(存储对象地址)
*
*/
public class DoubleCheckedLocking {
//2、提供私有的静态属性--->存储对象地址
private static volatile DoubleCheckedLocking instance;//直接初始化-饿汉式
//没有volatile 其他线程可能访问到一个没有初始化的对象
//1、构造器私有化---->避免外部new构造器
private DoubleCheckedLocking() {
}
//3、提供公共的静态方法--->获取属性(存储对象地址)
public static DoubleCheckedLocking getInstance() {
//再次检测
if(null!=instance)//避免不必要的同步,已经存在对象,若在同步块中存在延迟或操作太慢,会导致其他线程不必要的阻塞
return instance;
synchronized(DoubleCheckedLocking.class) {
if(null==instance)//首次检测
instance=new DoubleCheckedLocking();
//1、开辟空间
//2、初始化对象信息
//3、返回对象的地址给引用
//当初始化太慢,3可能先于2-->指令重排 导致线程A线程在初始化时,B线程已经拿到了引用-->空对象
//会有问题,故在属性声明时加上volatile
}
return instance;
}
public static void main(String[] args) {
Thread t=new Thread(()->{
System.out.println(DoubleCheckedLocking.getInstance());
}) ;
t.start();
System.out.println(DoubleCheckedLocking.getInstance());
}
}
- ThreadLocal
1、基础操作
/**
* ThreadLocal:每个线程自身的存储本地、局部区域
* get/set/initialValue
*
*/
public class ThreadLocalTest01 {
// private static ThreadLocal<Integer> threadLocal=new ThreadLocal<>();
//更改初始化值
/*private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 200;
}
};*/
//jdk8之后 lambda表达式
private static ThreadLocal<Integer> threadLocal=ThreadLocal.withInitial(()->200);
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get());
//设置值
threadLocal.set(99);
System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get());
new Thread(new MyRun()).start();
System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get());
}
public static class MyRun implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get());
threadLocal.set((int)(Math.random()*99));
System.out.println(Thread.currentThread().getName()+"--->"+threadLocal.get());
}
}
}
2、每个线程自身的存储自己数据 更改不会影响其他线程
/**
* ThreadLocal:每个线程自身的存储自己数据 更改不会影响其他线程
* get/set/initialValue
*
*/
public class ThreadLocalTest02 {
//更改初始化值
//jdk8之后 lambda表达式
private static ThreadLocal<Integer> threadLocal=ThreadLocal.withInitial(()->1);
public static void main(String[] args) {
for(int i=0;i<5;i++) {
new Thread(new MyRun()).start();
}
}
public static class MyRun implements Runnable{
@Override
public void run() {
Integer left=threadLocal.get();
System.out.println(Thread.currentThread().getName()+"得到了--->"+left);
threadLocal.set(left-1);
System.out.println(Thread.currentThread().getName()+"还剩下--->"+threadLocal.get());
}
}
}
//运行结果:
Thread-0得到了--->1
Thread-1得到了--->1`在这里插入代码片`
Thread-1还剩下--->0
Thread-4得到了--->1
Thread-4还剩下--->0
Thread-3得到了--->1
Thread-2得到了--->1
Thread-0还剩下--->0
Thread-2还剩下--->0
Thread-3还剩下--->0
3、ThreadLocal:分析上下文 环境 注意起点
/**
* ThreadLocal:分析上下文 环境 注意起点
* 1、构造器 哪里调用就属于哪里 找线程体
* 2、run方法 本线程自身的
* get/set/initialValue
*
*
*/
public class ThreadLocalTest03 {
// 更改初始化值
// jdk8之后 lambda表达式
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
public static void main(String[] args) {
new Thread(new MyRun()).start();
}
public static class MyRun implements Runnable {
public MyRun() {
System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "还剩下--->" + threadLocal.get());
}
}
}
//运行结果:
main--->1
Thread-0还剩下--->1
4、InheritableThreadLocal:继承上下文 环境 的数据 ,拷贝一份给子线程 注意起点
/**
* InheritableThreadLocal:继承上下文 环境 的数据 ,拷贝一份给子线程 注意起点
* 1、构造器 哪里调用就属于哪里 找线程体
* 2、run方法 本线程自身的
*
* */
public class ThreadLocalTest04 {
// 更改初始化值
// jdk8之后 lambda表达式
private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(2);
System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
//线程由main线程开辟
new Thread(()->{
//若果InheritableThreadLocal换成ThreadLocal该处输出为null
System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
threadLocal.set(200);
System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
}) .start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
}
}
//运行结果:
main--->2
Thread-0--->2
Thread-0--->200
main--->2
- 可重入锁
- juc并发编程中有可重入锁的实现:ReentrantLock
模拟不可重入锁
/**
* 不可重入锁:锁不可以延续使用
*
*
*/
public class LockTest02 {
Lock lock=new Lock();
public void a() throws InterruptedException {
lock.lock();
doSomething();
lock.unlock();
}
//不可重入
public void doSomething() throws InterruptedException {
lock.lock();
//..........
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
LockTest02 test=new LockTest02();
test.a();
test.doSomething();
}
}
class Lock {
// 是否占用
private boolean isLocked = false;
// 使用锁
public synchronized void lock() throws InterruptedException {
while(isLocked) {
wait();
}
isLocked=true;
}
// 释放锁
public synchronized void unlock() {
isLocked=false;
notify();
}
}
模拟可重入锁
/**
* 可重入锁:锁可以延续使用+计数器
*
*
*/
public class LockTest03 {
ReLock lock=new ReLock();
public void a() throws InterruptedException {
lock.lock();
System.out.println("锁计数器--->"+lock.getHoldCount());
doSomething();
lock.unlock();
System.out.println("锁计数器--->"+lock.getHoldCount());
}
//可重入
public void doSomething() throws InterruptedException {
lock.lock();
System.out.println("锁计数器--->"+lock.getHoldCount());
//..........
lock.unlock();
System.out.println("锁计数器--->"+lock.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
LockTest03 test=new LockTest03();
test.a();
test.doSomething();
System.out.println(test.lock.getHoldCount());
}
}
//可重入锁
class ReLock {
// 是否占用
private boolean isLocked = false;
private Thread lockedBy=null;//存储线程
private int holdCount=0;
// 使用锁
public synchronized void lock() throws InterruptedException {
Thread t=Thread.currentThread();
while(isLocked && lockedBy!=t) {
wait();
}
isLocked=true;
lockedBy=t;
holdCount++;
}
// 释放锁
public synchronized void unlock() {
if(Thread.currentThread()==lockedBy) {
holdCount--;
if(holdCount==0) {
isLocked=false;
lockedBy=null;
notify();
}
}
}
public int getHoldCount() {
return holdCount;
}
}
- CAS
- Atomic 原子操作,底层CAS思想,实现线程安全
/**
* CAS:比较并交换
*
*/
public class CAS {
private static AtomicInteger stock=new AtomicInteger(5);
public static void main(String[] args) {
for(int i=0;i<6;i++) {
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Integer left=stock.decrementAndGet();
if(left<0) {
System.out.println("抢完了!!!");
return;
}
System.out.println(Thread.currentThread().getName()+"抢了一件商品,"+"还剩:"+left);
}) .start();
}
}
}
- 多线程总结
1、线程的概念:多线程多条路径
2、三种实现方式
3、线程状态:新生、就绪、运行、阻塞、死亡
4、停止线程:实现一个标识,线程体中使用,对外提供改变标识的方法
5、线程基本方法:sleep wait join yield 获得线程名 优先级 用户/守护线程
6、并发:多线程环境下对同一份资源同时访问 ,同步:synchronized(同步块、同步方法)、CAS(保证原子操作)、volatile(线程间数据可见性)
7、并发协作:生产者-消费者:管程法、信号灯发
8、juc高级并发编程