线程
-
局部变量永远不会有线程安全问题,他不共享
-
synchronizzed出现在实例方法上,一定锁的是this,这种方式不灵活,另外,synchronized出现在实力方法上,整个方法体都被同步,会扩大同步的范围,导致程序效率降低
public synchronizzed void withdraw(){}//这种synchronizzed出现在了实力方法上,锁的一定是this
-
如果使用局部变量,建议使用stringBulier(非线程安全的),因为局部变量不存在线程安全问题,这样可以提高效率
总结:
synchronized有三种写法
-
第一种:同步代码块
灵活
synchronized(线程共享对象){
同步代码块
}
-
第二种:在市里方法上使用 synchronized
表示共享对象一定是this
并且同步代码块是整个方法体
-
第三种:在静态方法上使用 synchronized
表示找类锁
类锁永远只有一把
死锁
-
产生死锁:让两个线程共享一个资源,其中一个线程先锁住o1,再让该线程睡眠1秒,此时另外一个线程也快速锁住o2,也让他睡一秒,第一个线程睡眠结束发现o2已经被锁住,总结无法锁,同样另外一个线程也是,因此他们一直僵持着,程序无法结束,一直循环
//死锁 public class Thread05 { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); My my = new My(o1, o2); Me me = new Me(o1, o2); my.start(); me.start(); } } class My extends Thread{ Object o1; Object o2; public My(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 Me extends Thread{ Object o1; Object o2; public Me(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){ } } }
-
注意!:两把锁是嵌套的才会发生如果o1一直不能执行程序一直不结束
synchronized (o2){ synchronized (o1){ } }
线程安全问题
在开发中应该怎么解决线程安全问题:
-
方法一;采用局部变量代替实例变量和静态变量(最好)
-
方法二:创建多个对象,让每一个线程都有单独的对象,不共享资源(排第二)
-
方法三:如果不能使用前面两个方法,只能采用synchronized
守护线程
-
什么是守护线程
-
当main方法的主线程结束,守护线程跟着结束
-
-
创建一个守护线程
public static void main(String[] args) { Thread be = new BeiFen(); be.setName("备份线程"); //将be这个线程变成守护线程,当main方法的主线程结束,守护线程跟着结束 be.setDaemon(true); be.start(); //创建一个循环 for (int i = 0; i < 10; i++) { try { System.out.println(Thread.currentThread().getName()+"-------->"+i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class BeiFen extends Thread{ public void run(){ int i =0; //创建一个死循环 while(true){ try { System.out.println(Thread.currentThread().getName()+"-------->"+(++i)); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
总结:即便守护线程是一个死循环,只要主线程结束,他就跟着结束
定时器
-
定时器的作用
-
间隔特定的时间,执行特定的程序
-
public static void main(String[] args) { //制作一个定时器 Timer timer = new Timer(); // Timer timer = new Timer(true); 这种是把定时器变成一个守护线程 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh HH:mm:ss"); try { Date firsttime = sdf.parse("2021-11-21 12:22:10"); // timer.schedule(执行任务的对象 , 第一次执行的时间 , 间隔多久执行一次); timer.schedule(new LongTmieTask() ,firsttime ,1000*5); } catch (ParseException e) { e.printStackTrace(); } } } //因为 // timer.schedule(执行任务的对象 , 第一次执行的时间 , 间隔多久执行一次); // 中必须是执行任务的对象,而 TimeTAsk是一个抽象类 无法实例化,所以创建一个子类去继承他,并重写他的抽象方法 class LongTmieTask extends TimerTask{ @Override public void run() { DateFormat df = new SimpleDateFormat("yyyy-MM-hh HH:mm:ss"); String strtime = df.format(new Date()); System.out.println(strtime+"成功备份"); }
-
回忆时间的获取
public static void main(String[] args) { //获得当前时间 Date time = new Date(); //设置一个时间修改的模式 SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //采用fomat方法将当前时间修改成这个模式 String b = s.format(time); //创建一个字符串数组将他打印成时间 String a ="1999-11-07 12:22:30"; SimpleDateFormat st = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date parse = null; try { parse = st.parse(a); System.out.println("自己设置的时间"+parse); System.out.println("当前时间"+b); } catch (ParseException e) { e.printStackTrace(); } } }
wait()方法和notify()方法
-
第一:wait和notify方法都不是线程对象的发给发,是java中任何一个对象都有的方法,因为这两个方法是object自带的
-
第二:wait()方法作用
object 0 = new object();
o.wait();
表示让正在被o对象活动的线程静茹等待状态,无期限的等待,直到被唤醒
wait方法的调用会让当前线程(正在o对象上活动的线程静茹等待状态)
-
第三:notify()方法作用
object 0 = new object();
o.notify();
表示:唤醒正在o对象上等待的线程
还有一个notifyAll()方法:表示唤醒所有o对象上处于等待的线程
作业!
/*使用生产者和消费者模式实现交替输出: * 假设只有两个线程,输出一下结果 * t1--->1 * t2--->2 * t3--->3 * t4--->4 * t5--->5 * t6--->6 * 要求必须交替输出,并且t1线程负责输出奇数,t2输出偶数 * 两个线程共享数字,每个线程执行时都要对这个数字加1 * */ public class ThreadWork { public static void main(String[] args) { Num num = new Num(10); //创建对象 Thread t1 = new Thread(new Productor(num)); Thread t2 = new Thread(new Coustom(num)); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } } class Productor implements Runnable{ private Num num; public Productor(Num num) { this.num = num; } @Override public void run() { while(true){ synchronized (num){ if (num.i%2!=0){ try { num.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"输出的偶数数是"+num.i++); num.notify(); } } } } class Coustom implements Runnable{ private Num num; public Coustom (Num num) { this.num = num; } @Override public void run() { while(true){ synchronized (num){ if (num.i%2==0){ try { num.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //输出奇数 System.out.println(Thread.currentThread().getName()+"输出的奇数是"+num.i++); num.notify(); } } } } class Num{ int i ; public Num(int i) { this.i = i; } }
反射
-
反射机制可以操控字节码
-
反射机制相关的重要的类
-
java.lang.class: 代表整个字节码,代表一个类型,代表整个数
-
java.lang.reflect.method: 代表字节码中的方法字节码,代表类中的方法
-
java.lang.reflect..constructor: 代表字节码中构造方法的字节码,代表类中构造方法的字节码
-
java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的成员变量
-
-
反射机制是操作字节码的,所以我们应该先拿到字节码,有三种方法
-
方法一 :Class.forName()Class首字母大写
-
静态方法
-
方法的参数是一个字符串
-
字符串需要的是一个完整类名
-
完整类名必须带有包名,java.lang包也不能省略
Class c1 = Class.forName("java.lang.String") //c1 代表String.class文件 Class c2 = Class.forName("java.util.Date")//c1 代表Date.class文件 Class c3 = Class.forName("java.lang.Integer")//c1 代表Integer.class文件 Class c4 = Class.forName("java.lang..System") //c1 代表System.class文件
-
-
方法二:
-
==比较的是内存地址,但是class文件在指挥产生一个内存地址,所以下面的比较是true
-
Class c1 = null; Class c4 = null; c1 = Class.forName("java.lang.String"); c4 = Class.forName("java.util.Date"); //下面为方法二,可以得到与方法一得到的文件完全一样 String x ="adc"; Class e = x.getClass(); System.out.println(e == c1);//true Date date = new Date(); Class e2 = date.getClass(); System.out.println(e2==c4);//true
-
方法三:
//第三种方式 Class b1 = String.class;//代表String文件 Class b2 = Date.class; //代表Date文件 Class b3= Integer.class; //代表Integer文件 System.out.println(b1==e);//true System.out.println(b2==e2);//true
newinstance
//通过反射来创建一个对象 try { Class c = Class.forName("fanshe.User");//()里面是User类的完整类名,然后调用newinstance方法来实例化一个对象 try { Object o = c.newInstance();//完成了对象的创建,但是这种方法会调用User的无参构造,所以要注意 } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class User{ //如果这里定义了有参构造,一定要把午餐构造写出,不然上面会报错 }
-
反射实例化对象的优势:很灵活,可以通过在文件种修改v值,那么得到的完整的类名就会不同,就会实例化不同的对象,也可以批量实例化
public static void main(String[] args) { try { //创建io流读取文件数据 FileReader reader = new FileReader("FANSHE/FanSheIo.properties"); //创建map集合 Properties pro = new Properties(); //读取文件 pro.load(reader); //关闭流 reader.close(); String username = pro.getProperty("username");//得到的是一个完整的类名,于是可以通过反射的方法实例化对象 Class c = Class.forName(username); System.out.println(c); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } class Users { }
-
如果你只希望一个类的静态代码块执行,那么可以只写Class.forName(完整类名),这个方法会导致类加载,而静态代码快就是在类加载是执行
public static void main(String[] args) { Class.forName("fanshe.User"); } class User{ static{System.out.println("静态代码快执行了");} }
获取绝对路径
-
只有在src的根路径下的文件才可以用这种方法
String path = Thread.currentThread().getContextClassLoader().getResource("这里写文件在src下的路径").getPath();
public static void main(String[] args) { //获取文件的绝对路径 只适用于文件在src下的文件 String path = Thread.currentThread().getContextClassLoader().getResource("fanshe/LuJing.properties").getPath(); //System.out.println(path); //但是我输出的会又5% 7%这些东西,网上查了查先对path惊醒一个dvade path = java.net.URLDecoder.decode(path,"utf-8"); //在输出路径就对了 System.out.println(path); }
资源绑定器!
资源绑定器! 不需要用到io流:java.util.包下提供了一个资源绑定器,便于获取属性配置文件种的内容,使用一下这种方式的时候,属性配置文件xxx.properties必须放在类路径下面,
而且只能绑定xxx.properties文件.并且这个文件的扩展名一定要是properties 而且在写路径的时候properties还不用写
public static void main(String[] args) { //资源绑定器 ResourceBundle budle = ResourceBundle.getBundle("fanshe/LuJing"); //这种查找文件种的v值就不需要创建一个流个map集合了 String username = budle.getString("username"); System.out.println(username); } } class Userss{ }
不需要创建流和map集合就可以查找到v值