java学习笔记第七周(二)

目录

一、多线程

1、线程的基础知识

1.1 什么是进程?什么是线程?

1.2 进程和线程的关系

1.3 多线程并发的理解

2、实现多线程

2.1 第一种方式:

2.2 第二种方式

2.3 第三种方式

3、线程的生命周期

4、获取线程名字

5、线程的sleep方法

5.1 关于sleep方法的面试题

5.2 终止线程的睡眠

6、线程的调度(了解)

6.1 常见线程调度模型

6.2 java中提供给的与线程调度有关的方法

7、同步编程模型和异步编程模型

8、线程安全(重点)

8.1 存在的安全问题

8.2 解决线程安全问题

8.3 模拟账户取款安全问题

8.4 扩大同步范围

8.5 三大变量的安全问题

8.6 synchronized面试题1

8.7 synchronized面试题2

8.8 死锁

8.9 以后开发中怎么解决线程安全问题

9、守护线程

9.1 守护线程概念:

9.2 守护线程实现

10、定时器

11、Object类中的wait和notify方法

11.1 概述

11.2 生产者和消费者模式理解

11.3 wait和notify代码


一、多线程

1、线程的基础知识

1.1 什么是进程?什么是线程?

进程是一个应用程序(一个进程是一个软件)

线程是一个进程中的执行场景/执行单元。

一个进程可以启动多个线程。

1.2 进程和线程的关系

举例:

阿里巴巴:进程

        马云:阿里巴巴的一个线程

童文红:阿里巴巴的一个线程

京东:进程

        刘强东:京东的一个线程

进程可以看作是现实生活当中的公司

线程可以看作是公司当中的某个公司

注意:

线程A和线程B的内存独立不共享。(阿里和京东资源不共享)

在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈

java中之所以有多线程机制,目的就是为了提高程序的处理效率

思考:在使用了多线程之后,main方法结束,只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈

1.3 多线程并发的理解

2、实现多线程

java语言中,实现线程有两种方式。

java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就可以

2.1 第一种方式:

编写一个类,直接继承java.lang.Thread,重写run方法

package 多线程;

/**
 * 实现线程的第一种方式
 * 编写一个类,继承Thread
 *
 *
 * 注意:
 * 方法体当中的代码永远都是自上而下执行的
 *
 *
 * 以下程序的输出的结果有以下的特点:
 * 有先有后
 * 有多又少
 */

public class ThreadTest02 {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主栈里运行
        //新建一个分支线程对象
        MyThread myThread=new MyThread();

        //启动线程

        //myThread.run();  //不会启动线程,不会分配新的分支栈,因此这种方式还是在主栈中,就是单线程

        //调用start方法,start()方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间这段代码完成之后,瞬间就结束了。
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟出来,start()方法就结束了。线程就启动成功了。
        //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的
        myThread.start(); //这行代码不结束,下面的代码不会执行,但这行代码执行很快就结束了,

        //这里的代码还是主线程中
        for(int i=0;i<100;i++){
            System.out.println("主线程---"+i);
        }
    }
}

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

可以看见,使用start()方法,主线程和分支线程并发进行,互不干扰

package 多线程;

/**
 * 实现线程的第一种方式
 * 编写一个类,继承Thread
 *
 *
 * 注意:
 * 方法体当中的代码永远都是自上而下执行的
 *
 *
 * 以下程序的输出的结果有以下的特点:
 * 有先有后
 * 有多又少
 */

public class ThreadTest02 {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主栈里运行
        //新建一个分支线程对象
        MyThread myThread=new MyThread();

        //启动线程

        myThread.run();  //不会启动线程,不会分配新的分支栈,因此这种方式还是在主栈中,就是单线程

        //调用start方法,start()方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间这段代码完成之后,瞬间就结束了。
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟出来,start()方法就结束了。线程就启动成功了。
        //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的
        //myThread.start(); //这行代码不结束,下面的代码不会执行,但这行代码执行很快就结束了,

        //这里的代码还是主线程中
        for(int i=0;i<100;i++){
            System.out.println("主线程---"+i);
        }
    }
}

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

 可以看见,使用run();方法是没有启动新的线程的,因此不是多线程,只是一个方法的调用,先执行完分支线程的输出,然后才执行主线程的输出

2.2 第二种方式

编写一个类,实现java.lang.Runnable接口,实现run方法

package 多线程;

public class ThreadTest03 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        //MyRunnable r=new MyRunnable();
        //将可运行的对象封装成一个线程对象
        //Thread t=new Thread(r);
        //合并代码
        Thread t=new Thread(new MyThread());

        //启动线程
        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);
        }
    }
}

