第七章:多线程

第七章:多线程

一. 多线程的基本概念

1.什么是进程??

一个进程就是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。例如:千千静听进程,魔兽进程,Word 进程,QQ 进程,JVM 启动对应一个进程。

2.什么是线程??

线程是进程的一个执行场景。一个进程可以启动多个线程。

3.多线程的作用是什么??

计算机引入多进程的作用:提高 CPU 的使用率。

4.注意事项

  • 进程和进程之间的内存独立。

  • 线程和线程之间栈内存独立,堆内存和方法区内存共享。一个线程一个栈。

  • 对于单核的CPU来说,真的可以做到正在的多线程并发吗??

     单核的cpu只有一个大脑:
         不能做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
    
         对于单核的CPU来说在某个时间点上,时间上只能处理一件事情,但是由于cpu的处理速度
         极快,多个线程之间频繁切换执行,给人的感觉是:多个事务同时在执行。
    
  • 什么是真正的多线程并发??

      t1线程执行t1的,
      t2线程执行t2的。
      t1不会影响t2,t2也不会影响t1.。这叫真正的多线程并发。
    
  • 使用了多线程机制之后,main方法结束,是不是有可能程序不会结束。
    main方法结束之时主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。

二. 实现进程的三大方式及其缺点和优点。

1. 编写一个类继承java.lang.Thread类,将该类变为一个进程类。

 //定义线程类
        class MyThread extends Thread{
            @Override
            public void run() {
                }
            }
        }
        //创建线程对象
         MyThread myThread = new MyThread();
         //启动线程
         myThread.start();
/*
    实现线程的第一种方式:
        编写一个类,直接继承java.lang.Thread,重写run方法
 */
public class ThreadTest02 {
    
    
    //这里是main方法,这里的代码 属于主线程,在主栈中运行
    public static void main(String[] args) {
    
    
        //新建一个分支线程对象
        MyThread myThread = new MyThread();
        //启动线程
        //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码的任务完成后,瞬间就结束了。
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程启动成功。
        //启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)。
        //run方法在分支栈的底部,main方法在主栈的栈底部,它们是平级的。
        //myThread.run();//不会启动线程,不会分配新的分支栈
        myThread.start();
        //这里的代码还是运行在主线程中
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println("主线程---->" + i);
        }
    }
}

class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        //编写程序,这段程序运行在分支线程中(分支栈)
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println("分支线程--->"+ i);
        }
    }
}

2.实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

 编写一个类,实现java.lang.Runnable接口,实现run方法。
        //定义一个可运行的类,并实现java.lang.Runnable接口
        class MyRunnable implements Runnable{
            @Override
            public void run() {
            }
        }
        //创建线程对象,并通过构造方法包装成线程对象
          Thread t = new Thread(new MyRunnable());
/*
    实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
 */
public class ThreadTest03 {
    
    
    public static void main(String[] args) {
    
    
        //创建一个可运行的对象
        MyRunnable r = new MyRunnable();
        //将可运行的对象封装成一个线程对象
        Thread t = new Thread(r);
        //启动线程
        t.start();
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println("主线程---->" + i);
        }
    }
}
//这不是一个线程类,是一个可运行的类。他还不是一个线程
class MyRunnable implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println("分支线程--->"+ i);
        }
    }
}

3. 使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。

public class ThreadTest15 {
    
    
    public static void main(String[] args) throws Exception{
    
    
        //第一步创建一个“未来任务类对象”
        FutureTask task = new FutureTask(new Callable() {
    
    
            @Override
            public Object call() throws Exception {
    
    //call方法相当于run方法。只不过这个有返回值。
                System.out.println("call method begin");
                Thread.sleep(1000*10);
                System.out.println("call method end");
                int a = 100;
                int b = 200;
                return a + b;
            }
        });
        //创建线程对象
        Thread t = new Thread(task);
        //启动线程
        t.start();

        //怎么在主线程中,获取线程t的返回结果
        //get方法的执行会导致“当前线程阻塞”
        Object obj = task.get();
        System.out.println(obj);
        //main方法这里的程序要想执行必须更改get()方法的结束
        //而get方法可能需要很久。因为get()方法时为了拿另一个线程的执行结果
        //而另一个线程执行时需要时间的。
        System.out.println("hello world");
    }
}

