美文网首页程序员
想成为架构师,这些Java 多线程的知识你一定要知道,讲的很细!

想成为架构师,这些Java 多线程的知识你一定要知道,讲的很细!

作者: 程序员匡胤 | 来源:发表于2020-07-21 20:48 被阅读0次

    一、线程基础

    线程概述:
    线程是程序运行的基本执行单元,当操作系统执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须建立一个线程(这个线程称为主线程)来作为程序的主入口点,进程和线程是操作系统中必不可少的运行模型,在操作系统中可以有多个进程,包括系统进程和用户进程,一个进程可有多个线程,进程和进程之间不可共享内存,线程不仅可以共享进程的内存,而且还拥有自己的内存空间,,这段内存空间也叫线程栈,主要用来保存线程内部所使用的数据,如线程执行的函数中所定义的变量。
    线程的意义:
    (1)充分利用CPU资源
    (2)简化编程模型
    (3)简化异步事件的处理
    (4)使GUI更有效率
    (5)提高程序的执行效率

    二、创建线程

    Java语言使用类Therad代表线程,所有的线程对象必须是Therad类或其子类的实例,每条线程的作用是完成一定的任务,,实际上就是执行一段程序流,Java中使用run()方法来表示这一段程序流,创建线程有两种方法一是使用Therad类创建,二是使用Runnable接口创建。

    使用使用Therad类创建线程

    //创建线程所需要的类.第一种方法,继承thread类,重写run()方法,该类再调用run()方法
    class Xc extends Thread {
        public void run() {//重写run方法
            for(int i=0;i<10;i++) {
                System.out.println("线程函数"+i);   
            }
        }
    }
    public class mythread{
        public static void main(String[] args) {
        Xc xc1=new Xc();//线程1
        xc1.start();//开始线程1运行
        Xc xc2=new Xc();//线程2
        xc2.start();//开始线程2运行
        }
    }
    

    运行结果

    虽然线程类的子类可以实例化,但是在子类中必须覆盖run()方法才能运行线程的代码

    //第二种方法,实现runnable接口,实现run()方法,创建thread对象,调用start方法
    class Xc1 implements Runnable{
        public void run() {
            for(int i=0;i<=10;i++) {
                System.out.println("线程函数"+i);   
            }
        }
    }
    public class mythread01 {
        public static void main(String[] args) {
            Xc1 a1 =new Xc1();
            Xc1 a2 =new Xc1();
            Thread a= new Thread(a1);
            Thread b= new Thread(a2);//将实例化对象传入线程a,b
            a.start();
            b.start();
        }
    }
    

    运行结果

    由此可见两个线程同时运行
    在使用Runnable接口的类使,必须使用Therad类的实例才能创建线程,使用Runnable接口创建线程的两个步骤:将Runnable接口实例化;创建Therad对象,并将第一步实例化的对象作为参数传入Therad类的构造方法,最后通过Therad类的start()方法运行线程。

    三、线程的常用方法

    currentTherad():Therad类的静态方法,返回当前正在执行的线程对象
    setName();设置线程名
    getName();获取线程名
    isAlive();线程是否处于运行状态,处于运行状态时返回true,否则返回false,

    public class MyThread03 extends Thread{
        public void run(){
            System.out.println(this.getName());//获取线程名
        }
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName());//获取当前正在执行的线程对象
            MyThread03 thread1=new MyThread03();
            System.out.println("isAlive:"+thread1.isAlive());//返回线程是否处于运行状态
            MyThread03 thread2=new MyThread03();
            MyThread03 thread3=new MyThread03();//创建3个线程
            thread1.setName("线程1");
            thread2.setName("线程2");//设置线程1.2的名字
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    

    运行结果

    第一个输出的是主函数的线程名,第二行表示线程thread1已不在处于运行状态,由于thread3没有命名线程名称,所有系统默认命名为Thread-2

    四、线程的生命周期

    线程要经历开始(等待),运行,挂起,停止4种不同的状态,这4种状态通过线程类中的方法来进行控制。一旦线程开始执行run()方法,就会一直到这个方法结束之后线程才退出。这是一种终止线程的方法,其他的是stop()方法,强行终止线程(这个方法不推荐),suspend()()(不建议使用)使用后挂起线程,可以通过resume()(不建议使用)方法唤醒线程,而使用sleep(long millis)方法使线程休眠后,只能在设定时间结束处于就绪状态,该方法一般要抛出异常,join()该方法为强制执行线程,表示执行该线程结束后才能执行其他线程。

    public class MyThread04 extends Thread{
        public void run() {
            for(int x=0;x<5;x++) {
                try {
                    Thread.sleep(600);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("子线程number:"+x);
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread mainThread =Thread.currentThread();//获得主线程
            MyThread04 thread =new MyThread04();
            thread.start();//开始子线程
            
            for(int x=0;x<10;x++) {
                if(x==5) {      //设置强制执行条件
                    try {
                        mainThread.join();//强制执行线程任务
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(100);//延缓执行
                    
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行,x="+x);
            }
        }
    }
    

    运行结果

    本程序启动了两个线程main线程和子线程,本来两个程序交替运行,而当满足x==5时main线程会强制执行完毕后会再继续执行子线程,本来main线程要执行10次,遇到了join()方法,被强制执行完毕。

    五、线程的同步与死锁

    程序利用线程可以进行更为高效的程序处理,如果在没有多线程的程序中,那么一个程序在处理某些资源时主线程会进行全部处理,但是这样的处理速度会很慢,但是如果使用多线程的处理机制,可以使子线程与主线程一起处理资源,那么效率会比一个线程执行的更高。虽然使用多线程同时处理资源效率比单线程高许多,但是多线程如果操作同一个资源时一定会存在一些问题,例如操作资源完整性问题,由此有线程的同步与死锁问题。

    线程同步
    线程同步是指若干个线程对象并行进行资源访问时实现资源处理的保护操作,下面利用一个模拟买票的程序来进行同步问题的说明。

    class MyThread implements Runnable{//定义线程执行类
        private int ticket =3;//总票数为3
        public void run() {
            while(true) {//持续买票
                if(this.ticket>0) {//还有剩余票
                try {
                    Thread.sleep(100);//模拟网络延迟
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"买票,ticket ="+this.ticket--);
                }else {
                    System.out.println("****票已经卖光了****");
                break;//跳出循环
                }
            }
        }
    }
    public class MyThread05 {
        public static void main(String[] args) throws Exception{
        MyThread mt=new MyThread();
        new Thread(mt,"售票员A").start();//开启买票线程
        new Thread(mt,"售票员B").start();
        new Thread(mt,"售票员C").start();
        }
    }
    

    运行结果

    在本程序中为了更好地观察到同步问题,在判断票数(this.ticket>0) 和卖票(this.ticket–) 操作之间追加了一个线程休眠操作以实现延迟的模拟。通过执行的结果也可以发现程序出现了不同步的问题,而造成这些问题主要是由于代码的操作结构所引起的,因为卖票操作分为两个步骤。

    步骤1 ( this.ticket>0):判断票数是否大于0,大于0则表示还有票可以卖。

    步骤2 (thisticket–); 如果票数大于0,则卖票出去。

    假设现在只剩下最后一张票了,当第一个线程满足售票条件后(此时并未减少票数),其他的线程也有可能同时满足售票的条件,这样同时进行自减操作时就有可能造成负数。

    线程同步处理

    造成并发资源访问不同步的主要原因在于没有将若干个程序逻辑单元进行整体性的锁定,即当判断数据和修改数据时只允许一个线程进行处理,而其他线程需要等待当前线程执行完毕后才可以继续执行,这样就使得在同一个时间段内,只允许一个线程执行操作, 从而实现同步的处理。

    Java中提供有synchronized关键字以实现同步处理,同步的关键是要为代码加上“锁",而对于锁的操作程序有两种:同步代码块、同步方法。

    同步代码块是指使用synchronized 关键字定义的代码块,在该代码执行时往往需要设置一个同步对象,由于线程操作的不确定状态,所以这个时候的同步对象可以选择this。

    class MyThread implements Runnable{//定义线程执行类
        private int ticket =3;//总票数为3
        public void run() {
            while(true) {//持续买票
                synchronized(this) {
                    if(this.ticket>0) {//还有剩余票
                        try {
                            Thread.sleep(100);//模拟网络延迟
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"买票,ticket ="+this.ticket--);
                    }else {
                        System.out.println("****票已经卖光了****");
                        break;//跳出循环
                    }
                }
            }
        }
    }
    public class MyThread05 {
        public static void main(String[] args) throws Exception{
        MyThread mt=new MyThread();
        new Thread(mt,"售票员A").start();//开启买票线程
        new Thread(mt,"售票员B").start();
        new Thread(mt,"售票员C").start();
        }
    }
    

    运行结果


    本程序将票数判断与票数自减的两个控制逻辑放在了一个同步代码块中, 当进行多个线程并发执行时,只允许有一个线程执行此部分代码,就实现了同步处理操作。

    提示:同步会造成处理性能下降

    同步操作的本质在于同一个时间段内只允许有一个线程执行, 所以在此线程对象未执行完的过程中其他线程对象将处于等待状态,这样就会适成程序处理性能的下降。但是同步也会带来些优点:数据的线程访问安全。

    最后

    感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!

    相关文章

      网友评论

        本文标题:想成为架构师,这些Java 多线程的知识你一定要知道,讲的很细!

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