注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承别的类(Java是单继承),更加灵活。

附:通过匿名内部类实现

package 多线程;

public class ThreadTest04 {
    public static void main(String[] args) {
        //采用匿名内部类的方式
        //这里是通过一个没有名字的类,new出来的对象
        /**注意:!!
         * 这里参数放new接口实际上是new[匿名] implements Runnable,并不是真的用接口new对象
         */
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("分支线程---" + i);
                }
            }
        });
        //启动线程
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程---" + i);
        }
    }
}

2.3 第三种方式

实现Callable接口。(JDK8新特性)

这种方式实现的线程可以获取线程的返回值,之前的两种方式是不可以获取线程返回值的,因为run方法返回void。

系统委派一个线程去执行任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么才能拿到这个执行结果呢?使用第三种方式:实现Callable接口方式。

package 多线程;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;//JUC包下的,属于java的并发包,老JDK种没有这个包

/**
 * 实现线程的第三种方法:
 * 实现Callable接口
 * 这种方式的优点:可以获取到线程的执行结果
 * 这种方式的缺点:效率低,在获取t线程执行结果的时候,当前线程受阻
 */
public class ThreadTest14 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //第一步:创建一个“未来任务类”对象
        //参数非常重要,需要给一个Callable接口实现类对象
        //Callable是接口,new的话要使用匿名内部类
        FutureTask task=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { //call()方法相当于方法,只不过有一个返回值
                System.out.println("call method begin");
                Thread.sleep(1000*10);
                System.out.println("call method end");
                int a=100;
                int b=100;
                return a+b;//自动装箱,返回一个Object对象,因此300结果自动变成Integer。
            }
        });

        //创建线程对象
        Thread t=new Thread(task);
        //启动线程
        t.start();

        //这里是main方法,这是在主线程中,那么在主线程中,怎么获取t线程中的返回结果?
        //使用get()方法会导致"当前线程阻塞"
        Object obj=task.get();
        System.out.println("线程执行结果"+obj);
        //main方法这里的而程序要想执行必须等待get()方法的结束
        //而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果,但是另一个线程的执行是需要时间的
        System.out.println("Java");
    }
}

3、线程的生命周期

新建状态,就绪状态,运行状态,阻塞状态,死亡状态

4、获取线程名字

package 多线程;

/**
 * 1、怎么获取当前线程对象?
 * Thread t=Thread.currentThread();
 * 返回值t就是当前线程
 *
 * 2、获得线程对象名字。
 * 线程对象.getName("名字");
 *
 * 3、修改线程对象名字
 * 线程对象.setName("名字");
 *
 * 4、注意:当线程没有设置名字的时候,默认名字是Thread-0,Thread-1,Thread-2
 */
public class ThreadTest05 {
    public void dosome(){
        Thread.currentThread().getName();
    }
    public static void main(String[] args) {
        //代码出现在main方法当中,所以当前线程就是主线程
        Thread currentThread=Thread.currentThread();//这是个静态方法
        System.out.println(currentThread.getName());
        System.out.println("----"+currentThread.getName());//

        //创建线程对象
        MyThread2 t=new MyThread2();
        MyThread2 t2=new MyThread2();
        //获取线程名字
        System.out.println(t.getName());
        //设置线程的名字
        t.setName("tttt");
        String tName=t.getName();
        System.out.println(tName);
        t2.setName("qqqq");

        //启动线程
        t.start();//t线程启动
        t2.start();
    }
}
class MyThread2 extends Thread{
    public void run(){
        for(int i=0;i<100;i++){
            //currentThread就是当前线程对象,当前线程是谁启动的,当前线程的名字就是谁的
            //当t1线程执行run方法,那么这个线程就是t1,名字为tttt
            //当t2线程执行run方法,那么这个线程就是t2,名字为qqqq
            Thread currentThread=Thread.currentThread();
            System.out.println(currentThread.getName()+i);//这个方法更加常用,下面两个方法有局限性
            //System.out.println(this.getName()+i);
            //System.out.println(super.getName()+i);
        }
    }
}

5、线程的sleep方法

package 多线程;

/**
 * static void sleep(long millis)
 * 1、静态方法:Thread.sleep(1000);
 * 2、参数是毫秒
 * 3、作用:让当前线程进入休眠,进入”阻塞状态“,放弃占有CPU时间片,让给其他线程使用。
 *        这行代码出现在A线程种,A线程就会进入休眠
 * 4、Thread.sleep()方法,可以做到这种效果:
 *    间隔特定的时间,去执行一段 特点的代码,每隔多久执行一次
 */

public class ThreadTest06 {
    public static void main(String[] args) {
        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();
            }
        }
    }
}