4. 三种方式的优缺点。

  • 第一种方式的缺点:java中的类只支持单继承,这种方式继承了Thread类后就无法继承其他的类(不符合面向接口编程),因此次这种方式不灵活。
  • 第二种方式优点:让类可以继承其他的类,更加的灵活。
  • 第三种方式优点:以上两种方法实现都是在run()方法中写入线程代码块,但run()没有返回值,所以无法获取线程运行后的结果。
  • 第三种方式的缺点:第三种方式重写的call方法虽然有返回值,但是当我们在主线程或者其他线程中获取这个返回值时,会阻塞其他线程的执行。

三. 线程的生命周期

1. 线程的5大状态。

  • 新建状态 :当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

  • 就绪状态 :当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

  • 运行状态 :当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

  • 阻塞状态 :处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

     1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
    
     2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
    
     3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、
                  join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    
  • 死亡状态 :线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
    在这里插入图片描述

四. 线程的调度与控制

1. 常见的线程调度有哪些??

       1.抢占式调度模型:
            哪个线程的优先级比较高,抢到CPU时间片的概率就高一些
            java采用的就是抢占式调度模型
        2.均分式调度模型:
            平均分配CPU时间片,每个CPU占有的CPU时间长度一样
            平均分配,一切平等
            有一些编程语言,线程调度模型采用的就是这种方式。

2. java中提供了哪些方法是和线程调度有关系的呢??

        实例方法
            void setPriority(int newPriority)
                  更改线程的优先级。
            int getPriority()
                  返回线程的优先级。
                  默认优先级:5
                  最低优先级:1
                  最高优先级:10
            优先级比较高的获取CPU时间片可能会多一些。(但不完全是,大概率是多的)
        静态方法:
            static void yield()   让位方法
                  暂停当前正在执行的线程对象,并执行其他线程。
                  yield()方法不是阻塞方法。让当前线程让位,让给其他线程使用。
                  yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
            注意:在回到就绪之后,有可能会再次抢到
        实例方法:
            void join()
            合并线程
            如以下例子所示:
                class MyThread1 extends Thread{
                    public void doSome(){
                        MyThread2 t = new MyThread2();
                        t.join()//当前线程进入阻塞,t线程执行,直至t线程结束。当前线程才可以执行。
                    }
                }
                 class MyThread2 extends Thread{
                 }

3. 线程优先级

MAX_PRIORITY( 最高级 ); 10
MIN_PRIORITY (最低级)1
NOM_PRIORITY(标准)默认 5

/*
    关于线程的优先级
 */
public class ThreadTest11 {
    
    
    public static void main(String[] args) {
    
    
//        System.out.println("最高优先级:" + Thread.MAX_PRIORITY);
//        System.out.println("最低优先级:" + Thread.MIN_PRIORITY);
//        System.out.println("默认优先级:" + Thread.NORM_PRIORITY);
        //获取当前线程的优先级 main线程的默认优先级是5
//        System.out.println(Thread.currentThread().getName() + "线程的默认优先级是:" + Thread.currentThread().getPriority());
        Thread t = new Thread(new MyRunnable5());
        t.setName("t");
        t.start();

        //设置主线程的优先级为1
        //优先级高的只是抢到时间片多的概率较高
        Thread.currentThread().setPriority(1);
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "------>" + i);
        }
    }
}
class MyRunnable5 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);
        }
    }
}

4. sleep() 方法:让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,出于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。

/*
    关于线程的sleep方法:
        public static native void (long millis) throws InterruptedException;
            1.静态方法
            2.参数是毫秒
            3.作用:让当前线程进入休眠,进入"阻塞状态“,放弃占有CPU时间,让给其他线程使用。
            4.Thread.sleep();方法,可以做到这种效果:间隔特定的时间,去执行一段特定的代码。
 */
public class ThreadTest06 {
    
    
    public static void main(String[] args) {
    
    
        /*
        //让当前线程进入休眠,睡眠5秒
        //当前线程是主线程
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒后执行这段代码
        System.out.println("hello world");
         */
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "------>" +i);
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}
/*
    关于Thread.sleep()方法的一个面试题
 */
