java之多线程(二)

1.线程常用方法

1.1线程等待join

有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。例如,李四只有等 张三下班了他才上班,这时我们需要⼀个⽅法明确等待线程的结束。
在这里插入图片描述
1.join:等待某个线程执行完之后,再执行后续的代码
在这里插入图片描述

**
 * 关于join示例
 */
public class ThreadByJoin {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1=new Thread(()->{
    
    
            //1.张三开始上班
            System.out.println("1.张三开始上班");
            //2.张三正在上班
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            //3.张三下班
            System.out.println("3.张三下班");
        });
        t1.start();
        t1.join();
        Thread t2=new Thread(()->{
    
    
            //1.李四开始上班
            System.out.println("1.李四开始上班");
            //2.李四正在上班
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            //3.李四下班
            System.out.println("3.李四下班");
        });
        t2.start();
    }
}

在这里插入图片描述
join()相比于isAlive()方法的优势:
(1)写法更优雅
(2)运行时所用的资源更少。

join方法如果没有设置参数会无限等待下去
2.带参数的join(n)方法,等待线程结束,最多等待n毫秒。
在这里插入图片描述

1.2线程终止

李四一旦进入到工作状态,它就会按照行动指南上的步骤去工作,不完成是不会结束的,有时我们需要增加一些机制,例如老板突然来电话说对方是骗子,需要赶紧停止转账,涉及到线程的终止。

自定义标识符

/**
 * 使用自定义标识符终止线程
 */
public class ThreadInterrupt{
    
    
    //1.声明一个自定义标识符
    private  volatile static boolean flag=false;
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread=new Thread(()->{
    
    
            while(!flag){
    
    
                System.out.println("正在转账...");
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            System.out.println("啊?差点误了大事!");
        });
        thread.start();
        Thread.sleep(3000);
        //终止线程
        System.out.println("有内鬼,终止交易");
        flag=true;
    }
}

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

使用interrput()终止线程

1.interrupt()需要配合Thread.interrupted()或者Thread.currentThread().isInterrupted()一块使用,从而实现线程的终止。
Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记。
在这里插入图片描述
2.使用thread对象的interrupted()方法通知线程结束。

/**
 * 使用interrupt终止线程
 */
public class ThreadInterrupt2 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread=new Thread(()->{
    
    
       while(!Thread.interrupted()){
    
    
            //while(!Thread.currentThread().isInterrupted()){
    
    
               System.out.println("正在转账...");
           }
            System.out.println("啊?险些误了大事!");
        });
        thread.start();
        Thread.sleep(500);
        //终止线程
        thread.interrupt();
        System.out.println("有内鬼终止交易!");
    }
}

在这里插入图片描述
3.使用Thread.currentThread().isInterrupted()方法。
在这里插入图片描述
4.isInterrupted和interrupted的区别
(1)interrupted属于静态方法,所有程序都可以直接调用的全局方法;而isInterrupted属于某个实例的方法。
(2)interrupted在使用完之后会重置中断标识符,而isinterrputed不会重置中断标识符。
5.线程在接收到中断指令之后,立即中断了线程,相比于上一种自定义中断标识符的方法来说,它能更及时的响应中断线程指令。

使用stop终止线程

1.stop方法虽然可以终止线程,但是它已经是不建议使用的废弃方法,观察源码可以发现:
在这里插入图片描述
从上面的图片可以看出,stop方法是被@Deprecated修饰的不建议使用的过期方法,并且在注释的第一句话就说明了stop方法非安全的方法。在最新版本java中,此方法已经被直接移除了,所以强烈不建议使用。

总结

1.自定义中断标识符的停止方法,,此方法的缺点是不能及时响应中断请求;
2.使用interrupt中断线程方法,此方法是发送一个中断信号给线程,它可以及时响应中断,也是最推荐使用的方法。
3.stop方法,它虽然可以终止线程,但是方法已经是过期的,不建议使用。

1.3yield让出执行权

/**
 * yield方法演示(让出CPU的执行权)
 */
