【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

 

 

八、

 

posted @ 2020-06-21 21:59  柠檬不萌!  阅读(278)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end