美文网首页老男孩的成长之路Java成长之路
深度分析:面试90%被问到的多线程、创建线程、线程状态、线程安全

深度分析:面试90%被问到的多线程、创建线程、线程状态、线程安全

作者: 路人甲java | 来源:发表于2020-06-23 21:00 被阅读0次

    一、多线程

    1.概述

    多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。

    就是在单个程序中同时运行多个线程来完成不同的工作。

    2.并发与并行

    并发:指两个或多个事件在同一个时间段内发生。
    并行:指两个或多个事件在同一时刻发生(同时发生)。
    3.程序、进程与多线程
    1)程序
    Java源程序和字节码文件被称为“程序” (Program),是一个静态的概念。

    2) 进程
    是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

    3)线程

    线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    二、创建线程类

    1.继承Thread类

    Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。

    Java中通过继承Thread类来创建并启动多线程的步骤如下:

    定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体
    创建Thread子类的实例,即创建了线程对象
    调用线程对象的start()方法来启动该线程
    自定义线程类:

    public class MyThread extends Thread {
        //定义指定线程名称的构造方法
        public MyThread(String name) {
            //调用父类的String参数的构造方法,指定线程的名称
            super(name);
        }
        /**
         * 重写run方法,完成该线程执行的逻辑
         */
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName()+":正在执行!"+i);
            }
        }
    }
    

    测试类:

    public class Demo01 {
        public static void main(String[] args) {
            //创建自定义线程对象
            MyThread mt = new MyThread("新的线程!");
            //开启新线程
            mt.start();
            //在主方法中执行for循环
            for (int i = 0; i < 10; i++) {
                System.out.println("main线程!"+i);
            }
        }
    }
    
    

    1)Thread类介绍

    构造方法:

    public Thread():分配一个新的线程对象。

    public Thread(String name):分配一个指定名字的新的线程对象。

    public Thread(Runnable target):分配一个带有指定目标新的线程对象。

    public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

    常用方法:

    public String getName():获取当前线程名称。

    public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。

    public void run():此线程要执行的任务在此处定义代码。

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

    public static Thread currentThread():返回对当前正在执行的线程对象的引用

    2.实现Runnable接口

    采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。

    步骤如下:

    定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    调用线程对象的start()方法来启动线程。
    自定义线程类:

    public class MyRunnable implements Runnable {
        @Override
        public void run(){
            for(inti=0;i<20;i++){
                System.out.println(Thread.currentThread().getName()+""+i);
            }
        }
    }
    
    

    测试类:

    public class Demo{
        public static void main(String[]args){
            //创建自定义类对象线程任务对象
            MyRunnable mr = new MyRunnable();
            //创建线程对象
            Thread t = new Thread(mr,"小强");
            t.start();
            for(inti=0;i<20;i++){
            System.out.println("旺财"+i);
            }
        }
    }
    

    3.Thread和Runnable的区别

    实现Runnable接口比继承Thread类所具有的优势:

    适合多个相同的程序代码的线程去共享同一个资源。

    可以避免java中的单继承的局限性。

    增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

    线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

    4.用Lambda表达式创建多线程

    /**
     * 用Lambda表达式来创建多线程
     */
    public class Thread004Lambda {
        public static void main(String[] args) {
            Thread t = new Thread(()->{
                System.out.println("我是一个线程对象");
            });
    
            t.start();//启动线程
            System.out.println("我是主线程");
        }
    }
    
    

    三、线程状态

    1.线程的五种状态
    新生状态: new
    就绪状态: runnable
    运行状态: running
    阻塞状态: blocked
    执行完毕: dead


    image.png

    死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过执行stop或destroy方法来终止一个线程。

    2.终止线程

    不要调用 stop destory 方法 ,太暴力,一盆冷水让其停止。

    自定义类:

    /**
     *
     * 终止线程的套路
     * 1、加入标识
     * 2、线程体的执行需要依赖标识
     * 3、对外提供一个可以修改标识的方法
     * 4、外部适当的时机调用方法修改标识
     */
    class MyThread03 implements Runnable{
        private boolean flag = true;
    
        @Override
        public void run() {
            int i = 0;
            while (true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("在和小美聊天,在说第"+(i++)+"句话");
                if(!flag){
                    break;
                }
            }
        }
        public void stopThread(){
            flag = false;
        }
    }
    

    测试类:

    public class Thread005 {
        public static void main(String[] args) throws Exception{
            MyThread03 mt = new MyThread03();
            Thread t = new Thread(mt);
    
            t.start();//开启线程
    
            Thread.sleep(2000);
            mt.stopThread();
        }
    }
    

    3.阻塞状态(sleep/yield/join方法)

    sleep方法:Sleep时别的线程也不可以访问锁定对象。
    yield方法: 让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态。
    join方法: 等待当前线程死亡,当某个线程等待另一个线程执行结束后,才继续执行时,使用另一个线程的join方法。使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行

    4.线程基本信息

    image.png

    一个线程的默认优先级是5

    Thread.MIN_PRIORITY = 1
    Thread.MAX_PRIORITY = 10
    Thread.NORM_PRIORITY = 5
    注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。

    自定义类:

    class FatherThread  implements  Runnable {
        @Override
        public void run() {
            System.out.println("爸爸想抽烟,发现烟抽完了");
            System.out.println("爸爸让儿子去买包红塔山");
            Thread son = new Thread(new SonThread());
            son.start();  // 启动新的线程
            System.out.println("爸爸等儿子买烟回来");
            try {
                son.join();  // 一直傻傻地等
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("可以抽烟了");
        }
    }
    class SonThread implements Runnable {
        public void run() {
            System.out.println("儿子出门去买烟");
            System.out.println("儿子买烟需要10分钟");
            try {
                for (int i = 1; i <=10;i++) {
                    System.out.println(Thread.currentThread().getName() + " 第" + i + "分钟");
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 儿子买烟回来了");
        }
    }
    
    

    测试类:

    public class Demo008Thread {
        public static void main(String[] args)throws Exception {
    
            Thread t1 = new Thread(new SonThread(),"t1");
            Thread t2 = new Thread(new SonThread(),"t2");
    //        int p = t1.getPriority();获取优先级
            t1.setPriority(10);//设置线程优先级
            t2.setPriority(1);
    
            t1.start();
            t2.start();
            Thread.sleep(1000);
            //System.out.println(t.isAlive());  // 查看线程是否还活着
        }
    }
    

    四、线程安全

    1.线程同步

    当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

    要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

    2.同步代码块

    同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
    格式:

    synchronized(同步锁){
        需要同步操作的代码
    }
    

    同步锁:

    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

    锁对象可以是任意类型。
    多个线程对象要使用同一把锁。

    3.同步方法

    同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
    格式:

    
    public synchronized void method(){
        可能会产生线程安全问题的代码
    }
    1
    2
    

    同步锁是谁?

    对于非static方法,同步锁就是this。
    对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
    综合案例:

    /**
     * 账户类
     */
    class Account {
        String aname; // 账户名
        int money; // 账户余额
    
        public Account(String aname, int money) {
            this.aname = aname;
            this.money = money;
        }
    }
    
    class Drawing implements Runnable{
        Account account = null;
        int money = 0;  // 准备取钱的金额
        int acturalMoney = 0; // 实际取走的钱
    
        public Drawing(Account account, int money) {
            this.account = account;
            this.money = money;
        }
    
        @Override
        public  void run() {
            synchronized (account){//同步代码块
    
                // 如果余额不够, 取不了钱
                if (account.money < money) {
                    System.out.println(" 余额不足, 没去走钱");
                    return;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.money -= money;   // 取了钱,余额变少
                acturalMoney = money;    // 取出来的钱
                System.out.println(Thread.currentThread().getName() + "取了 " + acturalMoney + "  还剩 " + account.money);
            }
        }
    }
    
    

    测试类:

    /**
     * 两个人操作同一个账户
     */
    public class Demo010Thread {
        public static void main(String[] args) throws Exception{
            // 开户
            Account account = new Account("高富帅",100);
            System.out.println(account.money);
            Drawing d =  new Drawing(account,90);
            // 你去取钱
            Thread t = new Thread(d,"你" );
            Thread t1 = new Thread(d,"女朋友" );
            t.start();
            t1.start();
    
            Thread.sleep(1500);
            System.out.println(account.money);
        }
    }
    

    4.死锁及解决方案

    1)死锁的概念
    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    如何解决死锁问题:

    往往是程序逻辑的问题。需要修改程序逻辑。
    尽量不要同时持有两个对象锁。

    相关文章

      网友评论

        本文标题:深度分析:面试90%被问到的多线程、创建线程、线程状态、线程安全

        本文链接:https://www.haomeiwen.com/subject/jalkfktx.html