Java进阶--多线程


多线程: 多线程是模拟出来的,真正的多线程是指有多个CPU,即多核处理器,如服务器。在只有一个CPU的情况下,同一个时间点,CPU只能执行一个线程,但是线程之间切换很快,从而在感觉上是所有线程同时执行。

进程

进程: 程序的一次执行的过程,它是一个动态的概念,是系统分配资源的最小单位。

  • 一个进程可以包含多个线程,且至少包含一个线程。
  • 一个进程中如果开辟了多个线程,线程的运行由调度器安排调度,调度器由操作系统决定,线程的先后顺序不能人为干预。
    • 线程开启不一定会立即执行,根据调度器的调用执行

线程

线程: 线程是CPU调度和执行的单位,是携带资源的最小单位。main函数是主线程,是系统的入口,用于执行整个程序。

线程的创建

方式一:继承Thread类

继承Thread类,重写run方法,创建线程对象调用start方法开启线程。

public class testThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run---"+i);
        }
    }

    public static void main(String[] args) {
        //主线程

        //创建一个线程对象
        testThread1 tt=new testThread1();
        //调用start方法开启线程
        tt.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--"+i);
        }
    }
}

方式二:实现Runnable接口

通过实现Runnable接口,重写run方法,编写线程执行体,创建线程对象,调用start方法启动线程。

  • 避免了单继承局限性,灵活方便,方便一个对象被多个线程使用,更推荐使用这种方式。
public class testThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run---"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        testThread2 tt2=new testThread2();

        //创建线程对象,通过线程对象开启线程,代理方式
        Thread thread=new Thread(tt2);
        thread.start();
//        new Thread(tt2).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--"+i);
        }
    }
}

方式三:实现Callable接口

  • callable的好处:
    • 可以定义返回值
    • 可以抛出异常
public class testThread3 implements Callable<Boolean> {
    private String url;     //网络图片地址
    private String name;    //保存的文件名

    public testThread3(String url,String name){
        this.url=url;
        this.name=name;
    }

    //下载图片线程的执行体
    @Override
    public Boolean call() {
        WebDownLoader webDownLoader=new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建三个线程
        testThread3 ttd1=new testThread3("https://img1.baidu.com/it/u=2221573262,2740693252&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=313","1.jpg");
        testThread3 ttd2=new testThread3("https://img1.baidu.com/it/u=1544557496,2399568994&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","2.jpg");
        testThread3 ttd3=new testThread3("https://img1.baidu.com/it/u=2819817245,1398856886&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","3.jpg");

        //创建执行服务:
        ExecutorService ser= Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1=ser.submit(ttd1);
        Future<Boolean> r2=ser.submit(ttd2);
        Future<Boolean> r3=ser.submit(ttd3);
        //获取结果
        boolean rs1=r1.get();
        boolean rs2=r2.get();
        boolean rs3=r3.get();
        //关闭服务
        ser.shutdownNow();
    }

    class WebDownLoader{
        public void downloader(String url,String name){
            try {
                FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,downloader方法出现问题");
            }
        }
    }
}

运行时主线程和子线程会交替执行,运行结果如下(上述三种创建方式运行结果类似,用下图作为一个举例):
在这里插入图片描述

案例:使用多线程下载网络图片(继承Thead类)
public class testThreadDemo extends Thread{
    private String url;     //网络图片地址
    private String name;    //保存的文件名

    public testThreadDemo(String url,String name){
        this.url=url;
        this.name=name;
    }

    //下载图片线程的执行体
    @Override
    public void run() {
        WebDownLoader webDownLoader=new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        //创建三个线程
        testThreadDemo ttd1=new testThreadDemo("https://img1.baidu.com/it/u=2221573262,2740693252&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=313","1.jpg");
        testThreadDemo ttd2=new testThreadDemo("https://img1.baidu.com/it/u=1544557496,2399568994&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","2.jpg");
        testThreadDemo ttd3=new testThreadDemo("https://img1.baidu.com/it/u=2819817245,1398856886&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","3.jpg");

        //启动线程,线程执行的顺序不确定
        ttd1.start();
        ttd2.start();
        ttd3.start();
    }
}
案例:使用多线程下载网络图片(实现Runnable接口)
public class testThreadDemo implements Runnable{
    private String url;     //网络图片地址
    private String name;    //保存的文件名