public class ThreadTest07 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new MyThread3();
        t.setName("t");
        t.start();

        try {
    
    
            //问:这行代码会让线程t进入休眠状态吗??
            /*
              sleep是静态方法,静态方法虽然可以使用引用.的形式调用,但是底层会自动转换为类名.的形式
                因此时长说:静态方法不要用引用.的形式调用,因为它跟引用没有关系
                对于sleep方法,虽然看上去是t.sleep但是底层会转换为Thread.sleep(),因此在当前环境下回让main线程静止。
             */
            t.sleep(1000*5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("hello world");
    }
}
class MyThread3 extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "----->" + i);
        }
    }
}

5. jion() 方法: 让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行。

public class ThreadTest13 {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("main begin");
        Thread t = new Thread(new MyRunnable7());
        t.setName("t");
        t.start();
        //合并线程
        try {
    
    
            t.join();//t合并到当前线程中,当前线程受阻塞,t线程执行直至结束
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("main over");
    }
}
class MyRunnable7 implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "---->"+ i);
        }
    }
}

6.yield() 方法: 让位,当前线程暂停,回到就绪状态,让给其他线程。它与 sleep()类似,只是不能由用户指定暂停多长时间,并且 yield()方法只能让同优先级的线程有执行的机会。

/*
    让位,当前线程暂停,回到就绪状态,让给其他线程。
    静态方法:Thread.yield()
 */
public class ThreadTest12 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new Thread(new MyRunnable5());
        t.setName("t");
        t.start();
        for (int i = 1; i < 1000; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "----->" + i);
        }
    }
}

class MyRunnable6 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 1; i < 1000; i++) {
    
    
            if (i % 100 == 0){
    
    
                Thread.yield();//当前线程暂停一下,让给主线程。
            }
            System.out.println(Thread.currentThread().getName() + "----->" + i);
        }
    }
}

7. interrupt(中断) 方法,唤醒一个正在睡眠的线程。

/*
    如何终止一个正在sleep的线程,即如何中断一个线程的睡眠。
 */
public class ThreadTest08 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();

        //希望5秒之后,t线程醒来
        try {
    
    
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        //中断t线程的睡眠(这种中断睡眠的方式依靠了java的异常处理机制)
        t.interrupt();

    }
}

class MyRunnable2 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() + "----->end");
    }
}

8. 如何强行终止一个线程,即让一个正在运行的线程终止运行。

/*
    如何强行终止一个线程。
        这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死,
        线程没有保存的数据将丢失。不建议使用
 */
public class ThreadTest09 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();

        //模拟5秒睡眠
        try {
    
    
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //5秒之后强行终止t线程
        t.stop();//以过时,不建议使用
    }
}
class MyRunnable3 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();
            }
        }
    }
}

/*
    怎么合理的终止一个线程的执行。这种方式是常用的
 */
public class ThreadTest10 {
    
    
    public static void main(String[] args) {
    
    
        MyRunnable4 r = new MyRunnable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        //模拟5秒
        try {
    
    
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //终止线程
        //以标记的形式终止。
        r.run = false;
    }
}
class MyRunnable4 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就结束了,可以在结束之前,在此代码块中保存数据
                //终止当前线程
                return;
            }
        }
    }
}

五. 多线程并发环境下,数据的安全问题(重点)。

1. 什么时候数据在多线程并发环境下会存在安全问题呢??

必须包含以下三个条件:

  • 条件1:多线程并发
  • 条件2:有共享数据
  • 条件3:共享数据有修改的行为

2. 怎么解决线程安全问题呢???

  • 线程同步机制:线程排队执行。利用排队执行解决线程安全问题。

3. 线程同步的两个专业术语。

  • 异步编程模型:线程t1和t2,各自执行各自的,t1不管t2,t2不管t1(即线程并发,效率较高)
    异步就是并发
  • 同步编程模型:线程t1和t2,在线程t1执行的时候,必须等待t2线程执行结束,反之亦然。(线程排队执行,效率较低)同步就是排队。

4. 线程安全问题是怎么用代码体现的???

如下所示:线程安全问题需要满足三个条件

  • 条件1:多线程并发(t1和t2线程同时执行)。
  • 条件2:有共享数据(t1和t2线程都是操作act对象)。
  • 条件3:共享数据都有修改的行为(线程t1和t2都是对act对象的balance进行修改)。
