Java 多线程学习一(入门)—— 久违的 Java 多线程终于肝出来了!

1. 基本概念

  • 程序
    程序 是指令和数据组成的有序集合
  • 进程
    执行一次程序的过程
    进程是系统资源分配的单元
  • 线程
    一个进程包括一个或多个线程
    线程是 CPU 调度与执行的单位
    线程是独立执行的

注意:

多线程是指多个 CPU(即多核) 同时进行不同的任务。

一个 CPU 在同一个节点只能执行一个线程

2. 创建进程的方式

  1. 继承 Thread class
  2. 实现 Runnable interface
  3. 实现 Callable interface

Thread class 实现了 Runnable interface

JVM 允许应用程序同时执行多个执行线程

2.1 线程的优先级

每个线程都有一个优先级

较高优先级的线程优先于优先级较低的线程执行

新线程的优先级最初设置为创建线程的优先级

当在某个线程中创建一个新的线程时,并且当且仅当创建线程是守护进程时新线程才是守护线程。

2.2 main 线程停止

JVM 启动时,通常有一个非守护线程(通常调用某些指定类的名为 main 的方法)。 JVM 将继续执行线程,直到发生以下任一情况:

  • 已经调用了 Runtime 类的 exit 方法,并且安全管理器已经允许进行退出操作
  • 所有非守护进程线程的全部死亡

2.3 创建新线程

2.3.1 Thread

  1. 继承 Thread
  2. 重写 run()
  3. main() 方法 new 这个类并调用 start()
 /**
     * description: 线程学习
     * @author: Leet
     * @date: 2020-11-10 23:25
     **/
    public class TestThread1 extends Thread{
    
    
        
        @Override
        public void run() {
    
    
            for (int i = 0; i < 2000; i++) {
    
    
                System.out.println(String.format("%s%d", "我在看代码---", i));
            }
        }
    
        public static void main(String[] args) {
    
    
    
            new TestThread1().start();
    
            for (int i = 0; i < 2000; i++) {
    
    
                System.out.println(String.format("%s%d", "我在学习多线程---", i));
            }
        }
    }

2.3.2 Runnable

  1. 实现 Runnable
  2. 重写 run()
  3. main()new 一个这个类作为参数传给 Thread 对象,调用 start()
 /**
     * description: 多线程学习2
     *
     * @author: Leet
     * @date: 2020-11-10 23:39
     **/
    public class TestThread2 implements Runnable {
    
    
        @Override
        public void run() {
    
    
            for (int i = 0; i < 2000; i++) {
    
    
                System.out.println(String.format("%s%d", "我是天才-----", i));
            }
        }
    
        public static void main(String[] args) {
    
    
            new Thread(new TestThread2()).start();
    
            for (int i = 0; i < 2000; i++) {
    
    
                System.out.println(String.format("%s%d", "不,我才是天才-----", i));
            }
        }
    }

2.3.3 Callable

  1. 实现 Callable interface
  2. 重写 call() 方法,需抛出异常
  3. 创建目标对象 XXX
  4. 创建执行服务:ExecutorService es = Executors.newFixedThreadPool(1);
  5. 提交执行:Future<Boolean> result = es.submit(XXX);
  6. 获取结果:boolean r = result.get();
  7. 关闭服务:es.shutdown();

3. 静态代理模式

真实对象与代理对象都实现同一个接口

代理对象代理真实角色

package proxy;

/**
 * description:静态代理学习
 *              通过“结婚”这个场景带入
 * @author Leet
 * @date 2020/11/11 9:31
 */
public interface Marry {
    
    
    void marry();
}

class You implements Marry {
    
    
    @Override
    public void marry() {
    
    
        System.out.println("今天我要结婚了,我很高兴");
    }
}

class WeddingCompany implements Marry {
    
    

    public WeddingCompany(Marry marrier) {
    
    
        this.marrier = marrier;
    }

    private Marry marrier;

    @Override
    public void marry() {
    
    
        before();
        marrier.marry();
        after();
    }

    private void before() {
    
    
        System.out.println("结婚前,婚庆公司布置现场");
    }

    private void after() {
    
    
        System.out.println("结婚后,婚庆公司打扫现场");
    }
}

class TestStaticProxy {
    
    
    public static void main(String[] args) {
    
    
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.marry();
    }
}

3.1 Thread class 中的代理

Thread 类 代理 Runnable interface 的实现类

