第十七天 多线程

线程
两者定义
  • 进程:一个应用程序(一个进程就是一个软件)
  • 线程:一个进程中的执行单元
    • 一个进程中可以启动多个线程
两者关系
  • 进程可以看做一个公司
  • 线程可以看做公司员工

  • 进程之间的内存独立不恭喜啊
  • 线程在java语言中:
    • 共享堆内存和方法区
    • 栈内存独立,一个线程一个栈
    • 如果启动10个线程,就会开辟10个栈空间,每个栈之间互不干扰,各自执行,这就是多线程并发
目的
  • 提高程序的处理效率
思考
  • 在使用线程机制之后,如果main()方法结束了,是不是程序并不结束
    • main方法结束只是主线程结束了,即代表主栈空了,其他的栈可能仍旧在弹栈和压栈

实现线程的三种方式
  • 判断下述代码有几个线程

package Day024多线程;

public class Test01 {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }

    private static void m1() {
        System.out.println("m1() begin");
        m2();
        System.out.println("m1() over");
    }

    private static void m2() {
        System.out.println("m2() begin");
        m3();
        System.out.println("m2() over");
    }

    private static void m3() {
        System.out.println("m3() execute");
    }
}
三种方式
  1. 编写一个类,继承java.lang.Thread,重写run()方法
  2. 编写一个类,实现java.ang.Runnable接口,实现run()方法
  3. 实现Callable接口
实现线程的第一种方式
  • Java支持多线程机制,且Java已经将多线程实现了,只需要继承即可

步骤:

  1. 编写一个类,继承Thread,重写run
  2. 创建一个线程对象
    • new一个线程对象
  3. 启动线程
    • 使用用start()方法
package Day024多线程;

public class Test02 {
    //mian方法在主栈中执行,属于主线程
    public static void main(String[] args) {
        //2. 创建一个线程对象
        MyThread myThread = new MyThread();

        //3. 启动线程
        myThread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main ---> " + i);
        }
    }
}

//1. 编写一个类,继承Thread,重写run
class MyThread extends Thread{
    @Override
    public void run() {
        //这段代码在分支线程中执行
        for (int i = 0; i < 100; i++) {
            System.out.println("MyThread ---> " + i);
        }
    }
}

start()方法

  • start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,开辟之后瞬间结束,线程也就启动成功了
  • 线程启动成功之后就会自动调用run()方法,run方法于分支线程好比是main方法于主线程
  • 如果直接调用run方法与调用start方法的区别是:编辑器会认为run方法是一个普通方法,就不会有新的栈空间开辟,也就不存在多线程
实现线程的第二种方式

步骤:

  1. 编写一个类,实现Runnable,实现run方法
    • 该类不是一个线程类,仅仅是一个可运行的类
  2. 创建一个可运行对象
  3. 将该对象封装成一个线程对象
  4. 启动线程
package Day024多线程;

public class Test03 {
    public static void main(String[] args) {
        //2. 创建一个可运行对象
        //MyRunnable myRunnable = new MyRunnable();

        //3. 将该对象封装成一个线程对象
        //Thread t = new Thread(myRunnable);

        //合并2,3
        Thread t = new Thread(new MyRunnable());
        
        //4. 启动线程
        t.start();

        //主线程的程序
        for (int i = 0; i < 100; i++) {
            System.out.println("main ---> " + i);
        }
    }
}


//1. 编写一个类,实现Runnable,实现run方法
class MyRunnable implements Runnable{       //这不是一个线程类,只是一个可运行的类
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyRunnable ---> " + i);
        }
    }
}

注:这种实现接口的方式比较常用,因为一个类实现了接口不影响它去继承其他类,更加的灵活

使用匿名内部类的方式实现线程
package Day024多线程;

public class Test04 {
    public static void main(String[] args) {
        //创建类,使用匿名内部类的方式
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Runnable ---> " + i);
                }
            }
        });

        //启动线程
        t.start();

        for (int i = 0; i < 100; i++) {
                System.out.println("main ---> " + i);
            }

    }
}
  • 在实例化Thread对象的时候,传入new Runnable对象及其run方法
实现线程的第三种方式

见后续