/*
    银行账户
        不适用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题
 */
public class Account {
    
    
    private String actno;
    private double balance;
    Object obj = new Object();

    public Object get() {
    
    
        return obj;
    }


    public Account() {
    
    
    }
    public Account(String actno, double balance) {
    
    
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
    
    
        return actno;
    }

    public void setActno(String actno) {
    
    
        this.actno = actno;
    }

    public double getBalance() {
    
    
        return balance;
    }

    public void setBalance(double balance) {
    
    
        this.balance = balance;
    }
    //取款的方法
    public void withdraw(double money){
    
    
        //t1和t2并发这个方法、。。。t1和t2是两个栈。两个栈操作堆中同一个对象
        //取款之前的余额
        double before = this.getBalance();
        //取款之后的余额
        double after = before - money;

        //在这里模拟一下网络延迟,100%会出问题
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        //更新余额
        //思考:t1执行到这里了,但还没来得及执行这行代码,t2线程进来来withdraw方法了。此时一定出问题。
        this.setBalance(after);
    }
}
public class AccountThread extends Thread{
    
    
    //两个线程必须共享同一个线程对象
    private Account act;

    public AccountThread() {
    
    
    }

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

    @Override
    public void run() {
    
    
        //run方法表示取款操作
        //假设取款5000
        double money = 5000;
        //取款
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "对账户:" + act.getActno()
                + "取款" + money + "成功,余额为:" + act.getBalance());
    }
}

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //创建账户对象(只创建1个)
        Account act = new Account("act-001",10000);
        //创建两个两个线程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        //设置name
        t1.setName("t1");
        t2.setName("t2");
        //启动线程取款
        t1.start();
        t2.start();
    }
}

5. synchronized解决线程安全问题。

synchronized有三种写法。
       第一种:同步代码块:灵活
            synchronized(线程共享对象){
                同步代码快
            }
       第二种:在实例方法上使用synchronized
            表示共享对象一定是this
            并且同步代码块是整个方法体
       第三种:在静态方法上使用synchronized
            表示找类
            类锁只有1吧
            就算创建了100个对象,那类锁也只有一把
       对象锁:1个对象1把锁,100个对象100把锁
       类锁:100个对象,也可能只有1把类锁。

银行账户
    使用线程同步机制
    线程同步机制语法格式
     synchronized (){
     //线程同步代码块
    }
     synchronized后面小括号中传输的这"数据"是非常关键的。
     这个数据必须是多线程共享数据,才能达到多线程排队

     ()中写什么??
        要看程序员要让哪些线程同步。。
            假设t1,t2,t3,t4,t5有5个线程
            你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办??
            一定要在()中写一个t1,t2,t3共享的对象。而这个对象对于t4,t5来说不是共享的
      这里共享的对象是:账户对象(Account对象)(注意:Account是类型  act是对象,我们通常说的Account对象是
      指的Account的一个实例)
      账户对象是共享的,那么this就是账户对象

因此我们对Account类做如下修改:

public class Account {
    
    
    private String actno;
    private double balance;

    Object obj = new Object();
    public Object get111() {
    
    
        return obj;
    }


    public Account() {
    
    
    }
    public Account(String actno, double balance) {
    
    
        this.actno = actno;
        this.balance = balance;
    }
    public String getActno() {
    
    
        return actno;
    }

    public void setActno(String actno) {
    
    
        this.actno = actno;
    }

    public double getBalance() {
    
    
        return balance;
    }

    public void setBalance(double balance) {
    
    
        this.balance = balance;
    }
    //取款的方法
    public void withdraw(double money){
    
    
        //以下这几行代码必须是线程排队的不能并发
        //一个线程把这里的代码执行结束,另一个线程才能进来
        /*
             在java语言中任何一个对象都有一把"锁"(只是有个标记)。
             以下代码的执行原理:
                1.假设t1和t2并发执行,开始执行以下代码的时候,肯定是一个先一个后,
                2.假设t1先执行了,遇到了synchronized,这个时候自动找“后面的共享对象”的对象锁
                找到之后,并占有这把锁,然后执行同步代码快中的程序,在程序执行的过程中已知占用着这把锁。
                直到代码执行结束,这把锁才会释放。
                3.假设t1已经占有这把锁,此时t2遇到了synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁
                被t1占有,t2只能在同步代码快外等待t1的结束,知道t1把同步代码快执行结束了,t1会归还这把锁,此时t2终于
                等到了这把锁,然后t2占有这把锁之后,进入同步代码块执行程序.

                这样就达到了线程排队执行的效果。
                这里需要注意的是:共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
         */
        synchronized (this){
    
    
            double before = this.getBalance();
            double after = before - money;
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            this.setBalance(after);
        }

    }
}

