多线程入门

1、程序,进程和线程

  • 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念

  • 进程(Process):是程序的一次执行过程,他是一个动态的概念,是系统资源分配的单位

  • 线程(Thread):是CPU调度和执行的单位

main()称之为主线程,为系统的入口,用于执行整个程序

2、创建线程的三中方式

  • 继承Thread类:重写run()方法,编写线程执行体,创建线程对象,调用start()方法启动线程。但是

  • 实现Runnable接口:重写run()方法,执行线程需要丢入runnable接口实现类,调用start方法。同时这种方法也避免了单继承的局限性,灵活方便,而且方便同一个对象被多个线程使用。

  • 实现Callable接口(了解即可 ):重写call方法,抛出异常,需要返回值类型。使用时需要创建执行服务,然后提交执行,再获取结果,最后需要关闭服务等4个步骤。

3、静态代理(Thread类)

  • 真实对象和代理对象都要实现同一接口,代理对象要代理真实角色,这样就可以使得真实对象专注于一件事情,而代理对象会把其他完善的事情做完

  • 比如,我要结婚,然后找了一家婚庆公司,这样我就成了真实对象,婚庆公司就成了代理对象,而我仅仅需要结婚,其他事情就可以完全交给婚庆公司来完成了。

  • 所以说多线程就使用了静态代理,Thread类就是代理对象,而它需要一个真实对象来作为参数,而且这个真实对象需要实现Runnable接口。

public class StaticProxy {
    public static void main(String[] args) {
        new WeddingCompary(new You()).marry();
    }
}
​
interface Marry{
    public void marry();
}
​
//真实角色
class You implements Marry{
​
    @Override
    public void marry() {
        System.out.println("我要结婚了,很开心!");
    }
}
​
//代理角色
class WeddingCompary implements Marry{
​
    private Marry target;
​
    public WeddingCompary(Marry target) {
        this.target = target;
    }
​
    @Override
    public void marry() {
        before();
        this.target.marry();
        after();
    }
​
    private void after() {
        System.out.println("收拾残局,送客!");
    }
​
    private void before() {
        System.out.println("布置现场,邀请嘉宾!");
    }
​
}

4、Lambda表达式

  • 避免了匿名内部类定义过多,让代码看起来更简洁,去掉了一些没有意义的代码,只留下其核心逻辑,其实质属于函数式编程的概念

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

  • lambda表达式的演化过程:由普通方式->静态内部类->局部内部类->匿名内部类->lambda表达式,如下:

public class LambdaTest {
​
    static class ILike2 implements Like{
        @Override
        public void lambda() {
            System.out.println("lambda表达式2");
        }
    }
​
    public static void main(String[] args) {
        //普通方式调用
        Like like = new ILike();
        like.lambda();
        //静态内部类
        like = new ILike2();
        like.lambda();
        //局部内部类
        class ILike3 implements Like{
            @Override
            public void lambda() {
                System.out.println("lambda表达式3");
            }
        }
        like = new ILike3();
        like.lambda();
        //匿名内部类
        like = new Like() {
            @Override
            public void lambda() {
                System.out.println("lambda表达式4");
            }
        };
        like.lambda();
        //lambda表达式
        like = ()-> System.out.println("lambda表达式5");
        like.lambda();
    }
}
​
//定义一个函数式接口
interface Like{
    public abstract void lambda();
}
//接口的实现类
class ILike implements Like{
​
    @Override
    public void lambda() {
        System.out.println("lambda表达式");
    }
}
  • lambda表达式的简化:(前提必须是函数式接口)

    1. 如果有参数的话,可以省去参数类型,如果省去的话就要都去掉

    2. 如果有参数的话,可以省去包裹参数的括号;如果没有参数或者有多个参数的话不能省去括号

    3. 如果方法体里只有一句话的话可以省去花括号

5 、线程的状态

  • 创建状态:通过new Thread()创建线程对象进入了新生状态,调用start方法进入就绪状态。

  • 阻塞状态:当调用sleep、wait或同步锁时,线程进入阻塞状态,阻塞时间解除后,重新进入就绪状态,等待CPU调度执行

  • 就绪状态:通过CPU调度进入运行状态

  • 运行状态:在这个状态下,才真正执行线程中的代码

  • 死亡状态:线程终端或者结束,进入死亡状态,就不能再次启动了

  • 线程中常用的方法:

    1. setpriority(int newPriority):更改线程的优先级

    2. static void sleep(long millis):让当前正在执行的线程进入休眠

    3. void join():等待该线程终止

    4. static void yield():暂停当前线程对象,并执行其他线程

    5. boolean isAlive():测试线程是否处于活动状态

  • 停止线程

    1. 不推荐使用JDK提供的stop()、destroy()(过时)方法

    2. 推荐线程自己停下来

    3. 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行

  • 线程休眠(sleep方法进入休眠)

    每个对象都有一个锁,sleep不会释放锁