线程的生命周期
  • 新建状态
    • 刚new出来的线程对象
  • 调用start方法
  • 就绪状态
    • 就绪状态亦叫“可运行状态”,表示当前的线程具有抢夺CPU时间片的资格(CPU时间片就是执行权)
    • 当一个线程抢到CPU时间片后,就开始执行run方法,即代表线程进入运行状态
  • 运行状态
    • run方法的执行标志线程进入运行状态,当之前的CPU时间片用完之后,就会重新回到就绪状态
    • 重复就绪、运行,直到run方法执行结束或者遇到阻塞事件
    • 重复运行的时候,是接着上次的代码继续执行
    • 情况1:run方法执行结束,线程进入死亡状态
    • 死亡状态
    • 情况2:遇到阻塞事件,进入阻塞状态
  • 阻塞状态
    • 线程遇到阻塞状态的时候,就会放弃之前占有的CPU时间片,直至阻塞接触
    • 但是因为之前的CPU时间片已经没有了,所以会直接回到就绪状态,重写争抢CPU时间片
基础的三个方法
  1. 获取当前线程对象 —— currentThread()
  2. 获取线程对象名字 —— getName()
  3. 修改线程对象名字 —— setName()
package Day024多线程;

public class Test05 {
    public static void main(String[] args) {
        //获取当前线程对象
        Thread thread1 = Thread.currentThread();
        System.out.println(thread1);

        //new线程对象
        Thread thread = new Thread(new MyRunnable1());
        //修改线程对象名字
        thread.setName("t1");
        //获取线程对象名字
        String name = thread.getName();
        System.out.println(name);
        //启动线程
        thread.start();


        //再new一个线程对象
        Thread thread2 = new Thread(new MyRunnable1());
        //修改线程名字
        thread2.setName("t2");
        //获取并输出线程对象名字
        System.out.println(thread2.getName());
        //启动线程
        thread2.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main ---> " + i);
        }
    }

}

class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyRunnable1 ---> " + i);
        }

        //获取当前线程对象
        String name = Thread.currentThread().getName();
        System.out.println("---> " + name);
        /*
        * currentThread指的是当前对象,那么在分支线程中,当前对象是谁呢?
        * 谁启动run方法,currentThread就指向谁
        * t1线程执行了run方法,那么当前线程就是t1
        * t2线程执行了run方法,那么当前线程就是t2
        * */
    }
}
线程的睡眠
sleep方法
  • static void sleep(long millis)
  • 所以可以知道,sleep方法是静态方法
  • Thread.sleep();进行调用
  • 参数是毫秒
  • 作用:让当前线程进入休眠(阻塞状态),放弃CPU占有时间片,让给其他线程使用
  • 可以达到这种效果:间隔特定时间,去执行一段特定的代码
package Day024多线程;

public class Test06 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);

            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("over");
    }
}
  • 面试题
package Day024多线程;

public class Test07 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();

        //调用sleep方法
        try {
            t.sleep(1000);
            //会让程序进去阻塞状态(休眠)吗?
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("over");
    }
}

class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " ---> " + i);
        }
    }
}

终止线程睡眠
  • 是终止线程睡眠,而不是终止线程
package Day024多线程;

public class Test08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable4());
        t.setName("t");
        t.start();

        //睡眠5秒后醒来
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //中断t线程睡眠(利用了Java的异常处理机制)
        t.interrupt();

    }
}

class MyRunnable4 implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName() + "begin");
            try {
                //睡眠一年
                Thread.sleep(1000 * 60 * 60 * 24 * 365);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        System.out.println(Thread.currentThread().getName() + "over");
    }
}
终止线程执行
强行终止

注:该方法存在很大的缺点,容易丢失数据,因为这种方式是直接将线程杀死了,所以线程没有保存的数据就会丢失,不建议使用

package Day024多线程;

public class Test09 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable5());
        t.setName("t");
        t.start();

        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //5秒之后,线程强制终止
        t.stop();   //已经过时,不建议使用
    }
}

class MyRunnable5 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " ---> " + i);
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
合理终止
package Day024多线程;

public class Test010 {
    public static void main(String[] args) {
        MyRunnable6 myRunnable6 = new MyRunnable6();
        Thread t = new Thread();
        t.setName("t");
        t.start();

        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //终止线程(什么时候终止t的执行,把标记改成false就可以了)
        myRunnable6.run = false;
    }
}

