一、String 和 StringBuffer、StringBuilder 的区别:
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
二、什么是线程安全
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
三、创建线程的方式
分为四种——
1.Theard
2.Runnable
3.callable
4.Theardpool
1.Theard
//创建线程方式一:继承Thread类,重写run方法,调用start开启线程 public class test01 extends Thread { @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码---"+i); } } public static void main(String[] args) { //创建一个线程对象 test01 test01 = new test01(); //调用start方法开启线程 test01.start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习多线程---"+i); } } }
运行结果如图所示
2.Runnable接口
//创建线程方式一:继承Thread类,重写run方法,调用start开启线程 public class test01 implements Runnable{ @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码---"+i); } } public static void main(String[] args) { //创建Runnable接口的实现类对象 test01 test01 = new test01(); //创建线程对象,通过线程对象来开启我们的线程,代理 // Thread thread = new Thread(test01); // //调用start方法开启线程 // Thread.start(); new Thread(test01).start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习多线程---"+i); } } }
运行结果如图所示
总结:继承Theard类,子类继承Theard类具备多线程能力,启动线程:子类对象.start()
不建议使用Theard,因为避免OOP单继承局限性
实现Runnable接口,实现Runnable接口具备多线程能力,启动线程:传入目标对象+Theard对象.start()
推荐使用Runnable接口,因为避免了单继承局限性,灵活方便,便于同一个对象被多个线程使用。
案例:
/** * 多个线程同时操作同一个对象 买火车票案例 */ //发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱 public class Demo4_TrainTicketsCase implements Runnable { //票数 private int ticketNums = 10; @Override public void run() { while (true) { if (ticketNums <= 0) { break; } //捕获异常 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票"); } } public static void main(String[] args) { Demo4_TrainTicketsCase ticket = new Demo4_TrainTicketsCase(); new Thread(ticket, "小红").start(); new Thread(ticket, "老师").start(); new Thread(ticket, "黄牛1").start(); new Thread(ticket, "黄牛2").start(); } }
这样产生了安全隐患
案例:龟兔赛跑
/** * 模拟龟兔赛跑 */ public class Demo5_RaceCase implements Runnable { //胜利者 private static String winner; @Override public void run() { for (int i = 0; i <= 100; i++) { //模拟兔子休息 if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) { try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } //判断比赛是否结束 boolean flag = gameOver(i); //如果比赛结束,停止程序 if (flag) { break; } System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步"); } } //判断是否完成 private boolean gameOver(int steps) { if (winner != null) { return true; } else { if (steps >= 100) { winner = Thread.currentThread().getName(); System.out.println("winner is " + winner); return true; } } return false; } public static void main(String[] args) { Demo5_RaceCase race = new Demo5_RaceCase(); new Thread(race, "兔子").start(); new Thread(race, "乌龟").start(); } }
3.实现 Callable接口
实现Callable接口,需要返回值类型
重写call方法,需要抛出异常
创建目标对象
创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
提交执行:Future result1 = ser.submit(11);
获取结果:boolean r1 = result1.get()
关闭服务:ser.shutdownNow();
/** * 实现Callable接口 */ public class Demo6_CreateCallable implements Callable<Boolean> { private String url;//网络图片地址 private String name;//报错扥文件名 //有参构造 public Demo6_CreateCallable(String url, String name) { this.url = url; this.name = name; } //下载图片线程的执行体 public Boolean call() throws Exception { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url, name); System.out.println("下载了文件名为:" + name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { Demo6_CreateCallable c = new Demo6_CreateCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "1.png"); Demo6_CreateCallable c1 = new Demo6_CreateCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "2.png"); Demo6_CreateCallable c2 = new Demo6_CreateCallable("https://img-home.csdnimg.cn/images/20201124032511.png", "3.png"); //创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> r = ser.submit(c); Future<Boolean> r1 = ser.submit(c1); Future<Boolean> r2 = ser.submit(c2); //获取结果 boolean res = r.get(); boolean res1 = r1.get(); boolean res2 = r2.get(); //关闭服务 ser.shutdownNow(); } } //class WebDownloader在前面下载图片已经定义了,这里就不用再次写,直接使用就好
好处
可以定义返回值
可以抛出异常
案例:结婚案例
/** * 静态代理:结婚案例 */ public class Demo7_StaticProxy { public static void main(String[] args) { WeddingCompany weddingCompany = new WeddingCompany(new You()); weddingCompany.happyMarry(); } } //结婚 interface Marry { void happyMarry(); } //真实角色:你去结婚 class You implements Marry { @Override public void happyMarry() { System.out.println("doris要结婚了,超开心"); } } //代理角色:帮助你结婚 class WeddingCompany implements Marry { private Marry target;//代理-->真实目标角色角色,帮谁结婚 public WeddingCompany(Marry target) { this.target = target; } @Override public void happyMarry() { after(); this.target.happyMarry(); before(); } private void before() { System.out.println("结婚之前,布置现场"); } private void after() { System.out.println("结婚之后,收尾款"); } }
优化:使用线程,Lamda表达式
/** * 线程中的代理模式 */ public class Demo8_StaticProxy { public static void main(String[] args) { new Thread(()-> System.out.println("我爱你")).start(); new WeddingCompany(new You()).happyMarry(); } } //WeddingCompany...上一个文件定义过了这里就直接使用了
总结
真实对象和代理对象都要实现一个接口
代理对象要代理真实角色
好处
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事