CUMT--Java--线程

2023-12-28 21:31:12

目录

一、线程

1、概述

2、Java线程模型

3、主线程

二、创建线程?

1、继承Thread类

2、实现Runnable接口

3、使用Callable和Future接口

三、线程生命周期

1、新建和就绪状态

2、运行和阻塞状态

3、死亡状态

四、线程优先级

五、线程同步

1、非同步情况

2、同步代码块?

3、同步方法

4、同步锁

六、线程通信


一、线程

1、概述

? ? ? ? 进程:在操作系统中每个独立运行的程序就是一个进程,是操作系统进行资源分配和调度的一个独立单位,具有独立性、动态性、并发性。

? ? ? ? 线程:进程的组成部分,是最小的处理单位。

? ? ? ? 多线程与多进程的区别:多线程之间数据块相互独立、互不影响。数据块可以共享。

? ? ? ? 多线程编程的优点:

(1)多线程之间可以共享内存,节约系统资源成本

(2)充分利用CPU,执行并发任务效率高

(3)Java内置多线程功能支持,简化编程模型

(4)GUI应用通过启动单独线程收集用户界面事件,简化异步事件处理

2、Java线程模型

(1)Thread类

? ? ? ? Thread类(线程类),一般用于执行线程的构建、状态的切换,在后续的任务实现中,一般会将实现类继承于Thread,并构建Thread实例化对象,通过start来执行线程,并自动运行run()方法,通过重写run()方法,来完成自己需要实现的任务。

方法功能
final boolean isAlive()判断线程是否处于激活状态
static void sleep(long mills)线程休眠,参数以毫秒为单位
void run()线程的执行方法
void start()启动线程的方法,启动线程后自动执行run方法
void stop()线程停止,该方法已过时
final String getName()获取线程名称
final void setName(String name)设置线程的名称为name
long getId()获取线程id
setPriority(int newPriority)设置线程的优先级
getPriority()获取线程的优先级

? ? ? ?

(2)Runnable接口

? ? ? ? Runnable接口用于标识某个Java类是否作为线程类,该接口只有一个抽象方法run()。

(3)Callable接口

? ? ? ? Callable接口提供一个call()方法作为线程的执行体,call()方法可以有返回值?,也可以声明抛出异常。

? ? ? ? 由于Callable接口是函数式接口,在Java8之后可以使用Lambda表达式创建Callable对象。

(4)Future接口

? ? ? ? Future接口用来获取Callable接口中的call方法的返回值

3、主线程

? ? ? ? 每个进程至少包含一个线程,即主线程,主线程用来执行main()方法。

? ? ? ? 在main方法中调用Thread类的静态方法currentThread()来获取当前线程。

public class mainthread {
   public static void main(String []args)
   {
        Thread t=Thread.currentThread();    //创建线程对象为当前线程
        t.setName("MyThread");         //设置线程名为MyThread
        System.out.println(t.getName());    //获取线程名
        System.out.println(t.getId());      //获取id
   } 
}

二、创建线程?

? ? ? ? 创建线程的方式:继承Thread类、实现Runnable接口、使用Callable和Future接口

1、继承Thread类

? ? ? ? 步骤:

(1)定义一个子类继承Thread类,重写run()方法

(2)创建子类的实例

(3)调用线程对象的start()方法启动该线程。

public class ThreadDemo extends Thread{
    public void run()
    {
        System.out.println("启动run方法");    //重写run方法
    }

    public static void main(String []args)
    {
        ThreadDemo td=new ThreadDemo();       //创建Thread对象
        td.start();                           //线程启动
    }    
}

2、实现Runnable接口

????????步骤 :

(1)定义一个类实现Runnable接口

(2)创建一个Thread类的实例,将Runnable接口的实现类所创建的对象作为参数传入Thread类的构造方法中

(3)调用Thread对象的start()方法启动进程

public class ThreadDemo implements Runnable{
    public void run()
    {
        System.out.println("启动run方法");
    }

    public static void main(String []args)
    {
        Thread td=new Thread(new ThreadDemo()); //实现Runnable接口的实现类所构建的对象作为参数传入Thread类构造方法中。
        td.start();
    }
}

