• Java基础之多线程


    Java基础之多线程

    记一次失败的学习方式

    线程的三中创建方式,先上代码:

    /*
        创建新线程的三种方式:
      1、继承Thread类;
      2、实现Runable接口;
      3、实现Callable接口;
    
    需求:创建多线程对象,开启多线程。在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。
     */
    public class Test9 {
        public static void main(String[] args) {
    /*
            线程启动必须调用start()方法,而非run()
            //方式一
            ThreadTest1 tt1 = new ThreadTest1();
            tt1.setPriority(10);//设置线程优先级,默认为5,范围1-10
            tt1.start();
    */
    /*
            //方式二
            ThreadTest2 tt2 = new ThreadTest2();
            Thread theTt2Thread = new Thread(tt2);
            theTt2Thread.start();
    */
            //方式三
            Thread tt3 = new Thread(){
                @Override
                public void run(){
                    //使用匿名类可以很方便的访问外部变量(这里并未访问外部局部变量)
                    //但是在JDK7以前,就必须使用final修饰
                    for (int i = 0; i < 100; i++) {
                        if (i % 2 == 0) System.out.println("子线程:" + i);
                    }
                }
            };
            //tt3.setPriority(10);//线程优先级太高导致子线程打印完主线程才打印┓( ´∀` )┏233333(主线程默认5)
            tt3.start();
            //同时,该匿名类可以使用lambda表达式简化
            Thread tt4 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) System.out.println("子线程:" + i);
                }
            });
    
            //主线程打印奇数
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 1) System.out.println("主线程:" + i);
            }
        }
    }
    
    //通过继承Thread类来创建新线程
    class ThreadTest1 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) System.out.println("子线程:" + i);
            }
        }
    }
    
    //通过实现Runnable接口来创建新线程
    class ThreadTest2 implements Runnable {
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) System.out.println("子线程:" + i);
            }
        }
    }
    

    最开始执行该代码时,会产生这样的结果:

    子线程居然在主线程打印完了才开始打印,这曾一度让我以为没有创建出新线程,刚开始的时候start()方法在主方法下,我以为是顺序的原因,所以我先调用子线程的start()方法,然后在主方法打印(也就是现在的代码),发现结果还是这样,我甚至以为是因为ThreadTest方法不是公共的原因,然后想到之前看C#的时候有讲到优先级问题,然后百度了一下发现了具体原因:

    结果居然变了

    子线程居然又开始工作了,我花了一晚上查线程优先级问题,最后发现最开始的情况居然复现不了了???(绝对不是主线程for循环在上面的原因)不行我得把我查到的结论整理一下o(╥﹏╥)o,等以后再遇到这种情况再说吧

    线程优先级

    1.在任意时刻,当有多个线程处于可运行状态时,运行系统总是挑选一个优先级最高的线程执行,只有当线程停止、退出或者由于某些原因不执行的时候,低优先级的线程才可能被执行
    2.两个优先级相同的线程同时等待执行时,那么运行系统会以round-robin的方式选择一个线程执行(即轮询调度,以该算法所定的)(Java的优先级策略是抢占式调度!)
    3.被选中的线程可因为一下原因退出,而给其他线程执行的机会:
      1) 一个更高优先级的线程处于可运行状态(Runnable)
      2)线程主动退出(yield),或它的run方法结束
      3)在支持分时方式的系统上,分配给该线程的时间片结束
    4.Java运行系统的线程调度算法是抢占式(preemptive)的,当更高优先级的线程出现并处于Runnable状态时,运行系统将选择高优先级的线程执行
    5.例外地,当高优先级的线程处于阻塞状态且CPU处于空闲时,低优先级的线程也会被调度执行

    参见这里

    放个很详细的博客,不过有些地方还不是特别明白,以后再仔细研读


    线程状态及同步处理

    线程的状态,这里有个线程状态图:
    线程状态图
    其实上面这张图并不是特别完整,下面对于状态的转换会更完整一点:
    线程状态图2

    线程状态 导致状态发生条件
    NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
    Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
    Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
    Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
    Timed_Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
    Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

    有三种方式完成同步操作:

    1. 同步代码块。
    2. 同步方法。
    3. 锁机制。

    直接上代码:

    线程同步来模拟过山洞
    /*
    模拟多个人通过一个山洞:
    1.这个山洞每次只能通过一个人,每个人通过山洞的时间为1秒;
    2.随机生成10个人,同时准备过此山洞,并且定义一个变量用于记录通过隧道的人数。
    显示每次通过山洞人的姓名,和通过顺序;
     */
    public class Test9 {
        public static void main(String[] args) {
    /*
            Hole hole1 = new Hole();
            for (int i = 0; i < 10; i++) {
                Thread t = new Thread(hole1);
                t.start();
            }
    */
            //使用锁
            int numOfCrossed = 0;
            int[] theTest = new int[1];
            //锁必须是同一个,下面synchronized的参数同理,关于synchronized参数并没有限制
            Lock lock = new ReentrantLock();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    lock.lock();
                    crossTheHole(theTest);
                    lock.unlock();
                }).start();
            }
        }
    
        public static void crossTheHole(int[] numOfCrossed){
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在穿过洞");
            try {
                Thread.sleep(600);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            numOfCrossed[0]++;
            System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed[0] +"个");
        }
    }
    
    class Hole implements Runnable{
        int numOfCrossed = 0;
        Object lock = new Object();
        @Override
        public void run(){
            crossTheHole2();
        }
        //同步代码块
        public void crossTheHole(){
            synchronized (lock){
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在穿过洞");
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                numOfCrossed++;
                System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed +"个");
            }
        }
        //同步方法
        public synchronized void crossTheHole2(){
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在穿过洞");
            try {
                Thread.sleep(500);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            numOfCrossed++;
            System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed +"个");
        }
    }
    
    

    线程通信及锁

    进程间通信使用wait()和notify()进行等待和唤醒,注意使用的锁需要是同一个,并且线程被唤醒后并不是立即进入运行状态,而是从WAITING释放与其他线程进行竞争,如果获取到锁,则进入RUNNABLE状态,否则进入BLOCKED状态(即未获取到锁)。
    TIMED_WAITING在API中描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态,最常见的就是sleep()方法,当然,sleep单独一个线程也可以使用,注意sleep()并不会释放当前锁权限
    关于wait()和notify():

    Thread类的方法:sleep(),yield()等
    Object的方法:wait()和notify()等
    每个对象都有一个机锁来控制同步访问。Synchronized关键字可以和对象的机锁交互,来实现线程的同步。
    由于sleep()方法是Thread 类的方法,因此它不能改变对象的机锁。所以当在一个Synchronized方法中调用sleep()时,线程虽然休眠了,但是对象的机锁没有被释放,其他线程仍然无法访问这个对象。而wait()方法则会在线程休眠的同时释放掉机锁,其他线程可以访问该对象。
    Yield()方法是停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield() 方法将不会起作用。
    一个线程结束的标志是:run()方法结束。
    一个机锁被释放的标志是:synchronized块或方法结束。
    Wait()方法和notify()方法:当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的机锁。当它被一个notify()方法唤醒时,等待池中的线程就被放到了锁池中。该线程从锁池中获得机锁,然后回到wait()前的中断现场。
    join()方法使当前线程停下来等待,直至另一个调用join方法的线程终止。
    值得注意的是:线程的在被激活后不一定马上就运行,而是进入到可运行线程的队列中。

    另外,Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的,比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。

    关于多线程的更多信息请参见我的另一篇博文:Java多线程之以7种方式让主线程等待子线程结束,里面用到了concurrent这个强大的多线程包。


    synchronized:
    synchronized同步代码块时锁可以使任意对象
    synchronized同步非静态方法时,锁是this(因此同一对象的多个同步方法不能同时执行)
    synchronized同步静态方法时,锁是该类的类对象,即类名.class(即多个同步静态方法不能同时执行)

  • 相关阅读:
    laravel底层源码解析:pipeline,db,console
    composer命令清单
    composer使用笔记
    git常见问题
    JS阻止冒泡和取消默认事件(默认行为)
    vue项目构建:vue-cli+webpack常用配置
    MVC和三层架构
    SSM框架初始配置
    Java对象间的关系
    Spring框架
  • 原文地址:https://www.cnblogs.com/lixin-link/p/10991710.html
Copyright © 2020-2023  润新知