Java(day17)同步锁、注解、单例设计模式

本文详细介绍了Java中的同步锁机制,通过关键字`synchronized`实现线程安全,避免多线程环境下共享数据的并发问题。同时,文章还探讨了Java注解的使用,包括JDK内置注解和自定义注解,以及元注解的作用。此外,文章讲解了单例设计模式,强调其内存优化和资源控制的优点,并给出了不同实现方式的示例。

同步锁:解决线程安全问题

我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件:

在多线程程序中 + 有共享数据 + 多条语句操作共享数据

多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务)
所以可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题
那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行


同步和异步

同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。坏处就是效率降低,好处是保证安全。

异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。坏处是有安全隐患,好处是效率高。

锁:关键字synchronized

前提:

前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)

前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

同步代码块

synchronized(唯一的锁对象){会出现安全隐患的所有代码}

package cn.tedu.review;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestRunnable {
    public static void main(String[] args) {
        TicketsRunnable t = new TicketsRunnable();
//        Thread t1 = new Thread(t);
//        Thread t2 = new Thread(t);
//        Thread t3 = new Thread(t);
//        Thread t4 = new Thread(t);
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        /*Executors是用来辅助创建线程池的工具类对象,用newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象*/
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            //execute()是让线程池中的线程来执行业务
            pool.execute(t);//参数就是我们的业务

        }
    }
}
class TicketsRunnable implements Runnable{
    int tickets=100;
    //Object o=new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (TicketsRunnable.class) {
                if(tickets>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {

                    }
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                if (tickets <= 0) {
                    break;
                }
            }
        }
    }
}

注解

JDK注解:

@override:用来标记重写的方法

元注解:用来描述注解的注解

@Target():注解用在哪里:类上,方法上、属性上  

ElementType.TYPE 应用于类的元素

ElementType.METHOD 应用于方法上

ElementType.FIELD 应用于字段或属性(成员变量)

ElementType.ANNOTATION_TYPE 应用于注解类型

ElementType.CONSTRUCTOR 应用于构造函数

ElementType.LOCAL_VARIABLE 应用于局部变量

ElementType.PACKAGE 应用于包声明

ElementType.PARAMETER 应用于方法的参数

@Retention():注解的生命周期:源文件中、字节码文件中、运行中

SOURCE 在源文件中有效(即源文件保留)

CLASS 在class文件中有效(即class保留)

RUNTIME 在运行时有效(即运行时保留)

自定义注解:

package cn.tedu.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*本类用于完成自定义注解*/
public class TestAnnotation {
}
//2.通过@Target注解标记自定义注解的使用位置
/*3.通过元注解@Target规定自定义注解可以使用的位置
* 我们使用"ElementType.静态常量"的方式来指定自定义注解具体可以加在什么位置
* 而且,值可以写多个,格式:@Target({ElementType.XXX,ElementType.XXX}*/
@Target({ElementType.METHOD,ElementType.TYPE})//可以加在方法&类上
//3.通过@Retention注解标记自定义注解的生命周期
/*4.通过元注解@Retention规则自定义注解的生命周期
* 我们使用"RetentionPolicy.静态常量"的方式来指定自定义注解的生命周期
* 注意:值只能写一个:SOURCE CLASS RUNTIME 3选1 */
@Retention(RetentionPolicy.RUNTIME)//到运行时都有效
//1.定义自定义注解
/*1.首先注意:注解定义的语法与Java不同
* 2.定义自定义注解的格式:@interface 注解名*/
@interface Rice{
    //5.我们可以给注解进行功能增强--添加注解的属性
    /*5.注意:int age();不是方法的定义,而是给自定义注解添加了一个age属性*/
    //int age();//给自定义注解添加一个普通属性age,类型是int
    int age() default 0;//给自定义注解的普通属性赋予默认值0
    /*6.注解中还可以添加特殊属性value
    * 特殊属性的定义方式与普通属性一样,主要是使用方式不同
    * 注意:特殊属性的名字必须叫value,但是类型不做限制
    * 特殊属性也可以赋予默认值,格式与普通属性一样,不能简写
    * */
    //String value();//定义一个特殊属性value,类型是String
    String value() default "Lemon";//定义特殊属性并给特殊属性赋予默认值
}