    public testThreadDemo(String url,String name){
        this.url=url;
        this.name=name;
    }

    //下载图片线程的执行体
    @Override
    public void run() {
        WebDownLoader webDownLoader=new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        //创建三个线程
        testThreadDemo ttd1=new testThreadDemo("https://img1.baidu.com/it/u=2221573262,2740693252&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=313","1.jpg");
        testThreadDemo ttd2=new testThreadDemo("https://img1.baidu.com/it/u=1544557496,2399568994&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","2.jpg");
        testThreadDemo ttd3=new testThreadDemo("https://img1.baidu.com/it/u=2819817245,1398856886&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","3.jpg");

        new Thread(ttd1).start();
        new Thread(ttd2).start();
        new Thread(ttd3).start();
    }
}


class WebDownLoader{
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

案例运行结果: 线程的执行顺序跟开启的顺序无关,依赖于CPU的调度。
在这里插入图片描述

案例:龟兔赛跑(Runnable)
public class testRace implements Runnable{
    //胜者只有一个,所以可以使用静态变量
    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //模拟兔子休息
            if(Thread.currentThread().getName().equals("兔子") && i%10==0){
                try {
                    //设置兔子的休息时间是20-70的随机数
                    Thread.sleep((int) (Math.random()*50+20));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //模拟乌龟爬的慢
            if(Thread.currentThread().getName().equals("乌龟")){
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag=gameOver(i);
            if(flag){
                break;
            }
            //输出比赛进程
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }

    private boolean gameOver(int steps){
        if(winner!=null){
            return true;
        }{
            //判断是否有人到达终点
            if(steps==100){
                winner=Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        testRace race=new testRace();
        new Thread(race,"乌龟").start();
        new Thread(race,"兔子").start();
    }
}
案例:买票(线程不安全),线程安全的案例在线程同步部分讲解
public class testThreadDemo1 implements Runnable{
    private int ticketNum=10;       //总票数

    @Override
    public void run() {
        while (true){
            if(ticketNum<=0)
                break;
            //模拟延时,避免全部被分配给一个人
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到第"+ticketNum--+"张票");

        }
    }

    public static void main(String[] args) {
        //创建Runnable接口实现类对象
        testThreadDemo1 ttd=new testThreadDemo1();

        //开启线程
        new Thread(ttd,"小张").start();
        new Thread(ttd,"小李").start();
        new Thread(ttd,"黄牛党").start();
    }
}

运行结果: 线程不安全所以有相同的人拿到了相同的票
在这里插入图片描述

线程的特点:

  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
  • 线程会带来额外的开销,如CPU调度使劲按,并发控制开销。
  • 线程的执行顺序跟开启的顺序无关,依赖于CPU的调度。
  • 多个线程对同一份资源操作时,会存在资源抢夺问题,所以需要加入并发控制。

线程五大状态

线程的五大状态:创建,就绪,阻塞,执行,死亡。
各个状态之间相互关系可以通过下图清晰的了解:
在这里插入图片描述
在这里插入图片描述

线程常用方法

线程停止

  • 建议线程正常停止
  • 建议使用标志位
  • 不要使用stop或者destroy等过时的方法
案例:线程停止
public class testStop 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) {
        testStop stop=new testStop();
        //开启线程
        new Thread(stop).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if(i==900){
                //调用stop方法停止线程
                stop.stop();
                System.out.println("停止线程!");
            }
        }
    }
}

线程停止后,主线程正常执行,结果图如下:
在这里插入图片描述

线程休眠