5.1 关于sleep方法的面试题

package 多线程;

/**
 * 关于Thread.sleep()方法的一个面试题
 *
 * 注意:sleep方法出现在哪里哪里睡,
 */

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

        try {
            //问题:这行代码会让线程t进入休眠状态吗?
            t.sleep(1000*5);  //在执行的时候还是会转换为Thread.sleep(1000*5)
                                    // 这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠
                                    //这行代码出现在main方法种,main线程睡眠
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("hello world");
    }
}
class MyThread3 extends Thread{
    public void run(){
        for(int i=0;i<1000;i++){
            //如果在这里使用sleep方法,那么就是该分支线程睡眠
            System.out.println(Thread.currentThread().getName());
        }
    }
}

5.2 终止线程的睡眠

方法一:通过interrupt()方法

package 多线程;

/**
 * sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程?
 * 注意:这个不是终断线程的执行,是终断线程的睡眠
 */
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t=new Thread(new MyRunnable2());
        t.setName("ttt");
        t.start();

        //希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了)
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终断t线程的睡眠(这种终端睡眠的方式依靠了java的异常处理机制,会在sleep出报出异常来停止)
        t.interrupt(); //干扰,一盆冷水过去!

    }
}
class MyRunnable2 implements Runnable{
    //重点:run方法当中的异常不能throw,只能try catch,因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多异常。
    @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");
        System.out.println(Thread.currentThread().getName());

        //其他方法可以throws(这些不是继承的父类方法,而是自己的方法)
        /*
        public void doOther(){
        }
         */
    }
}

方法二:强行终止线程进行(合理方法)

package 多线程;

public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunnable4 r=new MyRunnable4();
        Thread t=new Thread(r);
        t.setName("t");
        t.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止线程,你想什么时候终止t的执行,那么你把标记改为false就结束了
        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;
            }
        }
    }
}

6、线程的调度(了解)

6.1 常见线程调度模型

常见的线程调度模型有两种:抢占式调度线程和均分式调度模型

抢占式调度线程:哪个线程的优先级比较高,抢到CPU时间片的概率就高一些。java中采用的就是抢占式调度线程

均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。

6.2 java中提供给的与线程调度有关的方法

实例方法:

1、void setPriority(int newPriority) //设置线程的优先级

2、int getPriority() //获取线程优先级

最高优先级是10,最低优先级是1,默认优先级是5。

package 多线程;

