JAVA学习8-线程安全、synchronized、反射机制、生产者消费者模式

71.线程安全

即多线程并发环境下,线程的安全问题;

多线程并发对同一个账号取款;

触发线程安全问题需要的3个条件:

        ①存在多线程并发;

        ②共享数据;

        ③对共享数据进行修改;

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

可以用线程排队(不能并发)解决线程安全问题,这种机制被称为线程同步机制;

线程同步(线程排队)就会牺牲一部分效率,但是效率的前提是数据安全,没有数据安全,效率无从谈起;

线程之间各自独立执行,不需要等待其他线程,这种编程模型叫做异步编程模型,也叫多线程并发,效率较高;

线程排队执行,线程之间发生了等待关系,这就是同步编程模型,效率较低;

多线程并发及其解决Demo:https://blog.csdn.net/y_w_x_k/article/details/124233368

JAVA三大变量:

        局部变量         在栈中

        实例变量         在堆中

        静态变量         在静态方法区

局部变量永远是线程安全的,因为局部变量在栈中,一个线程一个栈,线程之间不存在共享一个栈,局部变量永远不共享;

实例变量在堆中,堆只有一个;

静态变量在静态方法区中,静态方法区只有一个;

堆和方法区都是多线程共享的,所以可能存在线程安全问题;

常量也不存在线程安全问题,因为常量不可修改;

字符串操作中,如果是局部变量的话,建议使用StringBuilder,因为局部变量不存在线程安全问题,用StringBuffer每次需要经过线程池,执行变慢了;

ArrayList是非线程安全的;

Vector是线程安全的;

HashMap,HashSet是非线程安全的;

HashTable是线程安全的;

synchronized的三种写法:

        ①同步代码块

        synchronized (this) {
            double bal_before = this.getBalance();
            double bal_after = bal_before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            this.setBalance(bal_after);
        }

        ②用synchronized修饰方法体

public synchronized void moneyOut(double money) {
    ...
}

        ③在静态方法上使用synchronized

        表示找类锁,类锁永远只有1把,就算创建了100个对象,类锁也只有1把;

对象锁:100个对象100把锁;

类锁:100个对象,也可能只有1把类锁;

类锁详细demo:https://blog.csdn.net/y_w_x_k/article/details/124233368

72.死锁

程序出现死锁将会不出现异常,也不会出现错误,程序一直僵持在那里,这种错误最难调试;

public class ThreadRunable implements Runnable {
    Object o1;
    Object o2;

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

    @Override
    public void run() {
        // TODO Auto-generated method stub
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            synchronized (o2) {
                System.out.println("线程1运行。。。");
            }
        }
    }
}


public class ThreadRunable2 implements Runnable {
    Object o1;
    Object o2;

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

    @Override
    public void run() {
        // TODO Auto-generated method stub
        synchronized (o2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            synchronized (o1) {
                System.out.println("线程2运行。。。");
            }
        }
    }
}

public static void test30() {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new Thread(new ThreadRunable(o1, o2));
        Thread t2 = new Thread(new ThreadRunable2(o1, o2));

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

在开发中最好不要将synchronized嵌套使用,容易发生死锁,不容易排查错误;

73.开发中synchronized的使用

synchronized会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,再实在没有更好的解决方案的情况下才考虑用线程同步;

开发者中避免使用synchronized的方案:

第一种方案:尽量使用局部变量来代替实例变量和静态变量;

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象),对象不共享,就没有数据安全问题了;

如果不能使用局部变量,也不能创建多个对象,那只能使用synchronized了,使用线程同步机制;

74.守护线程

JAVA语言中,线程分为两大类:

        一类是:用户线程;

        一类是:守护线程(后台线程)

其中最有代表性的就是垃圾回收线程(守护线程);

守护线程的特点:

        守护线程一般是一个死循环,只要用户线程全部结束,守护线程也会自动结束;

主线程main方法是一个用户线程;

public class DefendThread implements Runnable {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 100; i++) {
            System.out.println("守护线程i--" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

public static void test31() {
        Thread t = new Thread(new DefendThread());
        // 设置守护线程,主线程结束,守护线程也自动结束
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程i--" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

75.定时器

java.util.timer定时器,可以实现定时器功能,但在实际开发中也很少用,因为很多高级框架都是支持定时任务的;

实际开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要通过简单的配置就能完成定时任务;

public class TimerTest extends TimerTask {

    @Override
    public void run() {
        // 编写定时任务
        // TODO Auto-generated method stub
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(new Date()) + ":备份数据");

    }
}

public static void test32() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstDate = sdf.parse("2022-04-18 12:51:00");

        Timer t = new Timer();
        //参数:定时任务,第一次执行时间,执行间隔
        t.schedule(new TimerTest(), firstDate, 5 * 1000);
    }

//匿名内部类方式
public static void test32() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstDate = sdf.parse("2022-04-18 12:51:00");