class MyRunnable6 implements Runnable{
    //打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                return;
            }
        }
    }
}
线程调度
常见线程调度模型
  • 抢占式调度模型
    • 线程的优先级越高,抢到CPU时间片的概率就高一些
    • Java就是采用抢占式调度模型
  • 均分式调度模型
    • 平均分配CPU时间片,每个线程占有CPU时间的时间一样
线程调度的方法
修饰符或类型方法名作用
voidsetPriority(int newPriority)设置线程的优先级
intgetPriority()获取线程优先级最低\高优先级是1\10,默认是5
static voidyield()暂停当前正在执行的线程对象,并执行其他线yield()方法不是阻塞方法,让当前线程从“运行状态”回到“就绪状态”,在回到就绪后可能还会抢到
voidjoin()合并线程
线程优先级
  • 线程的优先级越高,抢到的CPU时间片相对于多一点
package Day024多线程;

public class Test011 {
    public static void main(String[] args) {
        //获取当前线程对象
        Thread thread = Thread.currentThread();
        //获取当前线程优先级
        System.out.println(thread.getPriority());

        //新建线程对象
        Thread t1 = new Thread(new MyRunnable7());
        //设置t线程的优先级为10
        thread.setPriority(10);
        t1.setName("t");
        t1.start();
        
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

class MyRunnable7 implements Runnable{
    @Override
    public void run() {
        //获取线程优先级
        System.out.println("线程" + Thread.currentThread().getName() + "优先级是:" + Thread.currentThread().getPriority());

        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}
线程让位
  • 让当前线程暂停,回到就绪状态,让给其他线程
package Day024多线程;

public class Test012 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable8());
        t.setName("t");
        t.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

class MyRunnable8 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 100 == 0){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}
线程安全问题

注:

  • 在以后的开发中,项目都是在服务器中运行的,而服务器已经将线程定义了,线程对象的创建,线程的启动等,都已经完成了。所以这些代码是不需要写的
  • 所以程序员应该更加关注的是线程安全问题
    • 因为编写的程序是放在一个多线程环境下运行的,所以必须得处理好数据在多线程并发环境下是否安全的问题
线程不安全的条件
  1. 多线程并发
  2. 存在共享数据
  3. 数据有修改的操作

满足上述三个条件后,就会出现线程安全问题

解决线程安全问题
  • 解决方法:线程排队执行(不能并发)
  • 这种机制被称为:线程同步机制
  • 专业术语叫做:线程同步
    • 就是线程不能并发,必须排队执行

当线程开始排队执行的时候,就会牺牲一部分效率,但是数据安全了

同步和异步
  • 异步编程模型
    • 线程t1和线程t2,各自执行,互不相干
    • 即:多线程并发(效率较高)
  • 同步编程模型
    • 线程t1和线程t2,在同一时间只能执行一个线程
    • 即:线程排队执行(效率较低)
账户对象的例子

在不适用线程同步机制的情况,多线程对同一个账户进行取款,出现线程安全问题

账户:

package Day024多线程;

public class Account {
    //账户
    private String account;
    //余额
    private int balance = 10000;


    //构造方法
    public Account() {
    }

    public Account(String account) {
        this.account = account;
    }

    public Account(String account, int balance) {
        this.account = account;
        this.balance = balance;
    }


    //set and get
    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;
    }


    //取款的方法
    public void withdrawal(int money){
        //取钱之后的余额
        int after = getBalance() - money;

        //睡眠5秒,模拟网络延迟
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //更新余额,网络延迟导致t1取完钱后,数据没有上传更新,t2在这段时间取钱了
        setBalance(after);
    }
}

线程:

package Day024多线程;

public class AccountThread implements Runnable {
    //线程共享一个账户对象
    private Account account;

    public AccountThread(Account account) {
        this.account = account;
    }

    //run方法表示取款操作
    public void run() {
        //取款5000元
        int money = 5000;
        account.withdrawal(money);

        System.out.println(account.getAccount() + "取款:" + money + ",余额:" + account.getBalance());
    }
}

测试:

package Day024多线程;

