Java多线程基础巩固系列

多线程

多线程

一.创建方式(两个重要+一个了解)

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

结论

  1. 放大问题的发生性
  2. sleep(时间) 值得是当前线程阻塞的毫秒数。
  3. sleep存在异常InterruptedException。
  4. sleep时间到达以后线程进入就绪状态。
  5. sleep可以模拟网络延时,倒计时等。
  6. 每个对象都有一把锁,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(礼让不一定成功)

结论

  1. 礼让线程,让当前正在执行的线程暂停,但不阻塞
  2. 将线程从运行状态转为就绪状态
  3. 让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

总结

  1. join合并线程,等待此线程执行完毕之后,再执行其他线程,其他线程阻塞。
  2. 可以理解强制插队,可能在这之前是线程交替执行,一旦调用了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);
        }
    }
}

十一.线程的优先级

结论

  1. 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看cpu的调度。
  2. 优先级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 -人生不过三万天

结论

  1. 线程分为用户线程,守护线程
  2. 虚拟机必须确保用户线程执行完毕
  3. 虚拟机不用等待守护线程执行完毕
  4. 如后台记录操作日志,监控内存,垃圾回收等
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同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

十六. CopyOnWriteArrayList

-

十七.死锁

总结

  1. 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的清情形,某一个**同步块同时拥有“两个以上对象的锁“**时,可能会发生”死锁“问题。

  2. 产生死锁的四个必要条件

    1. 互斥条件:一个资源每次只能被一个进程使用

    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放

    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺

    4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

      上述四个条件必要条件,只要破坏其中一个或一个以上,就可以比避免死锁的发生。

案例(口红、镜子-化妆)

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锁

总结

  1. jdk5.0之后,java提供了更强大的线程同步机制----通过显式定义同步锁对象来实现同步。同步锁使用lock来充当
  2. java.util.concurrent.locks接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  3. 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的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放

  2. Lock只有代码块锁,synchronized有代码块锁和方法锁

  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供了更多的子类)

  4. 使用优先顺序

    Lock -> 同步代码块 (已经进入了方法体,分配了相应资源) ->同步方法 (在方法体之外)

十九.生产者消费者问题

-

二十.管程法

-

二十一. 信号灯法

-

二十二. 线程池

总结

  1. ExecutorService是真正的线程接口。常见的子类ThreadPoolExecutor
  2. 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());
    }
}

二十三. 总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北溟南风起

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值