• Java基础00-多线程28


    1. 实现多线程

    1.1 进程

      

    1.2 线程

      

    1.3 多线程的实现方式(方式一:继承Thread类)

      

    代码示例:
    定义类MyThread:

    //1:定义一个类MyThread继承Thread类
    public class MyThread extends Thread {
        // 2:在MyThread类中重写run()方法
        // 为什么要重写run方法,是因为在类中,并不是所有代码都需要执行多线程的,
        // 所有就有了run方法,需要执行多线程的代码写在run方法中。
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(i);
            }
        }
    }

    定义测试类:

    public class MyThreadDemo {
        public static void main(String[] args) {
            // 3:创建MyThread类的对象
            // 多线程就创建多个
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            // 4:启动线程
    //        my1.run();    //这样调用run方法是错误,并没有启动线程。
    //        my2.run();
    
            // 正确方式
            // void start ()导致此线程开始执行; Java虛拟机调用此线程的run方法
            my1.start();
            my2.start();
        }
    }

    在前面了解到多线程,这个程序也是多线程,那么在运行程序的时候,应该是两个一起执行,而不是执行完一个再执行另一个。
    运行结果:

    1.4 设置和获取线程名称

      
    代码示例:

    String getName():返回此线程的名称
    定义线程类:

    public class MyThread extends Thread {
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                //String getName():返回此线程的名称
                System.out.println(getName() + ";" + i);
            }
        }
    }

    测试类:

    public class MyThreadDemo {
        public static void main(String[] args) {
            
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
            
            my1.start();
            my2.start();
        }
    }

    运行结果:
    在线程类中,并没有设置线程名,而控制器显示输出了Thread-0和Thread-1。
    说明在Thread类中有默认的线程名,而且第一个线程类是0,后面就会加一。

    void setName(String name):将此线程的名称更改为等于参数name
    线程类不变

    public class MyThreadDemo {
        public static void main(String[] args) {
    
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
            
            //void setName(String name):将此线程的名称更改为等于参数name
            my1.setName("飞机");
            my2.setName("坦克");
    
            my1.start();
            my2.start();
        }
    }

    运行结果:
    这次的输出结果就是我们设置的值了。

    除了使用set方法设置线程名称,使用构造方法也是可以的
    使用构造方法设置线程名称,必须在线程类中创建有参构造方法,然后将参数传到父类的(Thread)的有参构造方法。

    线程类:

    public class MyThread extends Thread {
    
        public MyThread() {
            super();
        }
        
        public MyThread(String name) {
            //调用Thread类的构造方法,将参数传递过去。
            super(name);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ";" + i);
            }
        }
    }

    测试类:

    public class MyThreadDemo {
        public static void main(String[] args) {
    
            MyThread my1 = new MyThread("飞机");
            MyThread my2 = new MyThread("坦克");
    
            my1.start();
            my2.start();
        }
    }

    运行结果:

    那么如果获取main方法所在线程的名称那?
    使用public static Thread currentThread():返回对当前正在执行的线程对象的引用

    public class MyThreadDemo {
        public static void main(String[] args) {
            //public static Thread currentThread0:返回对当前正在执行的线程对象的引用
            System.out.println(Thread.currentThread().getName());
            
        }
    }

    运行结果:
    由此可见,main方法的线程名是main。

    1.5 线程调度

      

    MIN_PRIORITY:最小线程优先级,1
    MAX_PRIORITY:最大线程优先级:10
    NORM_PRIORITY:默认线程优先级:5

    1.6 线程控制

    void join(); 等待这个线程死亡,调用它的线程终止,则主线程一起死亡

       

    a.start();
    
    a.join();
    
    b.start();

    执行过程:主线程开始->线程a开始->线程a结束->主线程结束->线程b开始->线程b结束,因为join,b会等a先跑完,join完了会带走主线程。

    a.start();
    b.start();
    a.join();

    执行过程:主线程开始->a、b随机谁先开始->a、b随机谁先死->若a先死,则主线程终止,b继续跑,跑完死或若b先死,则a继续跑,跑完a死,主线程死

    代码示例:

    static void sleep(long millis):使当前正在执行的线程停留 (暂停执行)指定的毫秒数

    设置了sleep,在线程的执行过程中就会等待设置的时间过后,再执行。

    创建线程类:

    public class Thread1 extends Thread {
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ";" + i);
                try {
                      //static void sleep(long millis):使当前正在执行的线程停留 (暂停执行)指定的毫秒数
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    创建测试类:

    public class File1Demo {
        public static void main(String[] args) {
    
            Thread1 t1 = new Thread1();
            Thread1 t2 = new Thread1();
            Thread1 t3 = new Thread1();
            
            t1.setName("曹操");
            t2.setName("刘备");
            t3.setName("孙权");
            
            t1.start();
            t2.start();
            t3.start();
    
        }
    }

    运行结果:

    这里就不放图了,线程输出应该是每停留1000毫秒(一秒)再输出一次。

    void join():等待这个线程死亡

    设置了join()方法,其他的线程都会等待这个线程死亡,才会执行。

    创建线程类:

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

    创建测试类:

    public class File1Demo {
        public static void main(String[] args) {
    
            Thread1 t1 = new Thread1();
            Thread1 t2 = new Thread1();
            Thread1 t3 = new Thread1();
    
            t1.setName("康熙");
            t2.setName("四阿哥");
            t3.setName("八阿哥");
    
            t1.start();
            try {
                // void join():等待这个线程死亡
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
            t3.start();
    
        }
    }

    运行结果:

    这次加入了join()方法后,明显是t1(康熙)执行的最快,因为加入了这个方法后,其他的线程都要等他执行完(死亡)后才能执行。

     

    void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

    设置了守护线程后,主线程死亡一段时间,守护线程也要死亡。

    创建线程类:

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

    创建测试类:

    public class File1Demo {
        public static void main(String[] args) {
    
            Thread1 t1 = new Thread1();
            Thread1 t2 = new Thread1();
    
            t1.setName("张飞");
            t2.setName("关羽");
    
            // 设置主线程(main)为刘备
            Thread.currentThread().setName("刘备");
    
            // 设置守护线程
            // void setDaemon (boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虛 拟机将退出
            t1.setDaemon(true);
            t2.setDaemon(true);
    
            t1.start();
            t2.start();
    
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ";" + i);
            }
    
        }
    }

    运行结果:

    在运行结果中,主线程刘备在运行第10次后死亡了,而守护他的线程也在执行一段后死亡了,并没有执行完毕。

    主线程死亡后其他守护线程并不会立即死亡,而是一段时间后。

     

    1.7 线程生命周期

    正常的线程生命周期:

    还有一种情况就是在运行的时候其他线程抢走了CPU的执行权的话,它就会回到就绪状态,然后再抢CPU的执行权,如果抢到了就继续执行。

     

    还有一种情况就是在运行的时候执行了sleep()方法,执行后线程就会进入到阻塞状态,等到了设定的时间之后,它就会回到就绪状态,继续和其他的线程抢CPU的执行权。

    1.8 多线程的实现方式(方式二:实现Runnable接口) 

    代码示例:

    创建MyRunnable类

    //1:定义一个类MyRunnable实现Runnable接口
    public class MyRunnable implements Runnable {
        
        // 2:在MyRunnable类中重写run()方法
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                // 这里并不能使用getName方法,因为实现的是Runnable接口,和Thread并没有关系
                // 使用想要使用必须先得到当前正在执行的线程,然后再调用getName方法
                System.out.println(Thread.currentThread().getName() + ";" + i);
            }
        }
    
    }

    创建测试类:

    public class MyRunnableDemo {
    
        public static void main(String[] args) {
    
            // 3:创建MyRunnable类的对象
            MyRunnable myRunnable = new MyRunnable();
    
            // 4:创建Thread类的对象,把MyRunnable对象作为构造方法的参数
            //Thread (Runnable target, String name),添加Runnable和它的线程名
            Thread t1 = new Thread(myRunnable, "飞机");
            Thread t2 = new Thread(myRunnable, "坦克");
    
            // 5:启动线程
            t1.start();
            t2.start();
    
        }
    
    }

    运行结果:
    和继承Thread类的效果是一样的,但是比继承Thread更加的灵活。

    2. 线程同步

    案例:卖票

      
    代码示例:

    创建SellTicket类:

    //1:定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
    public class SellTicket implements Runnable {
        
        //表示有一百张票
        private int tickets = 100;
    
        // 2:在SellTicket类中重写run()方法实现卖票,代码步骤如下
        @Override
        public void run() {
    
            // c:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
            while (true) {
                // A:判断票数大于0,就卖票,并告知是哪个窗口卖的
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    // B:卖了票之后,总票数要减1
                    tickets--;
                }
            }
        }
    
    }

    创建SellTickDemo类:

    //3:定义一一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    public class SellTickDemo {
        public static void main(String[] args) {
            // A:创建SellTicket类的对象
            SellTicket sellTicket = new SellTicket();
    
            // B:创建三个Thread类的对象,把sellTicket对象作为构造方法的参数,并给出对应的窗口名称
            Thread t1 = new Thread(sellTicket, "窗口1");
            Thread t2 = new Thread(sellTicket, "窗口2");
            Thread t3 = new Thread(sellTicket, "窗口3");
    
            // C:启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    
    }

    运行结果:

    2.1 卖票案例的思考

    代码示例:

    SellTick类:

    public class SellTicket implements Runnable {
    
        private int tickets = 100;
    
        @Override
        public void run() {
    
            while (true) {
    
                if (tickets > 0) {
                    try {
                        // 通过sleep方法来模拟出票时间
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
    }

    SellTickDemo类:

    public class SellTickDemo {
        public static void main(String[] args) {
    
            SellTicket sellTicket = new SellTicket();
    
            Thread t1 = new Thread(sellTicket, "窗口1");
            Thread t2 = new Thread(sellTicket, "窗口2");
            Thread t3 = new Thread(sellTicket, "窗口3");
    
            t1.start();
            t2.start();
            t3.start();
    
        }
    }

    运行结果:


    出现了相同的票数,而且从第100张票直接就到了第97张票,还有出现了负数的情况。
    这是为什么那?
    解析:

    // 出现负数的票
        while(true)
        {
            // tickets=1
            // t1, t2, t3
            // 假设t1线程抢到CPU的执行权
            if (tickets > 0) {
                try {
                    // 通过sleep方法来模拟出票时间
                    Thread.sleep(100);
                    // t1线程休息100毫秒
                    // t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程 休息100毫秒
                    // t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 假设线程按照顺序醒过来
                // t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
                // 假设t1继续拥有CPU的执行权,就会执行tickets--;操作, tickets = 0;
                // t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第o张票
                // 假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -1;
                // t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
                // 假设t2继续拥有CPU的执行权,就会执行tickets--;操作, tickets = -2;[、
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }

    2.2 卖票案例数据安全问题的解决

      

    2.3 同步代码块

      
    加上锁之后,线程进入synchronized内部之后就上了锁,其他的线程就进不去了,即使是内部的线程休眠了,内部只能有一个线程。
    代码示例:

    线程类:

    public class SellTicket implements Runnable {
    
        private int tickets = 100;
        
        private Object obj = new Object();
    
        @Override
        public void run() {
            while (true) {
                //上锁,锁必须是同一把
                synchronized (obj) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
    
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                }
            }
    
        }
    }

    测试类:

    public class SellTickDemo {
        public static void main(String[] args) {
    
            SellTicket sellTicket = new SellTicket();
    
            Thread t1 = new Thread(sellTicket, "窗口1");
            Thread t2 = new Thread(sellTicket, "窗口2");
            Thread t3 = new Thread(sellTicket, "窗口3");
    
            t1.start();
            t2.start();
            t3.start();
    
        } 
    }

    运行结果:
    这次就正常输出了,没有重复的票,也没有负数的票。

    2.4 同步方法

       

    代码示例:

    同步方法:

    public class SellTicket implements Runnable {
    
        private int tickets = 100;
        private int x = 0;
    
        @Override
        public void run() {
            while (true) {
                if (x % 2 == 0) {
                    //锁对象是this
                    synchronized (this) {
                        if (tickets > 0) {
                            try {
                                Thread.sleep(100);
    
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                            tickets--;
                        }
                    }
                } else {
                    sellTicket();
                }
                x++;
            }
        }
        
        //同步方法
        private synchronized void sellTicket() {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }

    同步静态方法:

    public class SellTicket implements Runnable {
    
        private static int tickets = 100;
        private int x = 0;
    
        @Override
        public void run() {
            while (true) {
                if (x % 2 == 0) {
                    //锁对象是,类名.class,这里用到了反射
                    synchronized (SellTicket.class) {
                        if (tickets > 0) {
                            try {
                                Thread.sleep(100);
    
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                            tickets--;
                        }
                    }
                } else {
                    sellTicket();
                }
                x++;
            }
        }
    
        // 同步静态方法
        private static synchronized void sellTicket() {
            //将tickets 也变成静态
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }

    2.5 线程安全的类

    synchronized() 关键字的类,就是线程安全的类

      

      

    在Collections(工具类)中有一个方法可以返回指定类型的线程安全

      

    2.6 Lock锁

      

    代码示例:

    创建线程类:

    public class SellTicket implements Runnable {
    
        private int tickets = 100;
        // 创建一个Lock的实例,因为Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化、
        // ReentrantLock (): 创建一个ReentrantL ock的实例
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
    
            while (true) {
                // 加入try...finally的原因是,如果在运行中发生了错误,释放锁的操作照样会执行。
                try {
                    // 获得锁
                    lock.lock();
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        }
    }

    创建测试类:

    public class SellTickDemo {
        public static void main(String[] args) {
    
            SellTicket sellTicket = new SellTicket();
    
            Thread t1 = new Thread(sellTicket, "窗口1");
            Thread t2 = new Thread(sellTicket, "窗口2");
            Thread t3 = new Thread(sellTicket, "窗口3");
    
            t1.start();
            t2.start();
            t3.start();
    
        } 
    }

    运行结果:

    没有问题

    3. 生产者消费者

    3.1 生产者消费者模式概述

    生产者消费者模式详解

      

    等待:生产者生产好了数据,消费者没有去消费,这个时候生产者就会提醒消费者去消费。
    唤醒:消费者去消费的时候,发现没有数据,这个时候消费者就去提醒生产者生产数据。

      

    3.2 生产者消费者案例

      
    代码示例:

    创建奶箱类:

    //1:奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
    public class Box {
    
        // 奶的数量
        private int milk;
        // 定义一个成员变量,表示奶箱的状态
        private boolean state = false;
    
        // 存储牛奶,加入synchronized 防止线程安全问题
        public synchronized void put(int milk) {
            // 如果有奶等待消费
            if (state) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 如果没有奶就生产奶
            this.milk = milk;
            System.out.println("送奶工将第" + milk + "瓶奶放入奶箱");
    
            // 生产完毕之后,修改奶箱状态
            state = true;
    
            // 唤醒其他等待的线程
            notifyAll();
    
        }
    
        // 获取牛奶,加入synchronized 防止线程安全问题
        public synchronized void get() {
            // 如果没有牛奶等待生产
            if (!state) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 如果有牛奶就消费牛奶
            System.out.println("用户拿到第" + milk + "瓶奶");
    
            // 消费完毕之后,修改奶箱状态
            state = false;
    
            // 唤醒其他等待的线程
            notifyAll();
        }
    }

    创建生产者类:

    //2:生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
    public class Producer implements Runnable {
    
        private Box b;
    
        public Producer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                b.put(i);
            }
        }
    }

    创建消费者类:

    //3:消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
    public class Customer implements Runnable {
    
        private Box b;
    
        public Customer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            while (true) {
                b.get();
            }
        }
    }

    创建测试类:

    //4:测试类(BoxDemo):里面有main方法,main方法 中的代码步骤如下
    public class BoxDemo {
        public static void main(String[] args) {
            // A:创建奶箱对象,这是共享数据区域
            Box b = new Box();
    
            // B:创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
            Producer p = new Producer(b);
    
            // C:创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
            Customer c = new Customer(b);
    
            // D:创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
            Thread t1 = new Thread(p);
            Thread t2 = new Thread(c);
    
            // E:启动线程
            t1.start();
            t2.start();
    
        }
    }

    运行结果:

  • 相关阅读:
    Bresenham画线算法
    DDA算法
    GL_LINES & GL_LINE_STRIP & GL_LINE_LOOP
    贝塞尔曲线
    弱引用
    Lambert模型
    ShadowVolume
    Phong Shading
    求反射向量
    Vertex Modifier of Surface Shader
  • 原文地址:https://www.cnblogs.com/ajing2018/p/14682905.html
Copyright © 2020-2023  润新知