Java核心API-多线程

标题目录

  • 多线程
    • 线程与多线程
    • 顺序执行与并发执行
    • 使用场景
    • 线程的创建
      • 第一种创建线程形式
      • 第二种创建线程形式
      • 匿名内部类创建形式
    • 线程API
      • 主线程
      • 线程的生命周期
      • Sleep 阻塞
    • 多线程并发安全
      • synchronized关键字
      • 在静态方法上使用synchronized

多线程

线程与多线程

  • 线程是一个单一的顺序执行流程
    代码看起来像是串联在一起,逐句执行
    在这里插入图片描述
  • 多线程就是多个单一顺序执行流程 " 一起 " 执行
    多个串联的语句并联在一起
    Java核心API-多线程_第1张图片

顺序执行与并发执行

  • 顺序执行
    • 单独任何一条线程都是一个线性执行流程,程序从上到下一句一句执行
    • 顺序执行的缺点是代码之间有牵绊,如果上一句阻塞,后续代码则无法执行
  • 并发执行
    • 微观上走走停停,宏观上多个线程一起执行的现象称为 " 并发执行 "
    • 多个线程之间就是并发执行的
    • OS将时间划分为很多时间片段(时间片),尽可能均匀分配给每一个线程,获取时间片段的线程被CPU运行,而其他线程全部等待

使用场景

  • 互不干扰
    多线程通常用于在一个程序中需要同时处理多个互不干扰的任务
  • 多线程更快
    单线程可以完成,但是多线程可以更快

线程的创建

第一种创建线程形式

  • 继承 Thread 来定义一个线程
    重写 run() 方法,在其中定义该线程的任务代码
class MyThread1 extends Thread{
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println("你是谁啊?");
        }
    }
}

class MyThread2 extends Thread{
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println("开门,查水表的!");
        }
    }
}
  • 启动线程
    调用线程的 start() 方法
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        t1.start();
        t2.start();
    }
}

优点:结构简单,利于匿名内部类创建
缺点:
1)存在继承冲突问题。实际开发中我们通常使用继承用于复用某个类的方法,但是如果继承了线程则无法再继承其他类
2)当我们继承Thread后需要重写run方法来定义线程任务,这导致线程与任务存在了必然的耦合关系,不利于线程的复用。

第二种创建线程形式

  • 单独定义线程任务
    • 继承 Runnable 接口 或 Callabled 接口(线程池中常用)
    • 重写 run() 方法,在其中定义该线程的任务代码
class MyRunnable1 implements Runnable{
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println("你是谁啊?");
        }
    }
}

class MyRunnable2 implements Runnable{
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println("我是查水表的!");
        }
    }
}
  • 启动线程
    • 实例化线程任务
    • 实例化线程,同时将任务通过线程的构造器传递给线程
    • 调用线程的 start() 方法
public class ThreadDemo2 {
    public static void main(String[] args) {
        Runnable r1 = new MyRunnable1();
        Runnable r2 = new MyRunnable2();

        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);

        t1.start();
        t2.start();
    }
}

匿名内部类创建形式

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            public void run(){
                for (int i = 0; i < 1000; i++) {
                    System.out.println("你是谁啊?");
                }
            }
        };

        /*Runnable r2 = new Runnable(){
            public void run(){
                for (int i = 0; i < 10; i++) {
                    System.out.println("我是查水表的!");
                }
            }
        };
        Thread t2 = new Thread(r2);*/

        //Runnable接口可以使用lambda表达式创建
        
        /*Runnable r2 = ()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("我是查水表的!");
            }
        };
        Thread t2 = new Thread(r2);*/

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("我是查水表的!");
            }
        });

        t1.start();
        t2.start();
    }
}

线程API

主线程

  • Java程序中任何代码都是靠线程执行的,main 方法也不例外
  • 当程序启动后,JVM 会创建一条线程用于执行 main 方法,这条线程被 JVM 取名为 " main " ,因此我们也称他为 " 主线程 "

Thread 提供的静态方法:

  • static Thread currentThread()
  • 该方法返回执行它的线程
public class CurrentThreadDemo {
    public static void main(String[] args) {
        Thread main = Thread.currentThread();
        System.out.println("主线程:"+main);
        doSome();//主线程执行doSome方法
        //自定义一个线程
        Thread t = new Thread(){
            public void run(){
                Thread current = Thread.currentThread();
                System.out.println("自定义线程:"+current);
                doSome();//自定义线程执行doSome方法
            }
        };
        t.start();
    }

    public static void doSome(){
        //任何方法中都可以用CurrentThread获取当前线程
        Thread t = Thread.currentThread();
        System.out.println("运行doSome方法的线程是:"+t);
    }
}

输出:

主线程:Thread[main,5,main]
运行doSome方法的线程是:Thread[main,5,main]
自定义线程:Thread[Thread-0,5,main]
运行doSome方法的线程是:Thread[Thread-0,5,main]

线程的生命周期

Java核心API-多线程_第2张图片

Sleep 阻塞

  • Thread 的静态方法 sleep 用于使当前进程进入阻塞状态
    • static void sleep(long ms)
  • 该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前线程会重写进入Runnable状态,等待分配时间片
  • 该方法声明抛出一个 InterruptException,调用方法需要捕获这个异常
    • 使用线程的 interrupt() 方法可以中断一条正在 sleep 阻塞的线程,此时 sleep 方法会立即抛出中断异常:InterruptException。
