CUMT--Java--线程

目录

一、线程

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青软实训编 

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值