public class AccountTest {
    public static void main(String[] args) {
        //实例化Account对象
        Account account = new Account("张三");

        //实例化线程对象
        Thread t1 = new Thread(new AccountThread(account));
        //实例化另外一个线程对象,并让两个线程操作同一个账户
        Thread t2 = new Thread(new AccountThread(account));

        //线程改名
        t1.setName("t1");
        t2.setName("t2");

        //线程启动
        t1.start();
        t2.start();

        
        /*
        张三取款:5000,余额:5000
        张三取款:5000,余额:5000
        
        这种情况就导致t1和t2处均取出5000块,而在银行数据缺没有更新
        * */
    }
}

使用线程同步机制,让数据变安全
用户

package Day024多线程;

public class Account2 {
    //账户
    private String account;
    //余额
    private int balance = 10000;


    //构造方法
    public Account2() {
    }

    public Account2(String account) {
        this.account = account;
    }

    public Account2(String account, int balance) {
        this.account = account;
        this.balance = balance;
    }


    //set and get
    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;
    }


    //取款的方法
    public void withdrawal(int money){

        //下列代码块中的代码在线程中必须排队
        synchronized (this){
            //取钱之后的余额
            int after = getBalance() - money;

            //睡眠5秒,模拟网络延迟
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新余额,网络延迟导致t1取完钱后,数据没有上传更新,t2在这段时间取钱了
            setBalance(after);
        }


    }
}

线程

package Day024多线程;

public class AccountThread2 implements Runnable{
    private Account2 account;

    public AccountThread2() {
    }

    public AccountThread2(Account2 account) {
        this.account = account;
    }

    @Override
    public void run() {
        int money = 5000;
        account.withdrawal(money);

        System.out.println(account.getAccount() + "取款:" + money + ",余额:" + account.getBalance());
    }
}

测试

package Day024多线程;

public class AccountTest2 {
    public static void main(String[] args) {
        Account2 account = new Account2("张三");

        Thread t1 = new Thread(new AccountThread2(account));
        Thread t2 = new Thread(new AccountThread2(account));

        t1.start();
        t2.start();
    }
}
对synchronized的理解
public void withdrawal(int money){
    synchronized (this){
        int after = getBalance() - money;
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setBalance(after);
    }
}
  • 上述代码使用了线程同步机制,即只有当一个线程执行完成之后,另外一个线程才能继续执行
  • 语法:
synchronized(  /*共享对象*/   ){
    //线程同步代码块
}
  • 共享对象是写什么?

    • 需要同步线程的共同作用的对象
    • 例如:有 t1 t2 t3 三个线程,现在只需要t1和t2排队,t3不同排队
      • 共享对象就写成t1和t2的共享对象,而这个对象对于t3来讲是不共享的
    • withdrawa方法中共享的对象是:账户对象,
      • this代表的是当前对象,当前对象就是账户对象,所以可以填入this
      • 要根据情况而论,不一定是this,只要是多线程共享的那个对象即可
  • java语言中,所有对象都有一个标记,习惯称为“锁”

    • 100个对象就有100个锁,1个对象就有1个锁
  • 上述withdrawa方法的执行原理:

    • 线程t1和线程t2并发,执行取款操作的时候,会有一前一后
    • 假设t1先执行了,遇到了synchronized,就会自动去找“共享对象”的对象锁,找到之后,在线程同步代码块中的代码执行的过程中,就一直占用这把锁
    • t2再遇到synchronized的时候,也会去想去占有这把锁,但是因为t1已经占用了共享对象的锁,所以t2线程只能在同步代码块外面等待t1执行完成
    • t1执行完同步代码块后,会归还锁,然后t2再占有这把锁,t2就开始执行同步代码块中的代码
  • 所以说,共享对象一定要选取好

变量线程安全问题

  • Java的三大变量:实例变量、静态变量、局部变量
    • 局部变量永远不会出现线程安全问题,为什么?

使用局部变量的话,建议使用:StringBuilder

synchronized出现在实例方法上

  • synchronized出现在实例方法上,就一定是给this上锁了,
    • 所以也就导致了调用的时候十分不灵活
    • 这样也可能扩大同步的范围,导致程序的执行效率降低,所以并不常用
    • 优点:代码写得少了
  • 实例:
package Day024多线程;

public class Account3 {
    public static void main(String[] args) {
        Account3 a = new Account3("张三", 10000);

        Thread t1 = new Thread(new AccountThread3(a));
        Thread t2 = new Thread(new AccountThread3(a));

        t1.setName("t1");
        t2.setName("t2");

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


    }