public class ThreadTest11 {
    public static void main(String[] args) {
        //设置主线程的优先级为1
        Thread.currentThread().setPriority(1);

        /*System.out.println("最高优先级"+Thread.MAX_PRIORITY);
        System.out.println("最低优先级"+Thread.MIN_PRIORITY);
        System.out.println("默认优先级"+Thread.NORM_PRIORITY);

        //获取当前线程对象,获取当前线程的优先级
        Thread currentThread=Thread.currentThread();
        //main线程的默认优先级是:5
        System.out.println(currentThread.getName()+"线程的默认优先级是"+currentThread.getPriority());
*/
        Thread t=new Thread(new MyRunnable5());
        t.setPriority(10);
        t.setName("t");
        t.start();

        //优先级较高的,只是抢到CPU的时间片相对多一些(大概率方向偏向于优先级比较高的)
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
class MyRunnable5 implements Runnable{
    @Override
    public void run() {
        //获取线程优先级,默认为5
       //System.out.println(Thread.currentThread().getName()+"线程的默认优先级"+Thread.currentThread().getPriority());
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

3、void join() 合并线程

package 多线程;

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();

        //合并线程,t合并到当前线程中,当前线程受堵塞
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

静态方法:

1、static void yield() //让位方法。暂停当前正在执行的线程对象,并且执行其他线程。yiled()方法不是阻塞方法,让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从"运行状态"回到就绪状态。

package 多线程;

/**
 * 让位,让当前线程暂停,回到就绪状态,让给其他线程
 * 静态方法:Thread.yield();
 */
public class ThreadTest12 {
    public static void main(String[] args) {
        Thread t=new Thread(new MyRunnable6());
        t.setName("t");
        t.start();
        for(int i=0;i<=100;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
class MyRunnable6 implements Runnable{
    @Override
    public void run() {
        for(int i=1;i<=100;i++){
            //每10个让位一次
            if(i%10==0){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

7、同步编程模型和异步编程模型

8、线程安全(重点)

8.1 存在的安全问题

 

 从上图思考,什么时候数据在多线程并发的环境下会存在安全问题呢?

条件1:多线程并发。

条件2:有共享数据。

条件3:共享数据有修改的行为

满足以上3个条件以后,就会存在线程安全问题

8.2 解决线程安全问题

8.3 模拟账户取款安全问题

Test类:

package 线程安全测试;

public class Test {
    public static void main(String[] args) {
        //创建账户对象
        Account act=new Account("act001",10000);
        //创建两个线程
        Thread t1=new AccountThread(act);
        Thread t2=new AccountThread(act);

        //设置name
        t1.setName("t1");
        t2.setName("t2");
        //启动线程取款
        t1.start();
        t2.start();
    }
}

Account类:

package 线程安全测试;

/**
 * 银行账号
 */
public class Account {
    //账号
    private String actno;
    //余额
    private double balance;

    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;
        //更新余额
       //当t1执行到这里了,但是还没有来得及执行这行代码,t2线程进来withdraw方法了,此时一定出问题

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

        this.setBalance(after);
   }
}

AccountThread类:

package 线程安全测试;

public class AccountThread extends Thread{
    //两个线程必须共享同一个账户对象
    private Account act;

    //通过构造方法传递
    public AccountThread(Account act){
        this.act=act;
    }

    public void run(){
        //run方法的执行表示取款操作
        //假设取款5000
        double money=5000;

        //多线程并发实现取款
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额"+act.getBalance());
    }
}

结果:

解决方法:增加synchronized () 方法

package 线程安全测试.解决方案;

/**
 * 银行账号
 * 不适用线程同步机制,多线程对同一个用户进行取款,出现线程安全问题
 */
public class Account {
    //账号
    private String actno;
    //余额
    private double balance;

    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) {
        //以下这几行代码必须是线程排队的,不能并发
        //一个线程吧这里的代码全部执行结束之后,另一个线程才能进来
        /**
         * 线程同步机制的语法:
         *  synchronized () {
         *      //线程同步代码块
         *  }
         *
         *  synchronized后面小括号中传的这个数据是十分重要的。
         *  这个数据必须是多线程共享的数据,才能达到多线程排队
         *
         *
         *  ()中写什么?
         *      那么看你想让哪些线程同步,假设有t1,t2,t3,t4,t5,有5个线程
         *      如果你只希望t1,t2,t3排队,t4,t5不需要排队,你一定要在()中写一个t1,t2,t3共享的对象,而t4和t5不共享
         */

        /*这里的共享对象是:账户对象。
        * 账户对象是共享的,那么this就是账户对象
        * 不一定是this,只要是多线程共享的那个对象
        */

        /*
        在java中,任何一个对象都有一把锁,其实这把锁就是标记(只是把他叫作锁)
        100个对象,100把锁

        以下代码的执行原理
        1、假设t1和t2线程并发,开始执行以下代码的时候,肯定一个先一个后。
        2、假设t1先执行了,遇到synchronized,这个时候自动找”后面共享对象“的对象锁,
        找到之后,并占有这把锁的,直到同步代码块代码结束,这把锁才会释放
        3、假设t1已经占有这把锁,此时t2也遇到了synchronized关键字,也去占有后面共享对象的这把锁,结这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
        直到t1把同步代码结束了,t1会归还这把锁,此时t2才可以占有这把锁,进入同步代码块执行程序

        需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的
         */



        //synchronized (this) {//最好选这个,因为在这个例子中,别的线程操作的是别的账户,只要不是同时对同一个账户同时取款的都不需要等待
            //this指向的Account只有一个,那么Account里面的所有成员变量都只有一个,因此都可以作为参数
        //synchronized (actno) {//可以使用

        //Object obj2=new Object();//不能使用,注意这里是局部变量,每一个对象new的时候都会创建一个新的obj2对象,不是共享对象
        //synchronized (obj2){

        //synchronized ("abc"){//可以使用,因为"abc"在字符串常量池中,而字符串常量池是共享的(此时所有线程都会同步)
        synchronized (this) {
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            this.setBalance(after);
        }
    }
}

8.4 扩大同步范围

package 线程安全测试.解决方案;

public class AccountThread extends Thread{
    //两个线程必须共享同一个账户对象
    private Account act;

    //通过构造方法传递
    public AccountThread(Account act){
        this.act=act;
    }

    public void run(){
        //run方法的执行表示取款操作
        //假设取款5000
        double money=5000;

        //多线程并发实现取款
        synchronized (act) {//这种方法也可以,只不过扩大了同步范围,效率更低。
        //synchronized (this){} 这里的this是AccountThread对象,这个对象不共享
            act.withdraw(money);
        }
        System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额"+act.getBalance());
    }
}

8.5 三大变量的安全问题

synchronized重点:
synchronized重点(转载)https://blog.csdn.net/qq_41279172/article/details/104308214

8.6 synchronized面试题1

package 线程安全测试.synchronized面试题目;

/**
 * doOther方法执行的时候需要等待doSome结束吗?
 *  不需要,因为doOther()方法没有使用synchronized,没有被锁
 */

public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc=new MyClass();
        Thread t1=new MyThread(mc);
        Thread t2=new MyThread(mc);
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000);//这个睡眠的作用是保证t1线程先进行
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc=mc;
    }
    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 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    //如果这里改成public synchronized void doOther{},执行doOther时就需要
        public void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }

    }


8.7 synchronized面试题2

package 线程安全测试.synchronized面试题2.synchronized面试题目;

/**
 * doOther方法执行的时候需要等待doSome结束吗?
 * 需要,因为静态方法是类锁,类锁不管创建了几个对象,类锁只有一把。
 */

public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc=new MyClass();
        Thread t1=new MyThread(mc);
        Thread t2=new MyThread(mc);
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000);//这个睡眠的作用是保证t1线程先进行
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc=mc;
    }
    public void run(){
        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass{
    public synchronized static void doSome() {
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    //如果这里改成public synchronized void doOther{},执行doOther时就需要
        public void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }

    }

8.8 死锁

package 线程安全测试.死锁;

/**
 * 死锁代码要会写,一般面试官会要求你写
 * 只有会写,才能在以后的开放中避免这种现象
 * 死锁很难调试
 */

/**
 * o1锁了以后睡了,o2锁了也睡了,醒来以后互相卡住,无法继续
 * 因此 synchronized使用时不要嵌套使用
 */
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;
    }
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } 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;
    }
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
            }
        }
    }
}