//4.定义一个类用来测试自定义注解
//@Rice
class TestAnno{
    /*测试1:分别给TestAnno类 name属性 eat方法都添加Rice注解
    * 结论:属性上的注解报错了,说明自定义注解可以加在什么位置,由@Target决定*/
    //@Rice//报错了
    String name;
    /*测试2:当我们给Rice注解添加了一个age属性以后,@Rice注解使用时直接报错
    * 结论:当注解没有定义属性时,可以直接使用
    *      当注解定义了属性以后,必须给属性赋值,格式:@Rice(age = 10)*/
    /*测试3:给age属性赋予默认值以后,可以直接使用@Rice注解
    * 不需要给age属性赋值,因为age属性已经有默认值0了*/
    /*测试4:给Rice注解添加了特殊属性value以后,必须给属性赋值
    * 只不过特殊属性赋值时可以简写成 @Rice("Apple")
    * 测试5:如果特殊属性也赋予了默认值,那么可以直接使用这个注解
    * 如果要给注解的所有属性赋值,每条赋值都不能简写*/
    @Rice(age=10,value="orange")
    //@Rice("Apple")
    //@Rice(age = 10)
    //@Rice(10)//报错,不可以简写,普通属性没有这种格式
    public void eat(){
        System.out.println("干饭不积极,思想有问题");
    }
}

单例设计模式:生成一个对象

优点:可节约系统内存空间,控制资源的使用

1.饿汉式:不管你用不用这个类的对象,都会直接先创建一个对象

package cn.tedu.design;
//实现单例设计模式
public class Singleton1 {
    public static void main(String[] args) {
        MySingle s1 = MySingle.getSingle();

    }
}
//创建自己的单例程序
class MySingle{
    //1.创建私有的构造方法,防止外界调用创建本类对象
    private MySingle(){}
    //2.创建本类的对象,通过第3步的共有方法调用本类对象,从而创建唯一的对象
    private static MySingle single=new MySingle();
    //3.提供公共的访问方式
    //4.1为了main方法直接调用本方法,从而调用第二步创建的本类对象,需要将本方法设置为静态
    public static MySingle getSingle(){
        return single;
    }
}
package cn.tedu.reciew;
//复写单例设计模式
public class Singleton1 {
    public static void main(String[] args) {
        MySing s1=MySing.get();
        MySing s2=MySing.get();
        System.out.println(s1);
        System.out.println(s2);
    }
}
class MySing{
    private MySing(){}
    private static MySing Single=new MySing();
    public static MySing get(){
        return Single;
    }
}
运行结果:
cn.tedu.reciew.MySing@1540e19d
cn.tedu.reciew.MySing@1540e19d

 2.懒汉式:先不给创建这个类的对象,等你需要的时候再创建--延迟加载的思想

package cn.tedu.design;

public class Singleton2 {
    public static void main(String[] args) {
        MySingle2 s1=MySingle2.get();
        System.out.println(s1);
    }
}
class MySingle2{
    private MySingle2(){}
    //创建本类对象对应类型的引用类型变量,用来保存对象的地址值
    static MySingle2 single2;
    public static MySingle2 get(){

        return single2;
    }
}
运行结果:
null
package cn.tedu.design;

public class Singleton2 {
    public static void main(String[] args) {
        MySingle2 s1=MySingle2.get();
        System.out.println(s1);   
    }
}
class MySingle2{
    private MySingle2(){}
    //创建本类对象对应类型的引用类型变量,用来保存对象的地址值
    static MySingle2 single2;
    public static MySingle2 get(){
        single2=new MySingle2();
        return single2;
    }
}
运行对象:
cn.tedu.design.MySingle2@1540e19d

package cn.tedu.design;

public class Singleton2 {
    public static void main(String[] args) {
        MySingle2 s1=MySingle2.get();
        MySingle2 s2=MySingle2.get();
        System.out.println(s1);
        System.out.println(s2);
    }
}
class MySingle2{
    private MySingle2(){}
    //创建本类对象对应类型的引用类型变量,用来保存对象的地址值
    static MySingle2 single2;
    public static MySingle2 get(){
        if(single2==null){
            single2=new MySingle2();
        }
        return single2;
    }
}
运行结果:
cn.tedu.design.MySingle2@1540e19d
cn.tedu.design.MySingle2@1540e19d

由于我们只有唯一的对象Singleton2,且多条语句都操作了这个变量,所以需要加锁,保证同步排队的效果

package cn.tedu.design;

public class Singleton2 {
    public static void main(String[] args) {
        MySingle2 s1=MySingle2.get();
        MySingle2 s2=MySingle2.get();
        System.out.println(s1);
        System.out.println(s2);
    }
}
class MySingle2{
    private MySingle2(){}
     static Object o=new Object();
    //创建本类对象对应类型的引用类型变量,用来保存对象的地址值
    static MySingle2 single2;
    public static MySingle2 get(){
        synchronized (o){
            if(single2==null){
                single2=new MySingle2();
            }
            return single2;
        }
    }
}

单词:

synchronized 同步的
Executors 执行器

target 目标
element 元素
retention 保留

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值