  • sleep(时间),当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
    • 可以使用sleep模拟网络延迟,倒计时等
    • 每一个对象都有一个锁,sleep不会释放锁(具体在线程同步和死锁讲解)
案例:线程休眠–可以模拟倒计时,打印系统时间
public class testSleep {
    public static void main(String[] args) {
        //模拟倒计时
        /*try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        //打印系统当前时间
        Date startTime=new Date(System.currentTimeMillis());//获取系统当前时间
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime=new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void tenDown() throws InterruptedException {
        int num=10;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<0){
                break;
            }
        }
    }
}

线程礼让

  • 让当前正在执行的线程暂停,但不阻塞,即将线程从运行状态转变为就绪状态
  • 让CPU重新调度,礼让不一定成功,线程重新进入就绪队列后还是会和就绪队列中的其他线程竞争,能否成功需要看CPU的调度
案例:线程礼让

线程小明礼让线程小红,所以小明线程先执行,但因为礼让进入阻塞状态,小红线程顺利开启,并执行完毕后,小明线程才能执行完毕。

public class testYield {
    public static void main(String[] args) {
        MyYield myYield=new MyYield();
        new Thread(myYield,"小明").start();
        new Thread(myYield,"小红").start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程结束执行");
    }
}

结果如图所示:
在这里插入图片描述

线程强制执行

  • join合并线程,待此线程执行完成后再执行其他线程,其他线程阻塞
案例:线程强制执行

普通客户线程先执行,10次后,VIP线程前来办理业务,并强制执行,普通客户线程进入阻塞状态,等待VIP线程执行完毕后才能继续执行。

public class testJoin implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        testJoin tj=new testJoin();
        Thread thread=new Thread(tj);

        for (int i = 0; i < 30; i++) {
            //普通客户线程执行到10时,VIP客户来办理业务,
            //要等待VIP客户办理完成后,普通客户才能继续办理
            if(i==10){
                thread.start();
                thread.join();
            }
            System.out.println("普通客户办理业务"+i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("VIP客户办理业务!"+i);
        }
    }
}

结果如图所示:
在这里插入图片描述

案例:观察线程状态

观察线程从创建,就绪(开启),阻塞(sleep),结束的状态变化

public class testState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("end!!!");
        });
        //观测状态
        Thread.State state = thread.getState();
        System.out.println(state);//new
        //观察启动后
        thread.start();
        state=thread.getState();
        System.out.println(state);
        //只要线程不终止,就一直输出状态
        while (state!=Thread.State.TERMINATED){
            Thread.sleep(100);
            state=thread.getState();
            System.out.println(state);
        }

    }
}

结果如图所示: 中间waiting的时间就是线程sleep的时间
在这里插入图片描述

Lambda表达式

  • 目的
    • 避免匿名内部类定义过多
    • 简化代码,只留下核心逻辑
  • 特点
    • Lambda表达式只有一行代码时才能简化成一行,若有多行则需要用代码块包裹
    • 使用Lambda表达式的前提是接口是函数式接口
    • 多个参数也可以去掉参数类型,但是所有参数要统一,但必须加上括号

为了更好的理解Lambda表达式,要先介绍一下函数式接口。

函数式接口

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
  • 对于函数式接口,可以使用lambda表达式来创建该接口的对象

静态代理模式

  • 思路:
    • 真实对象和代理对象要实现同一个接口
    • 代理对象去代理真实对象
  • 好处:
    • 代理对象可以做很多真实对象做不了的事情
    • 真实对象只需要做自己的事情
案例:婚庆公司帮要结婚的人结婚,作为人只需要做结婚这一件事,其他由婚庆公司代理。

函数式接口:结婚接口

// 只包含唯一一个抽象方法
public interface Marry {
    void HappyMarry();
}

要结婚的人:只做结婚这一件事

public class Person implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("我开心的结婚了");
    }
}

婚庆公司:帮助要结婚的人完成结婚的其他事项

//继承Marry接口
public class WeddingCompany implements Marry{
    private Marry target;

    public 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("结婚后,收拾现场及尾款");
    }

}

静态代理:

public class testStaticProxy {
    public static void main(String[] args) {
        WeddingCompany wc=new WeddingCompany(new Person());
        wc.HappyMarry();
    }
}

了解函数式接口和静态代理后,我们通过一个案例进一步了解Lambda表达式,后续内容也会使用Lambda表示式来简化代码。

案例:Lambda表达式

Lambda表达式实现无参数的函数式接口

public interface ILike {
//    testLambda
    void lambda();

}

实现类

public class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}

通过不同的方式来实现函数式接口的方法,观察Lambda表达式的简化效果

public class testLambda {
    //静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("i like lambda too");
        }
    }

    public static void main(String[] args) {
        //1.使用实现类
        ILike like=new Like();
        like.lambda();
        //2.使用静态内部类
        like=new Like2();
        like.lambda();

        //局部内部类
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("i also like lambda");
            }
        }
        //3.使用局部内部类
        like=new Like3();
        like.lambda();

        //4.使用匿名内部类
        like=new ILike() {
            @Override
            public void lambda() {
                System.out.println("i always like lambda");
            }
        };
        like.lambda();

        //5.用lambda简化
        like=()->{
            System.out.println("i like Lambda, i am Lambda");
        };
        like.lambda();
    }
}

Lambda表达式实现有参数(多参数)的函数式接口

多参数的函数式接口,也可以是单参数

public interface ILove {
//    也可以有参数,testLambda2
    void lambda(int a,int b);

}

Lambda表达式实现多参数函数式接口时,不能简化括号和方法体的花括号

public class testLambda2 {
    public static void main(String[] args) {
        //用lambda简化
        ILove love=(int a,int b)->{
            System.out.println("i like Lambda--"+a);
        };
        //简化参数类型
        love=(a,b)->{
            System.out.println("i like Lambda--"+a+"---"+b);
        };
        //简化括号,单参数才可以省略括号
        /*love=a->{
            System.out.println("i like Lambda--"+a);
        };*/
        //简化花括号
//        love=a-> System.out.println("i like Lambda--"+a);

        love.lambda(521,522);
    }
}

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行