public class ThreadYield {
    
    
    public static void main(String[] args) {
    
    
        Thread t1=new Thread(()->{
    
    
            //得打当前线程
            Thread cThread=Thread.currentThread();
            for (int i = 0; i <100 ; i++) {
    
    
                //让出CPU执行权
                Thread.yield();
                System.out.println("执行了线程:"+cThread.getName());
            }
        },"张三");
                t1.start();
                //创建并动线程
             new Thread(()->{
    
    
                 Thread cThread=Thread.currentThread();
                 for (int i = 0; i < 100; i++) {
    
    
                     System.out.println("执行线程:"+cThread.getName());
                 }
                },"李四").start();

    }
}

在这里插入图片描述
yield方法会出让CPU的执行权,让线程调度器重新调度线程,但还是有一定的几率再一次调用到让出CPU的线程上的,这一次他就会执行线程的方法了,因为yield已经被执行过

1.4获得当前的线程

在这里插入图片描述

public class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
    //得到当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}

2.在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程累计两个子线程的结果。

/**
 * 在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后
 * 在主线程累计两个子线程的结果
 * 实现思路:1.使用普通线程,把线程的随机数的值存起来
 * 实现思路:2.使用有返回值的线程调用
 */
public class ThreadDemo14 {
    
    
     static int num1=0;
     static int num2=0;
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1=new Thread(()->{
    
    
            num1=new Random().nextInt(10);
        });

        t1.start();

        Thread t2=new Thread(()->{
    
    
            num2=new Random().nextInt(10);
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("最终的结果:"+(num1+num2));
    }
}

在这里插入图片描述

1.5休眠当前线程

1.5.1使用sleep休眠

在这里插入图片描述
1.传毫秒

public class ThreadSleep {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread=new Thread(()->{
    
    
            try {
    
    
                Thread.sleep(60*60*1000);
            } catch (InterruptedException e) {
    
    
               // e.printStackTrace();
                System.out.println("我接受到了终止的通知");
            }
        });
        thread.start();
        Thread.sleep(1000);
        System.out.println("终止子线程thread");
        thread.interrupt();
    }
}

在这里插入图片描述

1.5.2使用TimeUnit休眠

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

2.线程状态

2.1所有线程状态

1.打印线程的所有状态

public class ThreadState {
    
    
    public static void main(String[] args) {
    
    
        //打印所有的线程状态
        printState();
    }

    private static void printState() {
    
    
        for(Thread.State item:Thread.State.values()) {
    
    
            System.out.println(item);
        }
    }
}

在这里插入图片描述

线程状态有6种
(1)NEW:新建状态,当线程被创建,但是未启动(start方法)之前的状态。
(2)RUNNABLE:运行状态
在这里插入图片描述
(3)BLOCKED:阻塞状态(如果遇到锁,线程就会变为阻塞状态,等待另一个线程释放锁)
(4)WAITING:等待状态(没有明确的等待时间,无限期等待下去)
(5)TIMED_WAITING:等待状态(有时间限制的等待状态)
在这里插入图片描述
(6)TERMINATED:销毁状态,当线程结束完成之后就会变成此状态。

在这里插入图片描述

2.2线程状态转变

线程状态转变:
在这里插入图片描述
在这里插入图片描述

import java.util.concurrent.TimeUnit;

public class ThreadState {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        //打印所有的线程状态
        //printState();
        Thread t1=new Thread(()->{
    
    
            System.out.println("当前线程状态2:"+Thread.currentThread().getState());
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        System.out.println("当前线程的状态1:"+t1.getState());
        t1.start();
        //让主线程休眠1s
        Thread.sleep(1000);
        System.out.println("当前线程的状态3:"+t1.getState());

        //等待子线程执行完
        t1.join();
        System.out.println("打印线程状态4:"+t1.getState());

    }

    private static void printState() {
    
    
        for(Thread.State item:Thread.State.values()) {
    
    
            System.out.println(item);
        }
    }
}

在这里插入图片描述

3.线程安全问题

1.概念:线程不安全指的是程序在多线程的执行结果不符合预期。
2.单线程:
单线程代码是顺序执行,最后计算的结果为0是正确的。

public class ThreadDemo16 {
    
    
    static class Counter{
    
    
        //变量
        private int number=0;
        //循环次数
        private int MAX_COUNT=0;
        public Counter(int i){
    
    
            this.MAX_COUNT=MAX_COUNT;
        }

        //++方法
        public void incr(){
    
    
            for (int i = 0; i <MAX_COUNT ; i++) {
    
    
                number++;
            }
        }