8.9 以后开发中怎么解决线程安全问题

9、守护线程

9.1 守护线程概念:

9.2 守护线程实现

package 守护线程;

public class ThreadTest1 {
    public static void main(String[] args) {
        Thread t=new BakDateThread();
        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 BakDateThread extends Thread{
    public void run(){
        int i=0;
        while(true){
            System.out.println(Thread.currentThread().getName()+"-->"+(++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }


    }
}

死循环自动终止了 

10、定时器

package 计时器;

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

/**
 * 使用定时器指定定时任务
 */
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer timer = new Timer();
        //Timer timer=new Timer(true); //将计数器作为守护线程的方式

        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime =sdf.parse("2022-02-28 19:33:00");
        timer.schedule(new LogTimeTask(),firstTime,1000*10);

         //也可以使用匿名内部类实例化TimerTask抽象方法
        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                
            }
        },firstTime,1000*10);
    }
}
    }
}

//编写一个定时任务类
//假设这是ige记录日志的定时任务
//实例化抽象类TimeTask通过子类继承,然后实例化子类
class LogTimeTask 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+"成功完成了一次数据备份");
    }
}

 

11、Object类中的wait和notify方法

11.1 概述

11.2 生产者和消费者模式理解

11.3 wait和notify代码

要多理解此处代码和原理

package 多线程;

import java.util.ArrayList;
import java.util.List;

/**
 * 1、使用wait方法和notify方法实现“生产者和消费者模式”
 *
 * 2、什么是’生产者和消费者模式“?
 * 生产线程负责生产,消费线程负责消费。
 * 线程生产和消费线程要达到均衡。
 * 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
 *
 * 3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
 *
 * 4、wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题
 *
 * 5、wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放t线程之前占有的o对象的锁
 *
 * 6、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,
 *
 * 7、模拟这样一个需求:
 *     仓库我们采用List集合。
 *     1个元素就表示仓库满了。
 *     如果List集合中元素个数为0,就表示仓库空了。
 *     保证List集合中永远都是最多存储1个元素
 *     必须做到这种效果:生产1个消费一个
 *
 */
public class ThreadTest15 {
    public static void main(String[] args) {
        //创建一个仓库对象,共享的
        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) {
            //给仓库对象list加锁
            synchronized (list) {
                if (list.size() > 0) {//大于0,说明仓库中已经又1个元素
                    //当前线程进入等待状态,并且释放Producer之前占有的list集合的锁
                    try {
                        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{
    private List list;
    public Consumer(List list){
        this.list=list;
    }
    @Override
    public void run() {
        //一直消费
        while(true){
            synchronized (list){
                if(list.size()==0){
                    //仓库已经空了,消费者线程等待,释放掉list集合的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //程序能执行到此处说明仓库中有数据,进入消费
                Object obj=list.remove(0);
                System.out.println(Thread.currentThread().getName()+"-->"+obj);
                //唤醒生产者生产
                list.notifyAll();
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值