  • 优先级范围从1-10
    • Thread.MIN_PRIORITY=1
    • Thread.MAX_PRIORITY=10
    • Thread.NORM_PRIORITY=5
  • 优先级低只是意味着获得调度的概率低,并不是优先级低的就一定会后执行,线程的执行顺序看CPU的调度
  • 可以使用getPriority,setPriority方法获取和改变线程优先级

案例:设置线程优先级

public class testPriority {
    public static void main(String[] args) {
        //main线程优先级默认为5
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MyPriority myPriority=new MyPriority();
        Thread thread1=new Thread(myPriority);
        Thread thread2=new Thread(myPriority);
        Thread thread3=new Thread(myPriority);
        Thread thread4=new Thread(myPriority);
        Thread thread5=new Thread(myPriority);
        //先设置优先级,再启动
        thread1.start();//没有设置优先级,默认优先级为5

        thread2.setPriority(1);
        thread2.start();

        thread3.setPriority(4);
        thread3.start();

        thread4.setPriority(Thread.MAX_PRIORITY);
        thread4.start();

    }

}

class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println("线程"+Thread.currentThread().getName()+"优先级-->"+Thread.currentThread().getPriority());
    }
}

守护线程(daemon)

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕,但不必等待守护线程结束
    • 如后台记录操作日志,监控内存,垃圾回收等。

案例

public class testDaemon {
    public static void main(String[] args) {
        Earth earth=new Earth();
        Person person=new Person();
        //设置守护线程,正常线程都是用户线程
        Thread thread=new Thread(earth);
        thread.setDaemon(true); //默认值为false
        thread.start();
        //开启用户线程
        new Thread(person).start();
    }
}

class Earth implements Runnable{

    @Override
    public void run() {
        //地球运转====守护线程
        while (true){
            System.out.println("地球正常运转");
        }
    }
}

class Person implements Runnable{

    @Override
    public void run() {
        //人类可以活100岁
        for (int i = 0; i < 100; i++) {
            System.out.println("生命的第"+(i+1)+"年");
        }
        System.out.println("寿终正寝!");
    }
}

运行结果: 用户线程结束,守护线程仍然在执行的原因时,线程关闭也需要一定的时间。
在这里插入图片描述

线程同步

并发: 多个线程同时操作同一个对象。
线程同步: 线程同步属于一种等待机制,多个需要同时访问同一个对象的线程要进入该对象的线程等待池形成队列,等待前面的线程使用完毕,下一个线程才能继续使用。

synchronized关键字

synchronized通过队列+锁的方式实现线程同步。

  • 为了保证数据再方法中被访问时的正确性,再访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,线程使用对象后会释放锁,让下一个线程继续使用该对象。
  • synchronized只能实现线程同步,不能实现线程通信
synchronized方法
  • synchronized方法控制对象的访问,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
  • 方法中需要修改的内容才需要锁,锁太多会浪费资源
案例:买票问题(synchronized方法)(线程安全)
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket=new BuyTicket();
        new Thread(buyTicket,"小马").start();
        new Thread(buyTicket,"小明").start();
        new Thread(buyTicket,"小红").start();
        new Thread(buyTicket,"黄牛党").start();
    }
}

