多线程
多线程
一.创建方式(两个重要+一个了解)
1. extends Thread(重要)
2. implements Runnable(重要+广泛使用)
3. implements Callable (了解)
二.案例
1. 购买车票
2. 龟兔赛跑
3. 网图下载 (借助commons Io依赖)
三.静态代理(婚庆公司的例子)
好处
做很多真实对象无法做的事情**。比如,婚庆公司可以布置会场,可以做一些服务工作,而新人只需关注结婚本身,婚礼则由婚庆公司来主持和控场。
package com.zhagnshijie.thread;
public class StaticProxy {
public static void main(String[] args) {
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.HappyMarry();//代理target进行结婚
}
}
interface Marry{
void HappyMarry();
}
//真实对象 被代理对象
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("嘿嘿嘿,要结婚了,超开心!");
}
}
//代理 婚庆公司
class WeddingCompany implements Marry{
private Marry target;
WeddingCompany(Marry target){
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("结婚之前,布置婚礼现场!");
}
private void after() {
System.out.println("结婚之后,收取尾款!");
}
}
四.Lambda表达式
总结
1. lambda表达时前提函数式接口(任何接口,如果只包含**唯一的一个抽象方法**,那么就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来常见该接口的对象)
2. lambda表达式只有有一行代码的时候,才能把{}去掉简化成一行,如果有多行,必须使用{}包裹。
3. 多个参数,可以去掉参数类型,要去掉都去掉,不去都不去,但是()必须有!
4. 代码看起来简洁。
5. 去掉一堆没有意义的代码 ,只留下核心逻辑。
案例(记录向lambda演变的过程)
1. 接口+普通实现类
public interface Testlambda {
public static void main(String[] args) {
Like like = new Like();
like.lambda(); //I like lambda
}
}
//0.定义一个函数式接口(一个接口,且仅包含一个方法)
interface Ilike{
void lambda();
}
//1.实现类
class Like implements Ilike{
@Override
public void lambda() {
System.out.println("I like lambda");
}
}
2. 接口+静态内部类
public interface Testlambda {
//2.静态内部类
static class Like implements Ilike{
@Override
public void lambda() {
System.out.println("I like lambda-静态内部类");
}
}
public static void main(String[] args) {
Like like = new Like();
like.lambda(); //I like lambda-静态内部类
}
}
//0.定义一个函数式接口(一个接口,且仅包含一个方法)
interface Ilike{
void lambda();
}
3. 接口+局部内部类
public interface Testlambda {
public static void main(String[] args) {
//3.局部内部类
class Like implements Ilike{
@Override
public void lambda() {
System.out.println("I like lambda-局部内部类");
}
}
Like like = new Like();
like.lambda(); //I like lambda-局部内部类
}
}
//0.定义一个函数式接口(一个接口,且仅包含一个方法)
interface Ilike{
void lambda();
}
4. 接口+匿名内部类
public interface Testlambda {
public static void main(String[] args) {
//4.匿名内部类(没有类的名称,必须借助接口或父类)
Ilike like = new Ilike() {
@Override
public void lambda() {
System.out.println("I like lambda --匿名内部类");
}
};
like.lambda();//I like lambda --匿名内部类
}
}
//0.定义一个函数式接口(一个接口,且仅包含一个方法)
interface Ilike{
void lambda();
}
5. 接口+lambda简化(无参)
public interface Testlambda {
public static void main(String[] args) {
//4.jdk1.8 用lambda简化
Ilike like = () ->{
System.out.println("I like lambda --lambda简化");
};
like.lambda();//I like lambda --lambda简化
}
}
//0.定义一个函数式接口(一个接口,且仅包含一个方法)
interface Ilike{
void lambda();
}
6.接口+lambda简化(带参)
public class Testlambda2 {
public static void main(String[] args) {
Show show = (a,b)-> System.out.println("跑通过了 程序!"+ a+" 年龄:"+b);
show.showmsg("世界",18); //跑通过了 程序!世界 年龄:18
}
}
//0.定义一个接口
interface Show{
void showmsg(String a,Integer b);
}
五. 线程状态(5种)
新建 就绪 等待 计时等待 阻塞 死亡
六.线程停止
1. 自然结束
2. 使用标志位
public class ThreadStop implements Runnable{
//1.确定一个标志位
private boolean flag = true;
@Override
public void run() {
int i= 0;
while (flag) {
System.out.println("-------次线程跑到"+i++);
}
}
//2.设置一个公开的方法停止线程,设置标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
new Thread(threadStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程跑了"+i);
if(i==800)
{
System.out.println("此线程要停止啦!!!");
threadStop.stop();
}
}
}
}
3.JDK提供的Stop(不推荐,还有一个destroy方法已废弃)
七.线程休眠sleep
结论
- 放大问题的发生性。
- sleep(时间) 值得是当前线程阻塞的毫秒数。
- sleep存在异常InterruptedException。
- sleep时间到达以后线程进入就绪状态。
- sleep可以模拟网络延时,倒计时等。
- 每个对象都有一把锁,sleep不会释放锁。
1. 10秒倒计时
public class TestSleep {
public static void main(String[] args) {
tenDown();//调用倒计时(直接调用类的方法,而不用new一个类的实例)
}
//倒计时10秒
public static void tenDown(){//类的静态方法,可以直接调用
int num =10;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if(num<=0)
break;
}
}
}
2. 打印系统时间
public class SleepClock {
public static void main(String[] args) {
//打印当前系统时间
Date startTime = new Date(System.currentTimeMillis());//获取当前系统时间
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());//更新当前系统时间
}
}
}
3. 模拟网络延时(网络购票)
八.礼让线程yield(礼让不一定成功)
结论
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功,看cpu心情
public class YieldThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"停止执行");
}
public static void main(String[] args) {
YieldThread yieldThread = new YieldThread();
new Thread(yieldThread,"a").start();
new Thread(yieldThread,"b").start();
}
}
九.插队线程join
总结
- join合并线程,等待此线程执行完毕之后,再执行其他线程,其他线程阻塞。
- 可以理解强制插队,可能在这之前是线程交替执行,一旦调用了A.join()方法之后,就一定是A线程执行完毕之后,再执行其他的。
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("vip在执行----"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程正在执行----"+i);
if(i==500)
{
thread.join();//插队
}
}
}
}
十.观测线程的状态state
public class ViewState implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----runing-----");
}
}
public static void main(String[] args) {
ViewState viewState = new ViewState();
Thread thread = new Thread(viewState);
Thread.State state = thread.getState();//获取启动钱的状态
System.out.println(state);
thread.start();
state = thread.getState();
System.out.println(state);
while(state!=Thread.State.TERMINATED){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState();
System.out.println(state);
}
}
}
十一.线程的优先级
结论
- 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看cpu的调度。
- 优先级MIN=1 MAX=10 NORMAL=5,超出这个范围会报错。
设置优先级
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority);
thread1.setPriority(2);
thread1.start();
获取优先级(输出当前线程的优先级)
System.out.println(Thread.currentThread().getName()+"------正在执行"+"优先级是:"+Thread.currentThread().getPriority());
十二. 守护线程daemon -人生不过三万天
结论
- 线程分为用户线程,守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志,监控内存,垃圾回收等
public class DeamonThread{
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread threadYou = new Thread(you);
threadYou.start();
Thread threadGod = new Thread(god);
threadGod.setDaemon(true);//默认为false,为false的都为用户线程
threadGod.start();
}
static class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("你正在运行---");
}
}
}
static class God implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("上帝守护着你---"+i);
}
}
}
}
你正在运行---
你正在运行---
你正在运行---
你正在运行---
你正在运行---
你正在运行---
你正在运行---
你正在运行---
你正在运行---
你正在运行---
上帝守护着你---0
上帝守护着你---1
上帝守护着你---2
上帝守护着你---3
Process finished with exit code 0
十三. 线程同步(重点+难点)队列+锁
并发
多个线程同时操作同一个对象。 类似于排队在同一个窗口买饭。
形成条件
队列+锁
十四.三大不安全案例
1. 购票
public class UnsafeBuyTickets {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets,"a").start();
new Thread(buyTickets,"b").start();
new Thread(buyTickets,"c").start();
}
}
class BuyTickets implements Runnable{
private int ticketsnum=10;//最初有十张票
boolean flag=true;//true可以买票 false不可以买票
@Override
public void run() {
//买票
while(flag){
buy();
if(ticketsnum<=0)
flag = false;
}
}
void buy(){
System.out.println(Thread.currentThread().getName()+"买了"+ticketsnum--+"号票");
}
}
2.银行账户取钱
ublic class UnsafeAccount {
public static void main(String[] args) {
Account account = new Account(200,"结婚");//新建账户实例,初始化账户信息
DrawMoney you = new DrawMoney(account,50,"你");
DrawMoney firlFriend = new DrawMoney(account,100,"媳妇");
you.start();
firlFriend.start();
}
}
//账户
class Account{
int money;
String name;
public Account(int money,String name){
this.money = money;
this.name = name;
}
}
//银行
class DrawMoney extends Thread{
//账户
Account account;
//当次取出多少钱
int drawingMoney;
//当前手里多少钱
int nowMoney;
public DrawMoney(Account account,int drawingMoney,String name){
super(name);//传入当前线程的名字。以便于下面使用this.getName()表达线程名称
this.account = account;
this.drawingMoney=drawingMoney;
}
//取钱
@Override
public void run() {
//模拟网络延时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了!");
return;
}else{
//银行卡内余额=余额-取出来的钱
account.money=account.money - drawingMoney;
//手中的钱
nowMoney = nowMoney + drawingMoney;
System.out.println("卡中余额:"+account.money+" 手中的钱:"+nowMoney);
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
}
3.线程不安全的集合arraylist
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread( ()->arrayList.add(Thread.currentThread().getName()) ).start();
}
System.out.println(arrayList.size());
}
}
//这个可以验证arranList与Vector的线程是否安全的问题
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<String>();
//Vector<String> vector = new Vector<String>();
for (int i = 0; i < 10000; i++) {
new Thread( ()->arrayList.add(Thread.currentThread().getName()) ).start();
//new Thread( ()->vector.add(Thread.currentThread().getName()) ).start();
}
try {
Thread.sleep(20000);//给线程一些时间,不然的话,你直接通过主线程打印出来的数据可能不是最终的。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(arrayList.size());
//System.out.println(vector.size());
}
}
十五.同步方法和同步块
1.同步方法 public synchronized void method(int args){}
每个对象对应一把锁,每个synchronized方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
方法里面只有需要修改的内容才需要锁。
缺点 : 若一个大的方法申明为synchronized将会影响效率。
锁的太多,浪费资源。
2.同步块
synchronized (Obj) { }
Obj称为同步监视器 (Obj是执行过程中需要增删改查的对象,会改变的对象)
- Obj可以是任意对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class.
Obj同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
十六. CopyOnWriteArrayList
-
十七.死锁
总结
-
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的清情形,某一个**同步块同时拥有“两个以上对象的锁“**时,可能会发生”死锁“问题。
-
产生死锁的四个必要条件
-
互斥条件:一个资源每次只能被一个进程使用
-
请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放
-
不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
-
循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
上述四个条件必要条件,只要破坏其中一个或一个以上,就可以比避免死锁的发生。
-
案例(口红、镜子-化妆)
package com.zhagnshijie.thread;
public class DeadLock {
public static void main(String[] args) {
MakeUp girl1 = new MakeUp(0,"灰姑娘");
MakeUp girl2 = new MakeUp(1,"白雪公主");
new Thread(girl1,"灰姑娘").start();
new Thread(girl2,"白雪公主").start();
}
}
//口红
class LickRed{
}
//镜子
class Mirror{
}
//化妆
class MakeUp implements Runnable {
//保证资源一种只有一份(口红、镜子各有一个),用static来保证
static private LickRed lickred = new LickRed(); //这里可能有问题
static private Mirror mirror = new Mirror();
private int choice;
private String user;
@Override
public void run() {//线程体
makeup();//化妆
}
//构造器是外部来调用的,所以肯定是public ...
public MakeUp(int choice, String user) {//构造方法
this.choice = choice;
this.user = user;
}
void makeup() {
if (choice == 0) {//一上来就选择拥有口红
synchronized (lickred) {
System.out.println(Thread.currentThread().getName() + "获得了口红");
try {
Thread.sleep(1000);//防止一瞬间把两个资源都拿走
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(Thread.currentThread().getName() + "获得了镜子");
}
}
} else {
synchronized (mirror) { //一上来就选择拥有镜子
System.out.println(Thread.currentThread().getName() + "获得了镜子");
try {
Thread.sleep(2000);//防止一瞬间把两个资源都拿走
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lickred) {
System.out.println(Thread.currentThread().getName() + "获得了口红");
}
}
}
}
十八.Lock锁
总结
- jdk5.0之后,java提供了更强大的线程同步机制----通过显式定义同步锁对象来实现同步。同步锁使用lock来充当
- java.util.concurrent.locks接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在线程实现安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁
案例(买票)
public class LockTickets {
public static void main(String[] args) {
buyTicket buyTicket = new buyTicket();
new Thread(buyTicket,"a").start();
new Thread(buyTicket,"b").start();
new Thread(buyTicket,"c").start();
}
}
class buyTicket implements Runnable{
private final ReentrantLock reentrantLock = new ReentrantLock();
int num = 20;
@Override
public void run() {
while (true){
reentrantLock.lock();//上锁
try{
if(num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"购买了"+num--+"号票");
}
else
{
break;
}
}
finally {
reentrantLock.unlock();//开锁
}
}
}
}
lock与synchronized的对比
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供了更多的子类)
-
使用优先顺序
Lock -> 同步代码块 (已经进入了方法体,分配了相应资源) ->同步方法 (在方法体之外)
十九.生产者消费者问题
-
二十.管程法
-
二十一. 信号灯法
-
二十二. 线程池
总结
- ExecutorService是真正的线程接口。常见的子类ThreadPoolExecutor
- Executors:工具类、线程池的工厂类,用于创建返回不同的线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务 创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);//创建一个有10个线程的池子
//2.执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//3.关闭
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}