• 【7】Java多线程


    一、基础

    线程与进程的

    在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。

    某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程。

    进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。

    多线程

    Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部,我们又可以启动多个线程。

    二、创建多线程的三种方式

    1、继承 Thread 类,重写run()方法,run()方法代表线程要执行的任务。

    2、实现 Runnable 接口,重写 run()方法,run()方法代表线程要执行的任务。

    3、实现 callable 接口,重写 call()方法,call()作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出;使用start()方法来启动线程

    1、第一种方法:继承Thread类

    Thread是一个线程类,位于java.lang包下

    1)构造方法

    Thread():创建一个线程对象

    Thread(String name):创建一个具有指定名称的线程对象

    Thread(Rummable target):创建一个基于Runnable接口实现类的线程对象

    Thread(Runnable target,String name):创建一个基于Runnable接口实现类,并且具有指定名称的线程对象。

    2)Thread类的常用方法

    public void run():线程相关的代码写在该方法中,一般需要重写。

    public void start():启动线程的方法 public static void sleep(long m):线程休眠m毫秒的方法

    public void join():优先执行调用join()方法的线程。

    2、第二种方法:实现Runnable接口

    只有一个方法run();

    Runnable是Java中用以实现线程的接口

    任何实现线程功能的类都必须实现该接口

    3、第三种方法:实现callable 接口

    1、创建Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体,并且有返回值。

    2、创建Callable 实现类的实例,使用 FutureTask 类来包装Callable 对象,该 FutureTask 对象封装了该Callable 对象的 call()方法的返回值。

    3、使用FutureTask 对象作为 Thread 对象的target 创建并启动新线程。

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

    三、通过Thread 类创建线程实例

    最简单的例子

    class MyThread extends Thread{//创建线程类,继承Thread
        public void run(){
            System.out.println(getName()+"该线程正在执行!");
        }
    }
    public class ThreadTest {
        /**
         * 1.主方法main也是一个线程,mt方法也是一个线程,顺序是随机的
         * 2.启动线程,线程启动用的是srart,启动的是上面的run方法
         * 3.同一个Thread不能重复调用start方法,会抛出IllegalThreadStateException异常
         */
        public static void main(String[] args) {
    //        System.out.println("主线程1");
            MyThread mt=new MyThread();
            mt.start();//启动线程
    //        mt.start();
    //        System.out.println("主线程2");
        }
    }

    四、通过Runnable接口创建线程实例(应用更为广泛)

    实现Runnable接口创建线程:

    1、创建类PrintRunnable实现接口Runnable;

    2、重写run方法;

    3、定义Runnable实现类的对象(例:PrintRunnable pr=new PrintRunnable();)

    4、通过Thread线程类构造方法传入t1分配一个新的线程对象(例:Thread t1=new Thread(pr) ;)

    5、启动线程(例:t1.start();) PS:启动线程只能通过Thread及其子类启动

    //通过Runnable接口创建线程
    class PrintRunnable implements Runnable {
        int i = 1;//两个线程共处理10次
        @Override
        public void run() {
    //        int i = 1;//两个线程各处理10次
            while (i <= 10)
                //currentThread当前线程
                System.out.println(Thread.currentThread().getName() + "正在运行" + (i++));
        }
    }
    
    public class Test {
    
        public static void main(String[] args) {
            //启动线程有3步,
            //适合多个线程处理同一个资源
            PrintRunnable pr = new PrintRunnable();
            Thread t1 = new Thread(pr);
            t1.start();
            Thread t2 = new Thread(pr);
            t2.start();
    
        }
    }

    五、线程的状态和声明周期(sleep&join)

    1.线程的状态

    新建(New):创建Thread类或者Thread子类的对象时。

    可运行(Runnable):创建好的线程调用start方法后,也叫就绪状态。

    正在运行(Runnig):处于可运行状态的线程获取CPU的使用权后。

    阻塞(Blocked):线程遇到干扰暂停后。

    终止(Dead):线程执行完毕或者异常终止。

    2.线程的声明周期

    stop方法已经弃用。

    3、sleep方法

    1. Thread类的方法:public static void sleep(long millis) 
    2. sleep方法的应用场景:-计时 -控制刷新频率
    3. sleep方法的作用:在指定的毫秒数内让正在执行的线程休眠(暂停执行)
    4. 注:在休眠相应时间后,转为可运行状态(而不是正在运行状态),在获取cpu使用权后进入运行状态;
    5. 这个方法可能会发生InterruptedException异常,需要try-catch捕获。
    class MyThread implements Runnable{
    
        @Override
        public void run() {
            for(int i=1;i<=30;i++){
                System.out.println(Thread.currentThread().getName()+"执行第"+i+"次!");
                try {
                    Thread.sleep(1000);//等待1000毫秒,也就是1秒
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }    
    }
    public class SleepDemo {
    
        public static void main(String[] args) {
            MyThread mt=new MyThread();
            Thread t=new Thread(mt);
            t.start();
            Thread t1=new Thread(mt);
            t1.start();
        }
    
    }
    SleepDemo

    4、join方法

    1. Thread类的方法:public final void join(long millis) 
    2. join方法的作用:等待该线程终止的最长时间为millis毫秒,也就是超过这个时间后,无论线程有没有执行完毕,都可以开始执行其它线程。
    3. 调用join( )方法可以使其他线程由正在运行状态变成阻塞状态
    class MyThread extends Thread{
        public void run(){
            for(int i=1;i<=500;i++)
            System.out.println(getName()+"正在执行"+i+"次!");
        }
    }
    
    public class JoinDemo {
    
        public static void main(String[] args) {
            MyThread mt=new MyThread();
            mt.start();
            try {
    //            mt.join();//加入join方法后,mt方法抢先执行
                mt.join(1);//mt方法抢先执行1000毫秒
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            for(int i=1;i<=20;i++){
                System.out.println("主线程运行第"+i+"次!");
            }
            System.out.println("主线程运行结束!");
        }
    
    }
    JoinDemo

    六、线程优先级

    1、Java为线程类提供了10个优先级

    2、优先级可以用整数1-10表示(数字越大优先级越高),超过范围会抛出异常

    3、main方法的主线程默认优先级为5

    4、优先级常量来表示线程优先级:

      MAX_PRIORITY:线程的最高优先级10

      MIN_PRIORITY:线程的最低优先级1

       NOEM_PRIORITY:线程的默认优先级5

    5、优先级相关的方法:

      public int getPriority(); 获取线程优先级的方法

      public void setPriority(int newPriority); 设置线程优先级的方法

    七、线程同步关键字synchronized

    1、多线程运行问题

    1)各个线程是通过竞争CPU时间而获得运行机会的
    2)各线程什么时候得到CPU时间,占用多久,是不可预测的
    3)一个正在运行着的线程在什么地方被暂停是不确定的

    2、线程同步关键字synchronized,使用方式

    1)修饰一个代码块,被修饰的代码块称为同步代码块,作用范围是大括号{}括起来的代码;

     synchronized (obj) {} 
    2)修饰一个方法,被修饰的方法称为同步方法,其作用范围是整个方法;

     public synchronized void saveAccount() {} 
    3)修饰一个静态方法,作用范围是整个静态方法;

     public static synchronized void saveAccount() {} 
    4)修饰一个类,作用范围是synchronized后面括号括起来的部分。

    3、synchronized关键字作用

    保证代码的执行完整性,执行过程不被打断--当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后,其他线程才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,其他线程才能执行并锁定该对象。

    代码实例:

    public class Bank {
        private String account;// 账号
        private int balance;// 账户余额
    
        public Bank(String account, int balance) {
            this.account = account;
            this.balance = balance;
        }
    
        public String getAccount() {
            return account;
        }
    
        public void setAccount(String account) {
            this.account = account;
        }
    
        public int getBalance() {
            return balance;
        }
    
        public void setBalance(int balance) {
            this.balance = balance;
        }
    
        @Override
        public String toString() {
            return "Bank [账号:" + account + ", 余额:" + balance + "]";
        }
    
        // 存款
        public synchronized void saveAccount() {//1方法中加同步关键字
    
            // 获取当前的账号余额
            int balance = getBalance();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 修改余额,存100元
            balance += 100;
            // 修改账户余额
            setBalance(balance);
            // 输出存款后的账户余额
            System.out.println("存款后的账户余额为:" + balance);
        }
    
        public void drawAccount() {
            synchronized (this) {//2语句块中加同步关键字
                // 在不同的位置处添加sleep方法
    
                // 获得当前的帐户余额
                int balance = getBalance();
                // 修改余额,取200
                balance = balance - 200;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                // 修改帐户余额
                setBalance(balance);
                System.out.println("取款后的帐户余额:" + balance);
            }
    
        }
    }
    Bank
    //存款
    public class SaveAccount implements Runnable{
        Bank bank;
        public SaveAccount(Bank bank){
            this.bank=bank;
        }
        //调用run进行存款操作
        public void run(){
            bank.saveAccount();
        }
    }
    SaveAccount
    //取款
    public class DrawAccount implements Runnable{
        Bank bank;
        public DrawAccount(Bank bank){
            this.bank=bank;
        }
    
        //取款操作方法
        @Override
        public void run() {
            bank.drawAccount();
        }
        
    }
    DrawAccount
    public class Test {
    
        public static void main(String[] args) {
            // 创建帐户,给定余额为1000
            Bank bank=new Bank("1001",1000);
            //创建线程对象
            SaveAccount sa=new SaveAccount(bank);
            DrawAccount da=new DrawAccount(bank);
            Thread save=new Thread(sa);
            Thread draw=new Thread(da);
            save.start();
            draw.start();
            try {
                
                draw.join();
                save.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(bank);
        }
    
    }
    Test

    八、

  • 相关阅读:
    专题实验 Toad 用户的创建与管理( 包括 role 等 )
    专题实验 字符集(全球化支持)
    Toad 所有 菜单说明(太多)
    java 调试
    java 基础数据结构
    HeadFirst Jsp 09 (JSTL)
    HeadFirst jsp 08 无脚本JSP
    14 多线程
    Struts2配置
    Struts框架搭建时所遇到的问题
  • 原文地址:https://www.cnblogs.com/haifeima/p/13174191.html
Copyright © 2020-2023  润新知