//使用sleep实现一个简单的计时器
public static void main(String[] args) {
    Date date = new Date(System.currentTimeMillis());
    while (true){
        try {
            String format = new SimpleDateFormat("HH:mm:ss").format(date);
            System.out.println(format);
            Thread.sleep(1000);
            date = new Date(System.currentTimeMillis());  //更新当前时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 线程礼让

    礼让线程,让当前正在执行的线程暂停,但不阻塞;将线程从运行状态转为就绪状态

//测试代码
public class TestYield {
    public static void main(String[] args) {
        MyYield yield = new MyYield();
​
        new Thread(yield, "A").start();
        new Thread(yield, "B").start();
    }
}
​
class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "线程执行结束");
    }
}
  • 线程加入join:线程执行join方法后,会优先执行此线程,待此线程执行完毕后才执行其他线程

public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
​
    public static void main(String[] args) {
        TestJoin testJoin = new TestJoin();
        Thread vip = new Thread(testJoin, "vip");
        vip.start();
​
        for (int i = 0; i < 200; i++) {
            System.out.println("main:" + i);
            if(i == 90) {
                try {
                    vip.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 追踪线程的状态

public class TestState {
​
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("================================");
        });
        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);
                state = thread.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 线程优先级(1-10):优先级越高,执行的可能性就越大,但是不一定一定优先执行

    使用getPriority()获取线程优先级,使用setPriority(int)设置线程优先级

public class TestPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
​
        MyPriority priority = new MyPriority();
​
        Thread thread1 = new Thread(priority, "1");
        Thread thread2 = new Thread(priority, "2");
        Thread thread3 = new Thread(priority, "3");
​
        thread1.start();
​
        thread2.setPriority(4);
        thread2.start();
​
        thread3.setPriority(Thread.MAX_PRIORITY);
        thread3.start();
    }
}
​
class MyPriority implements Runnable{
​
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}
  • 守护线程:

    线程分为用户线程和守护线程,默认创建的线程是用户线程,守护线程通过线程的setDaemon(true)方法来设置为守护线程。在jvm虚拟机中,虚拟机是不管守护线程的,只有用户线程存在时程序才会正常的运行下去

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        Me me = new Me();
        //设置该线程为守护线程
        Thread thread = new Thread(god);
        thread.setDaemon(true);
        thread.start();
        //启动用户线程
        new Thread(me).start();
    }
}
​
//守护线程
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("苍天保佑你!");
        }
    }
}
​
//用户线程
class Me implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("活着...");
        }
        System.out.println("死去了");
    }
}

6、线程同步

  • 当多个线程访问同一资源时,可能会引发一些安全问题,比如:火车站卖票问题,银行取钱问题等等,这些都可能伴随着安全问题,因此我们可以通过队列+所机制来解决该问题

//银行取钱可能会引发的线程不安全的代码
public class DrawMoney {
    public static void main(String[] args) {
        Account account = new Account(1000, "130925");
​
        new Draw(account, 500, "我").start();
        new Draw(account, 800, "女朋友").start();
    }
}
​
class Account {
    private int money;
    private String name;
​
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
​
    public int getMoney() {
        return money;
    }
​
    public void setMoney(int money) {
        this.money = money;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
}
​
//模拟在银行取款
class Draw extends Thread{
​
    private Account account;
    private int drawMoney;
​
    public Draw(Account account, int drawMoney, String name){
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }
​
    @Override
    public void run() {
        if((account.getMoney() - this.drawMoney) < 0){
            System.out.println("余额不足," + this.getName() + "取钱失败!");
            return;
        }
        //模拟延时,使得两个线程都能进行到这里
        try {
            this.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        account.setMoney(account.getMoney() - this.drawMoney);
        System.out.println(this.getName() + "取了" + this.drawMoney + "元");
        System.out.println(account.getName() + "账户余额:" + account.getMoney() + "元");
    }
}
  • 通过synchronized方法和synchronized块来解决该问题,但是这种锁机制也是有一定缺陷的,比如会影响性能,但是可以保证安全,正所谓鱼与熊掌不可兼得。