第二种方式

public class Account {
    
    
    private String actno;
    private double balance;
    Object obj = new Object();

    public Object get() {
    
    
        return obj;
    }


    public Account() {
    
    
    }
    public Account(String actno, double balance) {
    
    
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
    
    
        return actno;
    }

    public void setActno(String actno) {
    
    
        this.actno = actno;
    }

    public double getBalance() {
    
    
        return balance;
    }

    public void setBalance(double balance) {
    
    
        this.balance = balance;
    }
    //取款的方法
    /*
        在实例方法上可以使用synchronized吗??可以的。
            缺点1:synchronized出现在实例方法身上,一定锁的是this,不能是其他对象,
                所以这样不灵活,效率低
            缺点2:synchronized出现在实例方法身上,表示整个方法体都需要同步,可能会扩大同步的范围,导致程序的执行效率较低。
                所以这丫中方式不常用
            优点1: 代码少了,节俭了。
            总结:如果共享的对象就是this,并且需要同步的是整个方法体,建议使用这种方式
     */
    public synchronized void withdraw(double money){
    
    
        double before = this.getBalance();
        double after = before - money;
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        this.setBalance(after);
    }
}

6. synchronized的线程死锁,什么是死锁,死锁怎么写??

如下所示:具体执行过程如下:
第一:存在两个线程和两个对象(t1和t2,o1和o2)
第二:假设t1先抢到执行时间,t1线程执行时,会使用对象o1的锁,然后等待1秒。
第三:t2执行后,使用对象o2的锁,暂停1s。
第四:1s后t1开始执行,需要o2的锁,但是o2被t2线程占用,此时t1线程陷入阻塞。
第五:1s后t2开始执行,此时需要o1的锁,但是o1被t1线程占用,此时t2陷入阻塞。
第六:t1和t2线程都陷入阻塞,但是t1和t2都需要对象执行完毕,所以陷入死锁状态。

/*
    死锁代码要会写
 */
public class DeadLock {
    
    
    public static void main(String[] args) {
    
    
        Object o1 = new Object();
        Object o2 = new Object();

        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);
        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread{
    
    
    Object o1;
    Object o2;

    public MyThread1(Object o1, Object o2) {
    
    
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run() {
    
    
        synchronized (o1){
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            synchronized (o2){
    
    

            }
        }

    }
}
class MyThread2 extends Thread{
    
    
    Object o1;
    Object o2;

    public MyThread2(Object o1, Object o2) {
    
    
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run() {
    
    
        synchronized (o2){
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            synchronized (o1){
    
    
            }
        }

    }
}

五. 多线程的其他内容。

1. 守护线程 t.setDaemon(true);

 java语言中线程分为两大类:
                一类是:用户线程
                一类是:守护线程(后台线程)
                其中具有代表性的就是:垃圾回收线程(守护线程)

                守护线程的特点:
                    一把你的守护线程是一个死循环,所有的用户线程结束,守护线程自动结束。
                注意:主线程main方法是一个用户线程。

                守护线程需要用在什么地方??
                    每天00:00分时候系统自动备份。
                    这个需要使用到定时器,并且可以将定时器设置为守护线程。
                    已知在哪里看着每到00:00的时候就备份一份。所有的使用户线程如果结束了,
                    守护线程自动退出,没有必要进行数据备份了。
/*
    守护线程
        void setDaemon(boolean on)
          将该线程标记为守护线程或用户线程。
 */
public class ThreadTest14 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new BakDataThread();
        t.setName("备份数据的线程");
        //启动线程之前,将线程设置为守护线程
        t.setDaemon(true);
        t.start();

        //主线程:用户线程
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "---->" + i);
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}
class BakDataThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        int i = 0;
        while (true){
    
    
            System.out.println(Thread.currentThread().getName() + "---->" + (++i));
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

2. 定时器 Timer

 定时器的作用:间隔特定的时间去执行特定的程序。
    实现定时器的方法:
      法1: 可以使用sleep方法,睡眠,设置睡眠时间,每到特定的时间点醒来,执行任务。这种方式
                是最原始的定时器(比较low)
      法2: 在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用用。不过这种方式
                 在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
      法3: 使用Spring框架中的SpringTask框架,(这种方式较多使用)
/*
    使用定时器指定定时任务

    void schedule(TimerTask task, Date firstTime, long period)
          安排指定的任务在指定的时间开始进行重复的固定延迟执行。
 */
public class TimerTest01 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //创建定时器对象
        Timer timer = new Timer();
        //Timer timer = new Timer(true);//将timer设置为守护线程

        //指定定时任务timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-11-19 16:51:00");
        timer.schedule(new LogTimerTask(),firstTime,1000*10);
    }
}
//由于TimerTask是一个抽象类所以需要编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{
    
    
    @Override
    public void run() {
    
    
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + "完成了一次数据备份!!");
    }
}