class BuyTicket implements Runnable{
    //票
    private int ticketNum=10;
    boolean flag=true;//设置外部停止标志

    @Override
    public void run() {
        //买票
        while (flag){
            buy();
            try {
                //模拟购买数据更新---网络延迟
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //添加synchronized关键字,将方法变为同步方法,锁的是this
    public synchronized void buy(){
        if(ticketNum<=0){
            flag=false;
            return;
        }
        //买票
        System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNum--+"张票");
    }
}

运行结果:
在这里插入图片描述

synchronized块
  • 样式:synchronized(obj){}
    • obj称为同步监视器
      • obj可以是任何对象,但推荐使用共享资源作为同步监视器
      • 同步方法中不需要指定同步监视器,同步方法中的同步监视器是this,这个对象本身,或者是class(反射中讲解)
    • 同步监视器执行过程
      • 第一个线程访问,锁定同步监视器,执行第一个线程的代码
      • 第二个线程访问,发现同步监视器锁定,无法访问,挂起线程,并定时访问查看同步监视器是否解锁
      • 第一个线程访问完毕,解锁同步监视器
      • 第二个线程访问,发现同步监视器没有锁,进入访问并锁定同步监视器
案例:银行取钱(synchronized块)
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account=new Account(150,"结婚基金");
        //取钱
        Drawing you=new Drawing(account,50,"you");
        Drawing gf=new Drawing(account,100,"gf");

        you.start();
        gf.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 ownAmount;

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account=account;
        this.drawingMoney=drawingMoney;
    }
    //取钱
    //synchronized默认锁的是this
    @Override
    public void run() {
        //同步块,锁定的对象是变化的量,需要增删改的对象
        synchronized (account){
            //判断账户里够不够取
            if(account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"取钱时账户余额不足!");
                return;
            }
            //模拟延时,这个时候you和gf两个线程都已经开始取钱,且认为账户余额是够的
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //更新账户余额
            account.money-=drawingMoney;
            //更新手里拥有的钱
            ownAmount+=drawingMoney;

            //Thread.currentThread().getName()==this.getName()
            System.out.println(this.getName()+"手里现有金额为:"+ownAmount);
            //打印账户余额
            System.out.println(account.name+"余额为:"+account.money);

        }
    }
}

Lock锁

  • 通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。
  • ReentrantLock(可重入锁)类实现了Lock,拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,ReentrantLock更常用,可以显式加锁,释放锁。

代码样式:
在这里插入图片描述

案例:买票(Lock锁)
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2=new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}

class TestLock2 implements Runnable{
    int ticketNum=10;

    //定义Lock锁
    private final ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                lock.lock();    //加锁
                if (ticketNum>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNum--);
                }else {
                    break;
                }
            }finally {
                lock.unlock();  //解锁
            }

        }
    }
}

synchronized与Lock对比

  • Lock是显式锁,需要所动开启和关闭锁。synchronized是隐式锁,出了作用域自动释放。
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:Lock>同步代码块>同步方法

线程同步存在的问题====>降低性能

  • 一个线程获得对象的排它锁时,其他需要使用该对象的线程都需要挂起等待
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时引起性能问题
  • 优先级低的线程先获得对象的排它锁时,来了一个优先级高的线程也要使用该对象,这时优先级高的线程需要等待优先级低的线程先执行,从而导致优先级倒置,引起性能问题

死锁

死锁: 每个线程需要占有多个资源才能执行,并且各自占有一些共享资源,还需等待其他线程释放其占有的资源才能执行,导致多个线程相互等待对方释放资源,从而都进入阻塞状态,产生死锁问题。
在这里插入图片描述

  • 产生死锁的必要条件
    • 互斥条件:一个资源每次只能被一个进程使用
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放
    • 不剥夺条件:进程已经获得的资源,在未使用完之前不能强行剥夺
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
  • 解决死锁问题,破坏上述产生死锁的条件一个或多个即可

案例:化妆

灰姑凉和白雪公主各自占有化妆所需要的一个资源,导致两个线程都不满足执行的条件,进入死锁状态。

public class DeadLock {
    public static void main(String[] args) {
        Makeup g1=new Makeup(0,"灰姑凉");
        Makeup g2=new Makeup(1,"白雪公主");
        g1.start();
        g2.start();
    }
}
//口红
class Lipstick{

}