    synchronized默认锁的是this对象,如果使用同步代码块的话,我们可以自定义锁的对象,一般情况下,我们锁的是共享的资源对象,如上案例中,我们就可以锁account对象

//还是用以上代码案例,我们只需要修改run方法中的代码即可,需要添加同步代码块,如下
    @Override
    public void run() {
        synchronized (account){
            if((account.getMoney() - this.drawMoney) < 0){
                System.out.println("余额不足," + this.getName() + "取钱失败!");
                return;
            }
            //模拟延时,使得两个线程都能进行到这里
            try {
                this.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            account.setMoney(account.getMoney() - this.drawMoney);
            System.out.println(this.getName() + "取了" + this.drawMoney + "元");
            System.out.println(account.getName() + "账户余额:" + account.getMoney() + "元");
        }
    }

7、死锁

  • 多个线程互相抱着对方需要的资源,然后形成僵持。

  • 产生死锁的四个必要条件:

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

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

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

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

    以上四个条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

public class DieLockTest {
    public static void main(String[] args) {
        new Thread(new MakeUp(0, "红太狼")).start();
        new Thread(new MakeUp(7, "美羊羊")).start();
    }
}
​
class MakeUp implements Runnable{
    //定义镜子和口红
    private static Mirror mirror = new Mirror();
    private static Lipstick lipstick = new Lipstick();
​
    private int choice;
    private String name;
​
    public MakeUp(int choice, String name){
        this.choice = choice;
        this.name = name;
    }
​
    @Override
    public void run() {
        if(choice == 0){
            synchronized (mirror){
                System.out.println("获得镜子的锁");
                try {
                    Thread.sleep(1000);
                    synchronized (lipstick){
                        System.out.println("获得口红的锁");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } else {
            synchronized (lipstick){
                System.out.println("获得口红的锁");
                try {
                    Thread.sleep(1000);
                    synchronized (mirror){
                        System.out.println("获得镜子的锁");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
​
class Mirror{ }
​
class Lipstick{ }

8、Lock锁

  • 从jdk5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当,它是juc包下的接口类(java.util.concurrent.locks.Lock),它是控制多个线程对共享资源进行访问的工具。ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实习爱你线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

  • synchronized与Lock的对比:(ReentrantLock:可重用锁)

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

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

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

    4. 优先使用顺序:Lock > 同步代码块 > 同步方法

9、生产者消费者问题(线程通信)

  • 这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件。在生产者消费者问题中,仅有synchronized是不够的,它只能保证线程同步,却不能解决线程通信。

  • JAVA提供了几个方法解决线程之间的通信问题

    • wait()方法:object类中的,与sleep不同,会释放锁

    • wait(long timeout):指定等待的毫秒数

    • notify():唤醒一个处于等待状态的线程

    • notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度

  • 解决方法:

    1. 管程法: 生产者将生成好的数据放入缓冲区,消费者从缓冲区拿出数据

    public class TestPC {
        public static void main(String[] args) {
            SynContainer container = new SynContainer();
    ​
            new Productor(container).start();
            new Consumer(container).start();
        }
    }
    //定义生产者
    class Productor extends Thread{
        private SynContainer synContainer;
        public Productor(SynContainer synContainer){
            this.synContainer = synContainer;
        }
        //生产者生产鸡
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                synContainer.push(new Chicken(i));
                System.out.println("生产者生产了第" + i + "只鸡");
            }
        }
    }
    //定义消费者
    class Consumer extends Thread{
        private SynContainer synContainer;
        public Consumer(SynContainer synContainer){
            this.synContainer = synContainer;
        }
        //消费者消费鸡
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
    //            Chicken chicken = synContainer.pop();
                System.out.println("消费者消费了第" + synContainer.pop().getId() + "只鸡");
            }
        }
    }
    //定义产品
    class Chicken{
        private int id;
    ​
        public Chicken(int id) {
            this.id = id;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
    }
    //定义缓冲区
    class SynContainer{
        //需要一个容器大小
        private Chicken[] chickens = new Chicken[10];
        //容器计数器
        private 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;
        }
    }
    1. 信号灯法:

    public class TestPC2 {
        public static void main(String[] args) {
            TV tv = new TV();
    ​
            new Actor(tv).start();
            new Watcher(tv).start();
        }
    }
    //定义生产者==》演员
    class Actor extends Thread{
        private TV tv;
    ​
        public Actor(TV tv){
            this.tv = tv;
        }
    ​
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if(i % 2 == 0){
                    tv.play("哈哈哈" + i, "脱口秀" + i);
                } else {
                    tv.play("不是所有牛奶都叫特仑苏", "广告" + i);
                }
            }
        }
    }
    //定义消费者==》观众
    class Watcher extends Thread{
        private TV tv;
    ​
        public Watcher(TV tv){
            this.tv = tv;
        }
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                tv.watch();
            }
        }
    }
    //定义产品==》节目
    class TV{
        private String voice;
        private String content;
    ​
        //标志位:TRUE:演员表演
        //       FALSE:观众观看
        private boolean flag = true;
    ​
        public String getVoice() {
            return voice;
        }
        public void setVoice(String voice) {
            this.voice = voice;
        }
        public String getContent() {
            return content;
        }
        public void setContent(String content) {
            this.content = content;
        }
    ​
        //演员表演
        public synchronized void play(String voice, String content){
            if(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("演员发声:" + voice + ",表演了:" + content);
            //通知观众观看
            this.notifyAll();
            //更新表演的内容
            this.voice = voice;
            this.content = content;
            //表演完成后设置标志位取反
            this.flag = !this.flag;
        }
        //观众观看表演
        public synchronized void watch(){
            if(this.flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观众观看了节目:" + this.voice + ",看到了:" + this.content);
            //通知演员表演
            this.notifyAll();
            //切换标志位
            this.flag = !this.flag;
        }
    }

10、线程池

  • 优点:

    1. 提高响应速度(大大减少了创建和销毁线程的时间)

    2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    3. 便于线程管理

  • 线程池的使用:jdk5.0起,提供了线程池相关API:ExecutorService和Executors

public class TestPool {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //启动线程
        service.execute(new Thread(new MThread()));
        service.execute(new Thread(new MThread()));
        service.execute(new Thread(new MThread()));
        //关闭链接
        service.shutdown();
    }
}
​
class MThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

该笔记是根据B站狂神老师的课程完成的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值