public class SleepDemo {
    public static void main(String[] args) {
        System.out.println("程序开始了");
        try{
            Thread.sleep(5000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("程序结束了");
    }
}

倒计时程序:

  • 程序启动后在控制台输入一个数字
  • 从该数字开始每秒递减(循环中需要使用 sleep 方法阻塞1秒)
  • 到0时输出时间到
public class SleepDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入倒计时:");
        int time = scanner.nextInt();
        for(int i=time;i>0;i--){
            System.out.println(i);
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("时间到");
    }
}

多线程并发安全

当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作顺序混乱从而引发不良后果
临界资源:操作该资源的全过程同一时刻只允许单线程进行

public class Bank {
    private int account = 20000;  //账户余额
    //取款方法,返回值为true表示允许出钞,否则拒绝出钞
    public boolean getMoney(int money){
        int account = getAccount();   //获取当前余额
        if(account>=money){
            account -= money;
            Thread.yield();//让执行yield方法的线程主动放弃本次剩余时间片,用于模拟执行到这里正好时间片耗尽
            saveAccount(account);
            return true;
        }
        return false;
    }

    //查询账户余额
    public int getAccount(){
        return account;
    }

    //保存新余额
    public void saveAccount(int account){
        this.account = account;
    }
}
public class SyncDemo1 {
    public static boolean success1=false;//t1是否取款成功
    public static boolean success2=false;//t2是否取款成功
    public static void main(String[] args) {
        /*
            为什么success1和success2不能直接定义在这里
            因为java语法规定,当一个方法的局部内部类中如果引用了这个方法的其他局部变量
            那么这个变量必须是final的,这意味着局部内部类中不能改变这个变量的值。
            而我们的案例中,局部内部类t1线程或t2线程中需要改变success1或success2的值
            因此它会违反final要求,因此不能定义为局部变量。
        */
        //boolean success1=false;
        Bank bank = new Bank();
        while(true) {
            //在方法中定义的内部类,称为局部内部类
            Thread t1 = new Thread() {
                public void run() {
                    success1 = bank.getMoney(20000);
                }
            };
            Thread t2 = new Thread() {
                public void run() {
                    success2 = bank.getMoney(20000);
                }
            };
            t1.start();
            t2.start();

            try {
                Thread.sleep(10);//让主线程卡10毫秒,目的是等待两个线程执行完
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (success1 && success2) {
                System.out.println("bug卡成功!可刑可铐");
                break;
            } else {
                bank.saveAccount(20000);
                success1 = false;
                success2 = false;
            }
        }
    }
}

synchronized关键字

解决并发安全问题的本质就是将多个线程并发(同时)操作改为同步(排队)操作来解决。

synchronized有两种使用方式

  • 在方法上修饰,此时该方法变为一个同步方法
  • 同步块,可以更准确的锁定需要排队的代码片段

同步方法:

当一个方法使用synchronized修饰后,这个方法称为"同步方法",即多个线程不能同时在方法内部执行,只能有先后顺序的一个一个进行, 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题

public synchronized boolean getMoney(int money)

同步块:

有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率,同步块可以更准确的控制需要多个线程排队执行的代码片段
语法:

synchronized(同步监视器对象){
	需要多线程同步执行的代码片段
}

同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个

public class SyncDemo2 {
    public static void main(String[] args) {
        Shop shop1 = new Shop();
        Shop shop2 = new Shop();
        Thread t1 = new Thread("张三"){
            public void run(){
                shop1.buy();
            }
        };

        Thread t2 = new Thread("李四"){
            public void run(){
                shop2.buy();
            }
        };

        t1.start();
        t2.start();
    }
}

class Shop{
    //在成员方法上使用synchronized时,同步监视器对象不可选,只能是this
    //public synchronized void buy(){}
    public void buy(){
        try{
            Thread t = Thread.currentThread();
            System.out.println(t.getName()+"正在挑衣服...");
            Thread.sleep(5000);
            /*
                通常同步监视器对象选择:临界资源
                换句话说:抢谁锁谁
             */
            synchronized (this){
                System.out.println(t.getName()+"正在试衣服...");
                Thread.sleep(5000);
            }


            System.out.println(t.getName()+"结账离开");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

有效的缩小同步范围可以在保证并发安全的前提下提高并发效率

  • 同步监视器对象的选取

    • 对于同步的成员方法而言,同步监视器对象不可指定,只能是this
    • 对于同步的静态方法而言,同步监视器对象也不可指定,只能是类对象
    • 对于同步块而言,需要自行指定同步监视器对象,选取原则:
      1. 必须是引用类型
      2. 多个需要同步执行该同步块的线程看到的该对象必须是同一个
  • 互斥性

    当使用多个synchronized修饰了多个代码片段,并且指定的同步监视器都是同一个对象时,这些代码片段就
    是互斥的,多个线程不能同时在这些代码片段上执行

在静态方法上使用synchronized

当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果,静态方法使用的同步监视器对象为当前类的类对象(Class的实例)

静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象

public class SyncDemo3 {
    public static void main(String[] args) {
        Boo b1 = new Boo();
        Boo b2 = new Boo();
        Thread t1 = new Thread(){
            public void run(){
//                Boo.doSome();
                b1.doSome();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
//                Boo.doSome();
                b2.doSome();
            }
        };
        t1.start();
        t2.start();
    }
}

class Boo{
    //静态方法指定的同步监视器对象不再是this,而是当前类的类对象(Class实例)
//    public synchronized static void doSome(){}
    public static void doSome(){
        //获取类对象的方式:类名.class
        synchronized (Boo.class){
            Thread t = Thread.currentThread();
            System.out.println(t+"正在执行doSome方法");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(t+"执行doSome方法完毕");
        }
    }
}

你可能感兴趣的:(java,开发语言)