    private String name;
    private double balance;

    public Account3() {
    }

    public Account3(String name, double balance) {
        this.name = name;
        this.balance = balance;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取钱
    public synchronized void withdraw(double money){
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        setBalance(getBalance() - money);
    }

}

class AccountThread3 implements Runnable{
    private Account3 account;

    public AccountThread3(Account3 account) {
        this.account = account;
    }

    public Account3 getAccount() {
        return account;
    }

    public void setAccount(Account3 account) {
        this.account = account;
    }

    @Override
    public void run() {
        double money = 5000;
        account.withdraw(money);

        System.out.println(account.getName() + "取款:" + money + ",余额:" + account.getBalance());
    }
}

面试题01

  • 在doSome执行的时候,需要等待doOther执行结束吗?
package Day024多线程;

public class Test014 {
    public static void main(String[] args) {
        Myclass mc = new Myclass();
        MyThread01 mt1 = new MyThread01(mc);
        MyThread01 mt2 = new MyThread01(mc);

        mt1.setName("mt1");
        mt2.setName("mt2");

        mt1.start();
        mt2.start();
    }
}

class MyThread01 extends Thread{
    private Myclass mc;

    public MyThread01(Myclass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("mt1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("mt2")){
            mc.doOther();
        }
    }
}

class Myclass {
    public synchronized void doSome(){
        System.out.println("doSome...begin");

        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("doSome...over");
    }

    public void doOther(){
        System.out.println("doOther...");
    }
}
  • 因为doOther方法并没有synchronized关键字锁住对象,所以实行doOther的时候,不需要共享对象锁

面试题02