        Timer t = new Timer();
        t.schedule(new TimerTask() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                System.out.println(sdf.format(new Date()) + ":备份数据");
            }

        }, firstDate, 5 * 1000);
    }

76.JDK8实现线程新方式--Callable

这个方式实现的线程可以获取返回值;

继承Thread与实现Runale接口方式是没法获取线程返回值的,因为run方法返回void;

委派一个线程去执行一个任务,该任务执行完后可能会有一个执行结果,Callable实现线程可以获取这个执行结果;

import java.util.concurrent.FutureTask; //JUC下的,属于java的并发包,老JDK下没有这个包,新特性
public static void test33() throws Exception {
        FutureTask ft = new FutureTask<>(new Callable<>() {

            @Override
            public Object call() throws Exception {
                Thread.sleep(2000);
                // TODO Auto-generated method stub
                return 300;
            }

        });

        Thread t = new Thread(ft);

        t.start();
        // 这里是main方法,主线程中
        // 获取t线程的返回结果
        // get方法执行会导致当前线程阻塞
        Object o = ft.get();
        System.out.println(o);

        /**
         * main方法想要继续执行必须等待get方法的结果
         * get方法可能要很久,因为get方法是为了拿到另一个线程的执行结果
         * 另一个线程执行需要时间
         */
        System.out.println("主线程下一步...");
    }

这种方式的优点是可以得到线程的返回结果;

缺点是效率比较低,在获取t线程的执行结果的时候,会阻塞当前线程;

77.Object的wait和notify方法(生产者和消费者模式)

wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类自带的;

wait方法的作用:

        Object o=new Object();

        o.wait();

        让正在o对象上活动的t线程进入无限期等待状态,并且释放掉t线程之前占有的o对象的锁,直到被唤醒为止;

        o.wait()的调用会让正在o对象上活动的当前线程进入等待状态;

notify方法的作用:

        Object o=new Object();

        o.notify();

        唤醒正在o对象上等待的当前线程(当前线程),只是通知,不会释放o对象上之前占有的锁;

notifyAll:

        Object o=new Object();

        o.notify();

        唤醒o对象上等待的所有线程;

生产者和消费者模式是为了专门解决某个特定的需求的;

 生产者和消费者模式:

        生产线程负责生产,消费线程负责消费,生产线程和消费线程需要达到均衡,这是一种特殊的业务需求,在这种特殊情况下需要使用wait和notify方法;

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

Demo:

/**
 * 模拟一个场景
 * List集合中假设只能存储一个元素
 * 一个元素就表示仓库满了
 * 如果List集合中的元素个数是0,就表示仓库空了
 * 保证List集合中一直是最多存储一个元素
 * 生产一个消费一个
 */
public class ThreadTestWN {

    public static void doTest() {
        List list = new ArrayList();
        Thread t1 = new Thread(new Producer(list));
        Thread t2 = new Thread(new Customer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");

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

}

// 生产者线程
class Producer implements Runnable {
    List list;

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

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            synchronized (list) {
                // 给仓库对象list加锁
                if (list.size() > 0) {
                    try {
                        // 已经生产完毕,当前线程进入等待状态,并且释放Producer之前占有的list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                // 程序执行到这说明集合没有元素,进行生产动作
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Object o = new Object();
                this.list.add(o);
                // 生产完毕唤醒其他线程,此时生产者没有释放锁,程序继续执行while循环,此时已经生产完毕,if判断为true,执行wait方法,生产者进入等待状态;
                System.out.println(Thread.currentThread().getName() + "生产---->" + o);
                list.notify();
            }
        }
    }
}

// 消费者线程
class Customer implements Runnable {
    List list;

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

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            synchronized (list) {
                if (list.size() == 0) {
                    // 已经消费完毕,当前线程线程等待,释放Customer占有的list锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                // 程序执行到这里说明集合有元素,进行消费动作
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Object o = list.remove(0);
                // 消费完毕,此时List集合没有元素,唤醒o对象所有占用当前线程,此时消费者没有释放锁,程序会继续执行while循环,之后进入if判断,为true,再执行wait方法,消费者进入等待状态
                System.out.println(Thread.currentThread().getName() + "消费---->" + o);
                list.notify();
            }
        }
    }
}

78.反射机制

通过java语言中的反射机制可以操作字节码文件(可以读和修改字节码文件,class文件);

在java.lang.reflect包下;

反射机制相关的重要的类:

        java.lang.class:代表字节码文件,代表一个类型;

        java.lang.Reflect.Method:代表字节码中的方法字节码;

        java.lang.Reflect.Constructor:代表字节码中的构造方法字节码;

         java.lang.Reflect.Field:代表字节码中的属性字节码;

要操作一个类的字节码,需要首先获取到这个类的字节码,有3中方式获取这个类的字节码:

方式一:Class c=Class.forName("java.lang.String"),括号内是完成类名带包名

方式二:  Class c=引用.getClass();getClass是Object类的方法,返回此 Object的运行时类

方式三:Class c=类名.class;JAVA语言中任何一种类型,包括基本数据类型,都有class属性

public static void test35() {
        //forName方式
        /**
         * 静态方法
         * 方法在参数是一个字符串
         * 字符串需要的是一个完整类名
         * 完整类名必须带有包名,java.lang包也不能省略
         */
        Class c1 = null;
        Class c4 = null;
        try {
            c1 = Class.forName("java.lang.String"); // c1代表String.class文件,或者说c1代表String类型
            Class c2 = Class.forName("java.lang.Integer"); // c2代表Integer.class文件,或者说c1代表Integer类型
            Class c3 = Class.forName("java.lang.System"); // c3代表System.class文件,或者说c1代表System类型
            c4 = Class.forName("java.util.Date"); // c4代表Date.class文件,或者说c1代表Date类型
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //getClass方式
        String s = "accc1";
        Class s1 = s.getClass();
        System.out.println(s1 == c1); // true

        Date d = new Date();
        Class d1 = d.getClass();
        System.out.println(d1 == c4); // true

        Class s2 = String.class;
        Class d2 = Date.class;
        System.out.println(s2 == s1);   // true
        System.out.println(d2 == d1);   // true
    }

 后期需要学习的高级框架,例如Spring...都用到了反射机制,所以反射机制还是重要的;学习反射机制有利于理解剖析框架底层的源代码;

Class.forName原理:

public class Movie {

    public Movie() {
        System.out.println("movie无参构造");
    }

    // 静态代码块,类加载的时候执行,并且只执行一次
    //如果只希望一个类的静态代码块执行,其他代码不执行,可以使用Class.forName("完整类名")
    //Class.forName这个方法的执行会导致类加载
    //类加载后,静态代码块执行
    static {
        System.out.println("Movie静态代码块");
    }
}

public static void test38() {
        try {
            Class.forName("ReflectTest.Movie");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
}

 79.反射机制创建对象

public static void test36() {
        try {
            Class mc = Class.forName("ReflectTest.Movie");
            // newInstance方法会调用Movie类的无参构造方法,完成对象的创建,必须保证Movie类的无参构造是存在的
            Object m = mc.newInstance();
            System.out.println(m);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
}

反射机制的好处

// java代码写一遍,在不改变源代码的基础上,可以做到不同对象的实例化,非常灵活
    // 符合OCP开闭原则,对扩展开放,对修改关闭
    public static void test37() throws Exception {

        // 这个方法是写死的,不够灵活
        // Movie m = new Movie();

        // 以下代码是灵活的,修改配置文件,可以创建出不同的实例对象
        FileReader fr = new FileReader("C:\\Users\\Administrator\\Desktop\\临时\\aa.txt");
        Properties pro = new Properties();
        pro.load(fr);
        //或者直接以流的形式返回
       //pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("ReflectTest/aa.txt"));

        String className = pro.getProperty("className");

        Class cls = Class.forName(className);
        Object obj = cls.newInstance();
        System.out.println(obj);
        fr.close();
    }

80.通用方式获取路径

使用此方式获取路径即使代码位置变更仍然能获取路径;

通用方式的前提是文件在类路径下;

凡是在src文件夹下的文件都是类路径下,src是类的根路径;

public static void test39() {
        /**
         * currentThread()获取当前线程
         * getContextClassLoader():线程对象的方法,获取当前线程的类加载器对象
         * getResource():获取资源,这是类加载器对象的方法,当前线程的类加载器对象默认从类的根路径下加载资源,从src目录作为起点开始
         * 这种方式获取绝对路径是通用的
         */
        String path = Thread.currentThread().getContextClassLoader().getResource("ReflectTest/aa.txt").getPath();

        // 采用以上的代码可以拿到一个文件的绝对路径
        System.out.println(path);
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

y_w_x_k

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值