//镜子
class Mirror{

}

class Makeup extends Thread{
    //共有资源只有一份,用static来确保
    static Lipstick lipstick=new Lipstick();
    static Mirror mirror=new Mirror();
    int choice;//选择
    String girlName;//女孩名字

    public Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //化妆,互相持有对方需要的资源
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipstick){
                System.out.println(this.girlName+"获得口红,锁定口红");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.girlName+"获得镜子,锁定镜子");
                    System.out.println(this.girlName+"开始化妆");
                }
                System.out.println(this.girlName+"释放镜子");
            }
            System.out.println(this.girlName+"释放口红");
        }else {
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子,锁定镜子");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.girlName+"获得口红,锁定口红");

                    System.out.println(this.girlName+"开始化妆");
                }
                System.out.println(this.girlName+"释放口红");
            }

            System.out.println(this.girlName+"释放镜子");
        }
    }
}

线程通信

线程通信: 都是Object类的方法,且都只能在同步方或同步代码块中使用,否则会抛出异常IILegalMonitorStateException
在这里插入图片描述
接下来,通过生产者消费者问题来了解线程通信。

生产者消费者问题

应用场景:

  • 假设仓库中只能存放N件物品,生产者将生产出来的产品放入仓库,消费者从仓库中取走商品消费。
  • 如果仓库中没有产品,则通知生产者生产产品放入仓库,否则生产者停止生产进入等待,直到仓库中的产品被消费者取走消费。
  • 如果仓库中有产品,消费者可以取走产品进行消费,否则消费者停止消费并等待,直到仓库中有产品可以消费。
管程法:利用缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer container=new SynContainer();
        Productor productor=new Productor(container);
        Consumer consumer=new Consumer(container);

        productor.start();
        consumer.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++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }
    }
}
//消费者
class Consumer extends Thread{
    SynContainer synContainer;
    public Consumer(SynContainer synContainer){
        this.synContainer=synContainer;
    }

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了--》"+synContainer.pop().id+"只鸡");
        }
    }
}
//产品
class Chicken{
    int id;//产品编号

    public Chicken(int id) {
        this.id = id;
    }
}
//缓冲区
class SynContainer{
    //容器大小
    Chicken[] chickens=new Chicken[10];
    //容器计数器
    int count=0;

    //生产者放入产品
    public synchronized void push(Chicken chicken){
        //如果容器满了,需要等待消费者消费
        if(count==chickens.length){
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果容器没有满,生产者继续生产产品
        chickens[count]=chicken;
        count++;
        //通知消费者可以消费了
        this.notifyAll();
    }

    //消费者取出产品
    public synchronized Chicken pop(){
        //判断能否消费产品
        if(count==0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //可以消费
        count--;
        Chicken chicken=chickens[count];
        //消费完后,通知生产者生产
        this.notifyAll();
        return chicken;
    }
}
信号灯法:利用标志位
public class TestPC2 {
    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){
        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){
        this.tv=tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

class TV{
    //录制演员表演,观众等待   T
    //观众观看表演,演员休息   F
    String voice;//表演的节目
    boolean flag=true;
    //表演
    public synchronized void play(String voice){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:"+voice);
        //通知观众观看
        this.notify();
        this.voice=voice;
        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;
    }
}

线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完后放回线程池中。避免频繁创建和销毁线程,实现线程重复利用。

  • JDK5.0提供了线程池相关API:ExecutorService和Executors
    • ExecutorService:线程池接口,常见子类ThreadPoolExecutor
      • 常用方法
        • void execute(Runnable command)执行任务/命令,没有返回值,一般用来执行Runnable
        • Futuresubmit(Callable task)执行任务,有返回值,一般用来执行Callable
        • void shutdown()关闭连接池
    • Executors:工具类,线程池的工厂类,,用于创建并返回不同类型的线程池
  • 好处
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不许需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时醉倒保持多长时间后会终止

案例:

public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool(10)参数为线程池大小
        ExecutorService servic= Executors.newFixedThreadPool(10);
        //执行
        servic.execute(new MyThread());
        servic.execute(new MyThread());
        servic.execute(new MyThread());
        servic.execute(new MyThread());
        //关闭链接
        servic.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
发出的红包

打赏作者

likehack

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

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

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

打赏作者

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

抵扣说明:

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

余额充值