  • 执行doSome的时候,需要等待doOther吗
package Day024多线程;

public class Test015 {
    public static void main(String[] args) {
        MyClass mc = new MyClass();

        MyThread2 t1 = new MyThread2(mc);
        MyThread2 t2 = new MyThread2(mc);

        t1.setName("t1");
        t2.setName("t2");

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

class MyThread2 extends Thread{
    private MyClass mc;

    public MyThread2(MyClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome...begin");

        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("doSome...over");
    }

    public synchronized void doOther(){
        System.out.println("doOther...begin");
    }
}
  • 会,因为doOther方法也被对象锁锁住了

面试题03

  • 执行doSome的时候,需要等待doOther吗
package Day024多线程;

public class Test016 {
    public static void main(String[] args) {
        MyClass2 mc1 = new MyClass2();
        MyClass2 mc2 = new MyClass2();

        MyThead3 mt1 = new MyThead3(mc1);
        MyThead3 mt2 = new MyThead3(mc2);

        mt1.setName("t1");
        mt2.setName("t2");

        mt1.start();
        mt2.start();
    }
}

class MyThead3 extends Thread{
    private MyClass2 mc;

    public MyThead3(MyClass2 mc) {
        this.mc = mc;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass2{
    public synchronized void doSome(){
        System.out.println("doSome...begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome...over");
    }

    public synchronized void doOther(){
        System.out.println("doOther...begin");
    }
}
  • 不会,因为作用的对象都不是同一个

面试题04

  • 执行doSome的时候,需要等待doOther吗
package Day024多线程;

public class Test017 {
    public static void main(String[] args) {
        MyClass3 mc1 = new MyClass3();
        MyClass3 mc2 = new MyClass3();

        MyThead4 t1 = new MyThead4(mc1);
        MyThead4 t2 = new MyThead4(mc2);

        t1.setName("t1");
        t2.setName("t2");

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

class MyThead4 extends Thread{
    private MyClass3 mc;

    public MyThead4(MyClass3 mc) {
        this.mc = mc;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            MyClass3.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            MyClass3.doOther();
        }
    }
}

class MyClass3{
    public synchronized static void doSome(){
        System.out.println("doSome...begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome...over");
    }

    public synchronized static void doOther(){
        System.out.println("doOther...begin");
    }
}
  • 需要,因为synchronized修饰的静态方法,静态方法是类锁,不管创建了几个对象,类锁只有一把
死锁
  • 要求必须会写
  • 注:synchronized在开发中最好不要嵌套使用,因为一不小心就很容易导致死锁现象发生

实例

package Day024多线程;

public class Test018 {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();

        MyThread02 mt1 = new MyThread02(o1, o2);
        MyThread03 mt2 = new MyThread03(o1, o2);

        mt1.setName("t1");
        mt2.setName("t2");

        mt1.start();
        mt2.start();
    }
}

class MyThread03 extends Thread{
    Object o1;
    Object o2;

    public MyThread03(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o1){
            try {
                Thread.sleep(1000 * 2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }
}

class MyThread02 extends Thread{
    Object o1;
    Object o2;

    public MyThread02(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o2){
            try {
                Thread.sleep(1000 * 3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}
  • 分析:
    • 当mt1和mt2两个线程开始的时候,
    • mt1锁住了o1对象
    • mt2锁住了o2对象
    • 然后两个都开始睡眠
    • 睡醒之后mt1发现o2对象被锁住,进入等待
    • mt2发现o1对象被锁住,继续进入等待
    • 于是“死锁”就发生了
解决线程安全问题
  • 方案一:使用局部白能力代码“实例变量”和“静态变量”
  • 方案二:如何必须使用实例变量,就考虑创建多个对象,如此一来实例变量的内存就不共享了
  • 方案三:如果不能使用局部变量,对象也不能创建多个的情况再使用 synchronized 关键字(线程同步机制)
线程的其他内容
守护线程
  • Java中线程的种类
    • 用户线程
    • 守护线程(后台线程,例如:垃圾回收器)
  • 特点:
    • 一般都是死循环
    • 所有的用户线程结束,守护线程自动结束
  • 实例
package Day024多线程;

public class Test019 {
    public static void main(String[] args) {
        MyThread3 t1 = new MyThread3();
        t1.setName("t1");

        t1.setDaemon(true);

        t1.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main --->" + i);
        }
    }
}

class MyThread3 extends Thread{
    @Override
    public void run() {
        int i = 0;
        while (true){
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
        }
    }
}
定时器
  • 作用:间隔特定的时间,执行特定的操作
  • 在Java的类库中已经写好了一个定时器:java.util.Timer
    • 不过这种方式在目前的开发中也很少使用,因为很多高级框架都是支持定时任务的
  • 在实际的开发中,使用较多的是Spring框架提供的SpringTask框架,这个框架只需要进行简单的配置,就可以完成定时器的任务
package Day024多线程;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class Test020 {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
//        Timer timer = new Timer(true);    //守护线程的模式

        //指定时间任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-12-27 15:55:30");  //开始时间

//        timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        timer.schedule(new LogTimerTask(), firstTime, 1000);
    }
}

class LogTimerTask extends TimerTask {
    public void run() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String StrTime = sdf.format(new Date());
        System.out.println(StrTime + "    完成一次数据备份");
    }
}

  • 定时任务类继承是不是Thread而是TimerTask
  • schedule()方法的使用
实现线程的第三种方式:实现Callable接口
  • 这种方式实现的线程可以获取线程的返回值
    • 之前的两种不行,因为run( )方法返回void
  • 当需要拿到一个线程执行的结果的时候,使用该方法
  • 步骤:
    • 创建一个“未来任务类”对象(参数十分重要,需要给Callable接口实现对象)
    • 在call方法中写获取结果的步骤
    • 使用get()方法获取结果
      • 注:get方法会导致“当前“线程进入”阻塞状态“
  • 实例
package Day024多线程;


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

public class Test021 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                System.out.println("call  begin");
                Thread.sleep(1000 * 3);
                System.out.println("call over");

                int a = 100;
                int b = 200;
                return a + b;
            }
        });

        Thread t1 = new Thread(task);
        t1.start();

        Object object = task.get();
        System.out.println("线程的执行结果是:" + object);

        System.out.println("阻塞");
    }
}

不使用匿名内部类的方式

package Day024多线程;

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

public class Test022{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask task = new FutureTask(new MyCallable());

        Thread t1 = new Thread(task);
        t1.start();

        Object o = task.get();
        System.out.println("线程执行结果是:" + o);

    }
}

class MyCallable implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("call begin");
        Thread.sleep(1000 * 3);
        System.out.println("call over");

        int a = 100;
        int b = 200;
        return a + b;
    }
}

注:Callable是一个接口,只能实现,且不能实例对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值