3、使用Callable和Future接口

? ? ? ? 步骤:

(1)创建Callable接口的实现类,实现call()方法

(2)使用FutureTask类来包装Callable对象

(3)将FutureTask对象作为Thread对象的target。创建并启动新线程

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回

? ? ? ? ?注意:在Callable接口定义中call()的返回值是<T>,也就是说可以任意设计,但不能是void。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadDemo implements Callable{
    public String call()                        //Callable接口实现类,实现call方法
    {
        return "call启动";
    }

    public static void main(String []args)
    {
        FutureTask <String>ft=new FutureTask<String>(new ThreadDemo()); //FutureTask类包装Callable对象
        new Thread(ft).start();                                         //启动新线程
        try{
            System.out.println(ft.get());                               //调用FutureTask类的get方法返回call的返回值。
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

三、线程生命周期

????????线程生命周期要经过五个状态:新建,就绪,运行,阻塞,死亡。

? ? ? ? 线程状态之间的转换如图所示。

1、新建和就绪状态

? ? ? ? 当使用new关键字创建一个线程后,该线程就处于新建状态

? ? ? ? 当线程对象调用start()方法后,线程就处于就绪状态,对于每一个线程new创建后,只能调用一次start()方法。

? ? ? ? 多次运行start()方法,会产生异常。

2、运行和阻塞状态

? ? ? ? 处于就绪状态的线程获得CPU后,开始执行run()方法,但下列情况会使线程进入阻塞状态:

(1)调用sleep()方法,主动放弃所占用CPU资源

(2)调用阻塞IO方法,在该方法返回前,线程被阻塞

(3)试图获得一个同步监视器,但该监视器被其他线程所占用

(4)执行条件不满足,调用wait()方法,使线程进入等待状态

(5)程序调用线程的suspend()方法将线程挂起

? ? ? ? 当线程从阻塞状态解除时,会进入就绪状态而不是运行状态,重新等待线程调度。

? ? ? ? 对于sleep()方法:

(1)毫秒为单位

(2)会引发异常,添加try...catch语句

? ? ? ? 使用sleep()方法后,线程进入阻塞状态,输出isAlive()返回false

? ? ? ? 对于isAlive()方法:

(1)若线程处于就绪、运行、阻塞状态时,返回true

(2)若线程处于新建、死亡状态时,返回false

3、死亡状态

? ? ? ? 结束线程的方式:

(1)线程执行完毕run方法或call方法

(2)抛出一个没有捕获的异常或错误

(3)调用stop()停止线程

? ? ? ? Thread类中的join()方法,用于等待该线程执行完毕,当一个线程调用另一个线程的join()方法时,他会被阻塞,直到被调用的线程执行完毕。在主线程中调用子线程的join()方法,可以确保子线程执行完毕后,再继续执行主线程的后续代码。

? ? ? ? 不要对处于死亡状态的线程调用start()方法,会产生异常。

四、线程优先级

? ? ? ? 线程的优先级代表线程的重要程度,优先级高的线程获得CPU的机会更多。

? ? ? ? Thread类提供三个静态常量标识线程优先级:

(1)MAX_PRIORITY:最高优先级(值为10)

(2)NORM_PRIORITY:默认优先级(值为5)

(3)MIN_PRIORITY:最低优先级(值为1)

? ? ? ? 另外可以通过setPriority()方法设置线程的优先级,通过getPriority()方法来获取线程的优先级。

? ? ? ? 下面代码示例测试不同优先级的线程的输出先后顺序:

? ? ? ? 一般来说,在重复多次运行情况下,优先级高的线程占据输出的前几行的概率要高一些。?

public class priority extends Thread{
    public priority(String name){                //带参构造方法
        super(name);
    }
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println("线程名:"+this.getName()+", 优先级:"+this.getPriority()+", i="+i);
        }
    }
    public static void main(String[] args)
    {
        System.out.println("默认线程优先级为:"+Thread.currentThread().getPriority());
        priority t1=new priority("t1(最低)");
        priority t2=new priority("t2(默认)");
        priority t3=new priority("t3(最高)");
        t1.setPriority(MIN_PRIORITY);            //设置最低优先级
        t3.setPriority(MAX_PRIORITY);            //设置最高优先级
        
        t1.start();
        t2.start();
        t3.start();
    }
}

五、线程同步

? ? ? ? 线程同步保证了某个资源在某一时刻只能由一个线程去访问。

? ? ? ? 线程同步的三种方式:同步代码块、同步方法、同步锁。

? ? ? ? 如果不使用同步,多线程同时访问同一数据就会造成数据丢失修改的错误,产生安全问题。

1、非同步情况

? ? ? ? 下面代码将通过银行存取钱问题,演示不使用同步时产生的问题:

? ? ? ? 注意:银行存取钱问题一共分为两个类(银行类,存取钱类),一个主函数

(1)建立银行类,变量账户名和账户余额

//银行类
public class Bankaccount {
    private String bankNo;      //账户名
    private double balance;     //账户余额
    public Bankaccount(String bankNo,double balance)
    {
        this.bankNo=bankNo;
        this.balance=balance;
    }
    public String getbankNo()
    {
        return bankNo;
    }
    public double getbalance()
    {
        return balance;
    }
    public void setbankNo(String bankNo)
    {
        this.bankNo=bankNo;
    }
    public void setbalance(double balance)
    {
        this.balance=balance;
    }
    public String toString()
    {
        return "账户名:"+bankNo+", 账户余额:"+balance;
    }
}

(2)建立非同步情况下的存取钱实现类 ,继承与线程类,并实现run方法进行存取钱操作。

//非同步情况存取钱实现类
public class Nosynbank extends Thread {
    private Bankaccount account;    //银行账户
    private double money;           //操作金额
    public Nosynbank(String name,Bankaccount account,double money)
    {
        super(name);            //线程名
        this.account=account;
        this.money=money;
    }
    public void run()
    {
        double d=this.account.getbalance();    //获取当前银行账户余额
        //money>0代表存钱,money<0代表取钱,当取钱数大于余额时
        if(money<0&d<-money)
            System.out.println(this.getName()+"操作失败,余额不足");
        else
        {
            d+=money;
            System.out.println(this.getName()+"操作成功,当前余额:"+d);
            try{
                Thread.sleep(1);               
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
            this.account.setbalance(d);        //修改当前银行账户余额
        }
    }
}

(3)测试类,由于多个线程非同步情况下,对于同一个账户的账户金额进行修改,所以四个线程的触发顺序并不按着输入的顺序执行,另外多次重复运行,最后的银行的账户余额也不同。

public class Demo {
    public static void main(String[]args)
    {
        Bankaccount bank1=new Bankaccount("0001", 10000);
        Nosynbank t1=new Nosynbank("Thread-1", bank1, 5000);
        Nosynbank t2=new Nosynbank("Thread-2", bank1, -2000);
        Nosynbank t3=new Nosynbank("Thread-3", bank1, 3000);
        Nosynbank t4=new Nosynbank("Thread-4", bank1, 1000);

        //启动所有线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();

        //等待当前所有子线程结束
        try{
            t1.join();  
            t2.join();
            t3.join();
            t4.join();
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(bank1);      //查看当前账户余额
    }
}

2、同步代码块?

(1)改写存取钱类,在run方法下添加一行synchronized(this.account),限制run方法的所有代码,其他所有代码均不修改。

public class Synbank extends Thread{
    /*
        成员变量和成员方法省略
    */
    public void run()
    {
        //同步代码块
        synchronized(this.account)
        {
            double d=this.account.getbalance();
            //money>0代表存钱,money<0代表取钱,当取钱数大于余额时
            if(money<0 && d<-money)
                System.out.println(this.getName()+"操作失败,余额不足");

            else
            {
                d+=money;
                System.out.println(this.getName()+"操作成功,当前余额:"+d);
                try{
                Thread.sleep(1000);               
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
                this.account.setbalance(d);
            }
        }
    }
}

(2)修改测试类,实例化同步存取钱类即可,注意此时的存取钱的线程仍然不按照所编写的顺序去执行,但是最后的银行账户金额修改正确,证明了同步方法避免了丢失修改问题。

public class Demo {
    public static void main(String[]args)
    {
        Bankaccount bank1=new Bankaccount("0001", 10000);
        Synbank t1=new Synbank("Thread-1", bank1, 5000);
        Synbank t2=new Synbank("Thread-2", bank1, -2000);
        Synbank t3=new Synbank("Thread-3", bank1, 3000);
        Synbank t4=new Synbank("Thread-4", bank1, 1000);

        //启动所有线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();

        //等待当前所有子线程结束
        try{
            t1.join();  
            t2.join();
            t3.join();
            t4.join();
        }
        
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(bank1);      //查看当前账户余额
    }
}

3、同步方法

(1)在银行账户类中添加同步存取钱方法,用synchronized来限制,同时修改银行账户线程的调用方式,不需要设置变量d来存储存取钱后的账户余额。

public class Bankaccount {
    /*
        成员变量和成员方法的函数省略,与上文相同
    */
    //同步方法
    public synchronized void access(double money)
    {
            //money>0代表存钱,money<0代表取钱,当取钱数大于余额时
            if(money<0 && balance<-money)
                //此时线程名称的表示,由于在类内,没有实例化对象,所以只能通过返回当前线程的名称的方式,代替原有的this.account.getName()
                System.out.println(Thread.currentThread().getName()+"操作失败,余额不足");    

            else
            {
                //注意同步代码块时另设置了变量d用来存储修改后的银行金额,并通过成员方法进行设置,而在类内不需要这样做。
                balance+=money;
                System.out.println(Thread.currentThread().getName()+"操作成功,当前余额:"+balance);
                try{
                    Thread.sleep(1000);               
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
    }
    
}

?(2)修改同步存取钱类的run方法,调用银行账户类中的access方法。

public class Synbank extends Thread{
    /*
        省略成员变量和成员方法
    */
    public void run()
    {
        this.account.access(money);
    }
}

4、同步锁

? ? ? ? 同步锁在同步方法的基础上进行修改。

(1)在银行账户类中,添加ReentrantLock锁对象,注意限制为private final

(2)access方法中对于同步的限制synchronized取消。

(3)在保证线程安全情况之前增加“加锁”操作,就是对access函数内的代码加一个try...finally异常机制,在try...finally之前加锁。

(4)在执行完线程安全代码后进行“释放锁”,加在finally块中,保证无论是否存在异常都会在语句执行完或中断后执行释放锁。

import java.util.concurrent.locks.ReentrantLock;

public class Bankaccount {
    /*
        其他成员方法和成员变量省略
    */
    private final ReentrantLock lock=new ReentrantLock();    //同步锁变量
    
    //同步方法
    public void access(double money)
    {
            lock.lock();                                     //线程安全情况下加锁,加在try外面
            try{
                if(money<0 && balance<-money)
                System.out.println(Thread.currentThread().getName()+"操作失败,余额不足");

                else
                {
                    balance+=money;
                    System.out.println(Thread.currentThread().getName()+"操作成功,当前余额:"+balance);
                    try{
                        Thread.sleep(1000);               
                    }
                    catch(InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }   
            }
            finally{                                        //无论是否加锁,都会执行释放锁
                    lock.unlock();
            }
            
            
    }
    
}

? ? ? ? 通过同步代码块、同步方法和同步锁方法都可以达到同步的方式。

六、线程通信

? ? ? ? 线程通信可使用Object类中定义的三个方法:

(1)wait():让当前线程等待,并释放对象锁,直到其他线程调用该监视器的notify()或notifyAll()方法来唤醒线程。

(2)notify():唤醒此同步监视器下等待的单个线程,解除该线程的阻塞状态

(3)notifyAll()? :唤醒此同步监视器下等待的所有线程,唤醒次序由系统控制。

? ? ? ? wait和sleep的区别:wait方法调用时会释放对象锁,而sleep方法不会。

??参考书籍:《Java 8 基础应用与开发》QST青软实训编?

文章来源:https://blog.csdn.net/m0_60177079/article/details/135268446
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。