• java多线程代码实例详解(按线程生命周期全面讲解)


    昨天,由于工作比较繁忙,只是简单整理了一下java的线程的生命周期的流程图,今天就根据这个流程图来一步一步的讲解java多线程的知识。

    图再来一遍:

    第一点、java线程新生态的生成

    也就是线程新建成功

    1、继承Thread类(为了方便添加线程名字,可以自定义构造方法),代码如下:

    public class MyThread extends Thread{
            // 自己书写构造方法,两种方法都可
            public MyThread(String name){
                // super.setName(name);
                   super(name);
            }
            @Override
            public void run() {
                System.out.println("This is a Thread!");
            }
        }

    二、实现Runnable接口(现在大多都用函数式编程来实现,为了看的明白,咱两种都用)

    // 一般方法
     Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            },"thread1");
    
    // 函数式编程
    Thread thread2 = new Thread(() ->System.out.println(Thread.currentThread().getName()),"thread2");

    第二点、java由新生态转化为就绪态

    实际就是start方法,start方法是开启一个新的线程,如果只调用run方法,还是在主线程执行run方法,不会开启新的线程,线程不会进入到就绪状态,所以start方法才是开启线程的方法,

    run方法中存入的只是任务内容。代码如下:

    Thread thread = new Thread(() ->System.out.println(Thread.currentThread().getName()),"thead");
    // 线程的启动
    thread.start();

    第三点、就绪态到运行态

    这一步的话,没有办法使用代码实现,因为呢,当线程执行start方法后,许多线程会去抢占cpu时间片,哪个线程抢占到了cpu时间片,哪个线程就进入到运行状态。

    第四点、由运行态回到就绪态

    回到就绪态,就需要线程主动放弃抢到的cpu时间片,也就是大家常说的线程的礼让

    但是需要注意的是,礼让不代表将cpu时间片彻底让给其他线程,还可能在次抢占时又获取到cpu时间片(没办法,有时候都不知道自己折磨厉害)

    代码如下:

    public static void main(String[] args) {
        Runnable r = () -> {
            for (int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + " : " + i);
                if(i == 3){
                    System.out.println(Thread.currentThread().getName() + " : " + i + "start use yield");
                    // 线程开始礼让
                    Thread.yield();
                }
    
            }
        };
        Thread t1 = new Thread(r,"thread1");
        Thread t2 = new Thread(r,"thread2");
        // 启动线程
        t1.start();
        t2.start();
    }

    第五点、由运行态到阻塞态

    故名思意就是阻塞了,线程暂停了,主要导致线程暂停的方法由:等待用户输入、使用sleep方法、使用jion方法

    一、用户输入就不多说了无非是:

    Thread thread = new Thread(() -> {
    Scanner scanner = new Scanner(System.in);
    int number = scanner.nextInt();
    }, "thread1");
    thread.start();

    二、使用sleep方法(睡眠)代码如下:

     public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                for (int i = 0;i < 10;i++){
                    System.out.println(Thread.currentThread().getName()+" : "+i);
                    try {
                        // 没执行一次睡眠1s
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"thread1");
            thread.start();
        }    

    三、使用join方法

    Thread类中的join方法的主要作用就是同步,使得线程之间的并行执行变为串行执行,切记join在start前面执行无效,不多说

     public static void main(String[] args) {
            // 创建两个线程
            Thread thread1 = new Thread(() -> {
                for(int i = 0;i < 10;i++){
                System.out.println(Thread.currentThread().getName()+" : "+i);
                }
            },"thread1");
    
            Thread thread2 = new Thread(() -> {
                for(int i = 0;i < 10;i++){
                    System.out.println(Thread.currentThread().getName()+" : "+i);
                }
            },"thread2");
            thread1.start();
            // 使用线程同步方法
            try {
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread2.start();
            System.out.println("This is main Thread!");
        }

    执行多次后结果如下:

    thread1 : 0
    thread1 : 1
    thread1 : 2
    thread1 : 3
    thread1 : 4
    thread1 : 5
    thread1 : 6
    thread1 : 7
    thread1 : 8
    thread1 : 9
    This is main Thread!
    thread2 : 0
    thread2 : 1
    thread2 : 2
    thread2 : 3
    thread2 : 4
    thread2 : 5
    thread2 : 6
    thread2 : 7
    thread2 : 8
    thread2 : 9

    呦呵,thread1厉害了,比主线程优先执行完,还没有thread2与其抢占线程。

    揪其原因如下: 程序在main线程中调用thread1线程的join方法,则main线程放弃cpu控制权,并返回thread线程继续执行直到线程t1执行完毕,

    所以是thread1执行完后,主线程才执行,其实就是主线程中同步了thread1线程,执行join方法后,主线程阻塞,等thread1执行完必后,主线程继续执行

    第六点、阻塞态回到就绪态

    话不多说了,就是用户输入完必,sleep方法执行完毕、join方法执行完毕

    第七点、由运行态到锁池

    首先,涉及到的内容为锁,那么什么条件可以用到锁呢,都有哪些锁呢

    一、为什么用到了锁

    来,先看一个小问题,实现一个购票系统,4个购票员进行购票,代码如下:

     // 定义售票箱的类
        static class Ticket{
            public static int number = 10;
        }
        // 定义四个售票员
        // 使用四个线程进行模拟
        public static void main(String[] args) {
            Runnable r = () -> {
                while(Ticket.number > 0){
                    System.out.println(Thread.currentThread().getName()+"start sell one ticket......"+"The remaining quantity of tickets is:"+ --Ticket.number);
                }
            };
            Thread t1 = new Thread(r,"thread - 1");
            Thread t2 = new Thread(r,"thread - 2");
            Thread t3 = new Thread(r,"thread - 3");
            Thread t4 = new Thread(r,"thread - 4");
            t1.start();
            t2.start();
            t3.start();
            t4.start();

    来,查看部分结果:

    thread - 1 start sell one ticket......The remaining quantity of tickets is:3
    thread - 1 start sell one ticket......The remaining quantity of tickets is:2
    thread - 1 start sell one ticket......The remaining quantity of tickets is:1
    thread - 1 start sell one ticket......The remaining quantity of tickets is:0
    thread - 4 start sell one ticket......The remaining quantity of tickets is:40
    thread - 3 start sell one ticket......The remaining quantity of tickets is:41

    ??神魔鬼

    原来都是资源共享惹的祸,这几个线程共享这个资源,造成了线程不安全

    怎么整?

    让他们排队用这个资源就好了呀,一个线程操作这个资源时,锁住资源,不让其他线程动即可,这也就有了锁

    二、有哪些锁

    1、同步代码段

    话不多说直接上代码:

      // 定义售票箱的类
        static class Ticket{
            public static int number = 10;
        }
        // 定义四个售票员
        // 使用四个线程进行模拟
        public static void main(String[] args) {
            Runnable r = () -> {
                while(Ticket.number > 0){
                    // 对象锁 "" 任意写都行
                    // 类锁
                    // 此锁对于所有人需要都相同
                    synchronized (""){
                        if (Ticket.number <= 0){
                            return;
                        }
                    System.out.println(Thread.currentThread().getName()+"start sell one ticket......  "+"The remaining quantity of tickets is:"+ --Ticket.number);
                }
                }
            };
            Thread t1 = new Thread(r,"thread - 1");
            Thread t2 = new Thread(r,"thread - 2");
            Thread t3 = new Thread(r,"thread - 3");
            Thread t4 = new Thread(r,"thread - 4");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }

    2、同步方法

    话不多说直接上代码:

     // 定义售票箱的类
        static class Ticket{
            public static int number = 10;
        }
        // 定义四个售票员
        // 使用四个线程进行模拟
        public static void main(String[] args) {
            Runnable r = () -> {
                while(SourceConflict.Ticket.number > 0){
                    sellTicket();
                }
            };
            Thread t1 = new Thread(r,"thread - 1");
            Thread t2 = new Thread(r,"thread - 2");
            Thread t3 = new Thread(r,"thread - 3");
            Thread t4 = new Thread(r,"thread - 4");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    
        /**
         * 同步方法如果为静态方法,锁位类锁,如果不是静态方法,则对应的是this
         */
        public synchronized static void sellTicket(){
            if(Ticket.number <= 0){
                return;
            }
            System.out.println(Thread.currentThread().getName()+"start sell one ticket......"+"The remaining quantity of tickets is:"+ --SourceConflict.Ticket.number);
        }

    3、显示锁

    话不多说直接上代码:

      // 定义售票箱的类
        static class Ticket{
            public static int number = 10;
        }
        // 定义四个售票员
        // 使用四个线程进行模拟
        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
            Runnable r = () -> {
                while(Ticket.number > 0){
                    lock.lock();
                    if(Ticket.number <= 0){
                        return;
                    }
                    System.out.println(Thread.currentThread().getName()+"start sell one ticket......"+"The remaining quantity of tickets is:"+ --Ticket.number);
                    lock.unlock();
                }
            };
            Thread t1 = new Thread(r,"thread - 1");
            Thread t2 = new Thread(r,"thread - 2");
            Thread t3 = new Thread(r,"thread - 3");
            Thread t4 = new Thread(r,"thread - 4");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }

    三、所以由运行态怎样到锁池呢

    当某一代码段或者方法被锁住后,其他线程等待锁标记,无法访问就会进入锁池

    四、补充

    提到锁,不得不说的就是死锁

    来先来个代码,瞅一瞅:

     public static void main(String[] args) {
            Runnable runnable1 = () -> {
              synchronized ("a"){
                  System.out.println("Thread1 gets lock named a");
                  synchronized ("b"){
                      System.out.println("Thread1 gets locks named a and b");
                  }
              }
            };
            Runnable runnable2 = () -> {
                synchronized ("b"){
                    System.out.println("Thread2 gets lock named b");
                        synchronized ("a"){
                            System.out.println("Thread2 gets lock named a and b");
                        }
                }
            };
            Thread t1 = new Thread(runnable1);
            Thread t2 = new Thread(runnable2);
            t1.start();
            t2.start();
        }

    这个就会锁到死,完全符合死锁的定义:死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁

    runnable1占着a锁标记等b,rannable2占着b锁标记等a,等到死,也不释放,就产生了死锁

    怎么解决呢,这时候就引出下一个内容,使用wait方法进入等待对列,之后通过唤醒进入锁池

    第八点、使用wait方法进入等待对列,之后通过唤醒进入锁池

    一、通过wait方法进入锁池以及唤醒

    为了解决上个死锁问题,我们采用让其中一个线程,拿到一个锁标记,执行完必后,释放掉锁标记,进入等待对列,等其他线程执行完必后,唤醒此线程即可。

    代码如下:

     public static void main(String[] args) {
            Runnable runnable1 = () -> {
                synchronized ("a"){
                    System.out.println("Thread1 gets lock named a");
                    try {
                        "a".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized ("b"){
                        System.out.println("Thread1 gets locks named a and b");
                    }
                }
            };
            Runnable runnable2 = () -> {
                synchronized ("b"){
                    System.out.println("Thread2 gets lock named b");
                    synchronized ("a"){
                        System.out.println("Thread2 gets lock named a and b");
                        "a".notifyAll();
                    }
                }
            };
            Thread t1 = new Thread(runnable1);
            Thread t2 = new Thread(runnable2);
            t1.start();
            t2.start();
        }

    二、唤醒方法notifyAll和notify区别

    notify:通知,是Object中的一个方法,唤醒等待队列中的一个线程,使这个线程进入锁池

    notifyAll: 通知,是Object中得一个方法,唤醒等待队列中得所有线程,使这些线程全部进入锁池

    第九点、锁池到就绪态

    锁池中的线程,拿到锁标记回到就绪态,也就是占有锁标记的线程将锁标记释放

    第十点、死亡态

    一、线程中的所有逻辑执行结束

    二、线程执行过程中出现了未经处理的异常

    好了,不得不说,java多线程真是让人tddl,今天就先写这些,之后会补上java线程池的内容,本人小白一个,欢迎业界大佬批评指教!

  • 相关阅读:
    过滤器(Filter)
    DBUtils结果集处理器介绍
    Tomcat配置连接c3p0连接池
    JdbcUtils
    数据库连接池
    JDBC处理事务
    JDBC入门(5)--- 时间类型、大数据
    JDBC入门(4)--- 批处理
    JDBC入门(3)--- PrepareStatement
    JDBC入门(2)--- ResultSet之滚动结果集
  • 原文地址:https://www.cnblogs.com/mcjhcnblogs/p/13089232.html
Copyright © 2020-2023  润新知