3. 生产者和消费者模式

关于Object类中的wait方法和notify方法。(生产者和消费者模式)
   第一:wait方法和notify方法不是线程对象的方法,是Java中任何一个java对象都有的方法,
         这两个方法都是Object类中自带的。
         wait方法和notify方法不是通过线程对象调用,不是这样的:t.wait(),t.notify()
   第二:wait方法的作用??
          Object o = new Object()
          o.wait()
      表示:让o对象上活动的线程进入等待状态,并且是无期限等待,直到被唤醒为止。
            o.wait();方法的调用,会让“当前线程”(正在o对象上活动的线程),进入等待状态。

   第三:notify()方法的作用??
            o.notify()表示:
               唤醒正在o对象上等待的线程
            o.notifyAll()表示:
               唤醒正在o对象上等待的所有线程。
/*
    1. 使用wait方法和notify实现“生产者和消费者模式”
    2. 什么是“生产者和消费者模式”
        生产线程负责生产,消费线程负责消费
        生产线程和消费线程要达到平衡
    3. wait方法和notify方法不是线程对象的方法,是Java中任何一个java对象都有的方法,

    4. wait方法和notify方法建立在线程同步的基础之上。应为多线程要同时操作一个仓库,有线程安全问题。

    5. wait方法的作用:o.wait()让正在o对象上活动的线程t进入等待转台,并且释放掉t线程之前占有的o对象的锁

    6. notify 方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象之前占有的锁

    7. 模拟这样一个需求
        仓库采用List集合
        List集合中假设只能存储一个元素
        如果List集合中的元素个数为0表示仓库空了,
        保证List集合永远都是最多只能存储1个元素

        必须做到这样的效果:生产1个消费1个。
 */
public class ThreadTest16 {
    
    
    public static void main(String[] args) {
    
    
        //创建1个仓库对象,共享的
        List list = new ArrayList();
        //创建两个线程对象
        //生产者线程
        Thread t1 = new Thread(new Producer(list));
        //消费者线程
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程");
        t2.setName("消费者线程");

        t1.start();
        t2.start();
    }
}
//生产线程
class Producer implements Runnable{
    
    
    private List list;

    public Producer(List list) {
    
    
        this.list = list;
    }

    @Override
    public void run() {
    
    
        //一直生产
        while (true){
    
    
            synchronized (list){
    
    
                if (this.list.size() > 0){
    
    
                    try {
    
    
                        //当前线程进入等待状态,并且释放list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "----->" + obj);
                //唤醒消费者进行消费
                list.notifyAll();
            }
        }
    }
}
//消费线程
class Consumer implements Runnable{
    
    
    List list;
    public Consumer(List list) {
    
    
        this.list = list;
    }

    @Override
    public void run() {
    
    
        //已知消费
        while (true){
    
    
            synchronized (list){
    
    
                if (list.size() == 0){
    
    
                    try {
    
    
                        list.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                //程序能够执行到此处说明仓库中有数据,进行消费
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "---->" + obj);
                //唤醒生产者
                list.notifyAll();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_28384023/article/details/109824999