        //--方法
        public void decr(){
    
    
            for (int i = 0; i <MAX_COUNT ; i++) {
    
    
                number--;
            }
        }

        public int getNumber(){
    
    
            return number;
        }

        public static void main(String[] args) {
    
    
            Counter counter=new Counter(1000000);
            counter.incr();
            counter.decr();
            System.out.println("最终结果:"+counter.getNumber());
        }
    }
}

在这里插入图片描述
2.多线程:
利用多线程对同一个变量进行修改产生了错误,计算结果是错误的。

public class ThreadDemo16 {
    
    
    static class Counter{
    
    
        //变量
        private int number=0;
        //循环次数
        private int MAX_COUNT=0;
        public Counter(int MAX_COUNT){
    
    
            this.MAX_COUNT=MAX_COUNT;
        }

        //++方法
        public void incr(){
    
    
            for (int i = 0; i <MAX_COUNT ; i++) {
    
    
                number++;
            }
        }

        //--方法
        public void decr(){
    
    
            for (int i = 0; i <MAX_COUNT ; i++) {
    
    
                number--;
            }
        }

        public int getNumber(){
    
    
            return number;
        }

        public static void main(String[] args) throws InterruptedException {
    
    
            Counter counter=new Counter(1000000);
            Thread t1=new Thread(()->{
    
    
                //++操作
                counter.incr();
            });
           Thread t2=new Thread(()->{
    
    
               //--操作
               counter.decr();
           });
           //启动线程
            t1.start();
            t2.start();

            //等待两个线程执行完
            t1.join();
            t2.join();
            System.out.println("最终结果:"+counter.getNumber());
        }
    }
}

在这里插入图片描述

线程不安全因素

1.抢占式执行(狼多肉少)
多线程会抢占CPU资源,会造成线程不安全。
2.多个线程修改同一个变量

public class ThreadDemo16 {
    
    
    static class Counter{
    
    
        //变量
        private int number=0;
        //循环次数
        private int MAX_COUNT=0;
        public Counter(int MAX_COUNT){
    
    
            this.MAX_COUNT=MAX_COUNT;
        }

        //++方法
        public void incr(){
    
    
            for (int i = 0; i <MAX_COUNT ; i++) {
    
    
                number++;
            }
        }

        //--方法
        public void decr(){
    
    
            for (int i = 0; i <MAX_COUNT ; i++) {
    
    
                number--;
            }
        }

        public int getNumber(){
    
    
            return number;
        }

        public static void main(String[] args) throws InterruptedException {
    
    
            Counter counter=new Counter(1000000);
            Thread t1=new Thread(()->{
    
    
                //++操作
                counter.incr();
            });
           Thread t2=new Thread(()->{
    
    
               //--操作
               counter.decr();
           });
           //启动线程
            t1.start();
            t2.start();

            //等待两个线程执行完
            t1.join();
            t2.join();
            System.out.println("最终结果:"+counter.getNumber());
        }
    }
}

在这里插入图片描述

3.操作是非原子性操作
(1)什么是原子性?
执行代码时要一次性执行完,不能停顿。
(2)⼀条 java 语句不⼀定是原⼦的,也不⼀定只是⼀条指令。
比如:上面的temp++操作(非原子性)
(一)从内存查询temp当前值
(二)进行数据修改操作temp+1
(三)刷新temp的最新值
(3)不保证原子性会给线程带来什么问题?
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断,结果就可能是错误的。
在这里插入图片描述
上面的情况正确答案应该是0,而上面结果为1,造成了线程的不安全性。
4.内存可见性问题
()可见性指,一个线程对共享变量值的修改,能够及时的被其他线程看到。
(2)java内存模型
java虚拟机规范中定义了java内存模型。目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。
在这里插入图片描述
在这里插入图片描述
由于每个线程有自己的工作内存,这些工作内存中的内容相当于同一个共享变量的"副本",此时修改线程1的工作内存中的值,线程2的工作内存不一定会及时变化。JMM带来的问题是会导致线程非安全问题的发生。

(3)上面的那个计算例子:
正确的结果应该为0,由于内存可见性问题结果错误为1.
在这里插入图片描述

5.指令重排序
编译器优化的本质是调整代码的执行顺序,在单线程下没问题,但在多线程下容易出现混乱,从而导致线程安全问题。
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_51970219/article/details/123692089