4. lambda

  • 函数式接口
    该接口只有一个方法

在使用该接口实现类的时候,就可以使用 lambda

public class Main {
    
    
	public static void main(String[] args) {
    
    
		new Thread(() -> System.out.println("lambda 创建线程")).start();
	}
}

lambda 表达式在容器的流式编程中用着很舒服

5. 线程状态

在这里插入图片描述
在这里插入图片描述

5.1 线程方法

setPriority(int newPriority) 更改线程优先级
static void sleep(long milis) 让该线程休眠 milis ms
void join() 等待该线程终止
static void yield() 暂停该线程,执行其他线程(让贤)
isAlive() 该线程是否存活

5.1.1 线程停止

  • 利用有限的次数让线程停止
  • 使用一个 flag (终止循环)让线程停止
  • 不要使用 stop()destory() (被舍弃)

5.1.2 线程休眠

sleep()

需要捕获异常:InterruptedException

sleep 不会释放锁

达到失眠时间后,该线程进入就绪状态

5.1.3 线程礼让

yield()

线程礼让:让当前线程停止,但不阻塞,变为就绪状态

礼让不一定成功

5.1.4 join()

合并线程,待此线程执行完,再执行其他线程(其他线程此时阻塞)

 /**
     * description: join() 方法测试
     * @author: Leet
     * @date: 2020-11-11 11:06
     **/
    public class TestJoin implements Runnable {
    
    
        @Override
        public void run() {
    
    
            for (int i = 0; i < 1000; i++) {
    
    
                System.out.printf("%s%d\n", "线程 VIP 来了 ---》 ", i);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
    
    
            Thread thread = new Thread(new TestJoin());
            thread.start();
    
            for (int i = 0; i < 500; i++) {
    
    
                if (i == 200) {
    
    
                    thread.join();
                }
    
                System.out.printf("%s%d\n", "线程 main ---》 ", i);
            }
            // main :VIP 线程属实过分了,它自己执行那么多次,不老实排队,竟然利用自己的特权插队,这是可恶。
        }
    }

5.2 观测线程状态:Thread.State

5.3 线程优先级

Java 为每个线程提供了优先级

线程调度器按照优先级决定调度哪个线程

  • 优先级只是意味着被调度概率的高低
  • 线程优先级:1~10
  • 获取线程优先级
    getPriority()
  • 设置线程优先级
    setPriority(int priority)

建议:优先级设定在 start()

   /**
     * description: 线程优先级测试
     *
     * @author: Leet
     * @date: 2020-11-11 11:26
     **/
    public class TestPriority implements Runnable {
    
    
        @Override
        public void run() {
    
    
            System.out.printf("%S 的 priority : %d\n", Thread.currentThread().getName(), Thread.currentThread().getPriority());
        }
    
        public static void main(String[] args) {
    
    
            System.out.printf("%S 的 priority : %d\n", Thread.currentThread().getName(), Thread.currentThread().getPriority());
    
            Thread thread1 = new Thread(new TestPriority(), "thread1");
            Thread thread2 = new Thread(new TestPriority(), "thread2");
            Thread thread3 = new Thread(new TestPriority(), "thread3");
            Thread thread4 = new Thread(new TestPriority(), "thread4");
            Thread thread5 = new Thread(new TestPriority(), "thread5");
    
            thread1.setPriority(1);
            thread2.setPriority(5);
            thread2.setPriority(9);
            thread4.setPriority(2);
            thread5.setPriority(10);
    
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
        }
    }

5.4 守护线程

  • setDaemon(true) 将该线程设为守护线程
  • 用户线程结束,不管守护线程如何,JVM(程序)停止
    /**
     * description: 守护线程
     * @author: Leet
     * @date: 2020-11-11 14:15
     **/
    public class TestDaemon {
    
    
        public static void main(String[] args) {
    
    
            Thread thread = new Thread(new God());
            thread.setDaemon(true);
            thread.start();
    
            new Thread(new You()).start();
        }
    }
    
    class God implements Runnable {
    
    
        @Override
        public void run() {
    
    
            while (true) {
    
    
                System.out.println("GOD BLESS YOU");
            }
            // 你到天堂,见到 GOD
            // GOD: 我还没反应过来,你咋就死了????!!!!!
        }
    }
    
    class You implements Runnable {
    
    
        @Override
        public void run() {
    
    
            for (int i = 0; i < 36500; i++) {
    
    
                System.out.println("You are happy every day");
            }
            System.out.println("----------Goodbye! The world----------");
        }
    }

6. 线程同步

6.1 队列与锁

  • 对于统一资源访问人数较多时,可以采用队列
  • 为解决访问冲突,在访问时,加入锁机制 synchronized
    一个线程获得对象的排他锁。该线程独占资源,其他线程必须等待至该线程释放锁。
    • 存在的问题
      1. 未拿到锁的线程被挂起
      2. 性能损失:
        • 调度
        • 优先级

6.2 不安全的例子

  1. 买票
  2. 取钱
  3. 容器

6.3 同步方法

通过 private 保证数据只能被对象访问,针对方法提出 synchronized 机制(作用在方法或块上)保证同步。

对于普通同步方法,锁是当前实例对象,如果有多个实例,那么锁对象必然不同,无法完成同步

对于静态同步方法,锁是当前类的 Class 对象。有多个实例,但是锁对象是相同的,可以完成同步

对于同步方法块,锁是 synchronized 括号中的配置对象。

6.4 死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,导致两个或多个线程都在等待对方释放资源,都停止的情形

一个同步块同时拥有 两个以上对象的锁 时,就可能发生“死锁”问题

package synchronizedtest.deadsynchronized;
    
    /**
     * description: 验证死锁
     *    死锁:多个线程互相持有各自需要的资源,而僵持
     *    synchronized 块同时持有两个以上对象的锁,就可能造成死锁
     *
     * @author: Leet
     * @date: 2020-11-11 15:25
     **/
    
    public class DeadLock {
    
    
        public static void main(String[] args) {
    
    
            // 灰姑娘想化妆,她拿起了口红
            new Thread(new Makeup(0, "灰姑娘")).start();
            // 白雪公主想化妆,她拿起了镜子
            new Thread(new Makeup(1, "白雪公主")).start();
        }
    }
    
    /**
     * 口红
     */
    class Lipstick {
    
    }
    
    /**
     * 镜子
     */
    class Mirror {
    
    }
    
    /**
     * 化妆
     */
    class Makeup implements Runnable {
    
    
    
        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();
            }
        }
    
        /**
         * lipstick 与 mirror 是 Makeup 类的静态属性,为 Makeup 类对象持有;
         * lipstick 与 mirror只有一个,
         * 所以说,下方同步块中的代码,锁共享资源的方法正确
         */
        private void makeup() throws InterruptedException {
    
    
            if (choice == 0) {
    
    
                synchronized (lipstick) {
    
    
                    System.out.printf("%s 获得口红\n", girlName);
                    Thread.sleep(1000);
                }
                synchronized (mirror) {
    
    
                    System.out.printf("%s 获得镜子\n", girlName);
                }
            } else {
    
    
                synchronized (mirror) {
    
    
                    System.out.printf("%s 获得镜子\n", girlName);
                    Thread.sleep(2000);
                }
                synchronized (lipstick) {
    
    
                    System.out.printf("%s 获得口红\n", girlName);
                }
            }
        }
    }

7. 线程协作

之前学的都是每个线程各自进行自己的工作,彼此间没有关系、互不干扰。

现在要让线程配合起来工作!

7.1 生产者与消费者模式

生产者与消费者的问题

假设仓库中只能存放一件产品,生产者将生产的东西放入仓库,消费者将仓库中的东西取走消费

如果仓库中没有产品,那么生产者将产品放入仓库,否则停止生产并等待,直至仓库中的东西被消费者取走

如果仓库中放有产品,那么消费者将产品取走消费,否则停止消费并等待,直至仓库中再次放有产品

这个是一个线程同步的问题,二者共享同一个资源,彼此相互依赖、互为条件.

显而易见,仅使用 synchronized 是不够的:synchronized 不能实现不同线程间消息的传递。

7.2 线程通信

wait() 让线程一直等待,直至其他线程通知,期间不持有(释放)锁
wait(long timeout) 让线程一直等待指定的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个类上所有调用 wait() 方法的线程,优先级高的线程优先调度

注意:他们都是 Object 类 中的方法,必须在同步块中调用,否则抛出 IllegalMonitorStateException

7.3 解决方法

7.3.1 管程法

设置一个具有一定容量的缓冲区,生产者生产的东西放入缓冲区,消费者从缓冲区取东西消费

根据缓冲区容量的状态判断唤醒哪个线程、让哪个线程等待

    package thread.synchronize;
    
    /**
     * description: 生产者与消费者问题:管程法解决
     *
     * @author: Leet
     * @date: 2020-11-12 19:58
     **/
    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 {
    
    
        SynContainer container;
    
        public Productor(SynContainer container) {
    
    
            this.container = container;
        }
    
        @Override
        public void run() {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                try {
    
    
                    container.push(new Chicken(i));
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.printf("生产了 %d 只鸡\n", i);
            }
        }
    }
    
    class Consumer extends Thread {
    
    
        SynContainer container;
    
        public Consumer(SynContainer container) {
    
    
            this.container = container;
        }
    
        @Override
        public void run() {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                try {
    
    
                    System.out.printf("消费了---> %d 只鸡\n", container.pop().id);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
    
    class Chicken {
    
    
        int id;
    
        public Chicken(int id) {
    
    
            this.id = id;
        }
    }
    
    class SynContainer {
    
    
        Chicken[] chickens = new Chicken[10];
        int count = 0;
    
        /**
         * 生产者放入产品
         * @param chicken 产品
         * @throws InterruptedException InterruptedException
         */
        public synchronized void push(Chicken chicken) throws InterruptedException {
    
    
            // 如果容器满了,需要等待消费者消费
            if (count == chickens.length) {
    
    
                wait();
            } else {
    
    
                // 容器没满,生产者直接放入产品
                chickens[count++] = chicken;
    
                // 通知消费者消费
                notifyAll();
            }
        }
    
        /**
         * 消费者消费
         * @return Chicken Chicken
         * @throws InterruptedException InterruptedException 
         */
        public synchronized Chicken pop() throws InterruptedException {
    
    
            // 如果容器空了,消费者等待(等待生产者向容器中放入产品)
            if (count == 0) {
    
    
                this.wait();
            } {
    
    
                // 容器中有产品,消费者直接消费
                Chicken chicken = chickens[--count];
                
                // 告知生产者生产东西
                notifyAll();
    
                return chicken;
            }
        }
    }

7.3.2 信号灯法

设置一个标识符,根据标识符的状态判断唤醒哪个线程、让哪个线程等待

    package thread.synchronize;
    
    /**
     * description: 生产者与消费者问题:信号灯法解决
     *
     * @author: Leet
     * @date: 2020-11-12 20:32
     **/
    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("Hello World 播放中");
                } else {
    
    
                    this.tv.play("抖音:记录记录美好生活");
                }
            }
        }
    }
    
    class Watcher extends Thread {
    
    
        TV tv;
    
        public Watcher(TV tv) {
    
    
            this.tv = tv;
        }
    
        @Override
        public void run() {
    
    
            tv.watch();
        }
    }
    
    class TV {
    
    
        String voice;
    
        /**
         * true: 演员表演,观众等待
         * false: 观众观看,演员等待
         */
        boolean flag = true;
    
        public synchronized void play(String voice) {
    
    
            if (!flag) {
    
    
                try {
    
    
                    wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
    
            System.out.printf("演员表演了 %s\n", voice);
    
            notifyAll();
    
            this.voice = voice;
            this.flag = !this.flag;
        }
    
        public synchronized void watch() {
    
    
            if (flag) {
    
    
                try {
    
    
                    wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
    
            System.out.printf("观众观看了 %s\n", voice);
    
            notifyAll();
    
            this.flag = !this.flag;
        }
    }

这个例子有点缺陷:会死锁。
那请聪明的你改一改吧

8. 线程池

  • ExecutorService
    线程池接口
    • void execute(Runnable command)
      执行 Runnable 接口 的实现类
    • <T> Future <T> submit(Callable<T> task)
      执行 Callable 接口 的实现类
    • void shutdown()
      关闭线程池
  • Executor
    工具类、线程池的工厂类
    用于创建并返回不同类型的线程池
   import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * description: 线程池
     *
     * @author: Leet
     * @date: 2020-11-12 20:57
     **/
    public class TestThreadPool implements Runnable{
    
    
        @Override
        public void run() {
    
    
            System.out.println(Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
    
    
            ExecutorService es = Executors.newFixedThreadPool(10);
    
            es.execute(new TestThreadPool());
            es.execute(new TestThreadPool());
            es.execute(new TestThreadPool());
            es.execute(new TestThreadPool());
    
            es.shutdown();
        }
    }



猜你喜欢

转载自blog.csdn.net/Snakehj/article/details/109657370
今日推荐