typora-root-url: src
java——study(mspaint)
一. 多线程详解(java.thread)
重点:线程的实现和线程的同步
1. 线程简介
多任务,多线程
-
普通方法调用和多线程
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tk7KsmrA-1666515575309)(F:\study\javaStudy\study\src\1.png)]
-
在操作系统中运行的程序就是进程,比如你的qq,播放器……
Process与Thread
- 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 通常在一个进程中可以包含若干个 线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
- 注意:很多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,多有就有同时执行的错局。
- main()称之为主线程,为系统的入口,用于执行整个程序;
2. 线程创建
(1) 继承Thread类
-
自定义线程类继承 Thread类
-
重写 run() 方法,编写线程执行体
-
创建线程对象,调用 start() 方法启动线程
-
public class TestThread extends Thread{ @Override public void run() { // run() 方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码"); } } public static void main(String[] args) { //main线程,主线程 // 创建一个线程对象 TestThread testThread = new TestThread(); // 调用start()方法开启线程 testThread.start(); for (int i = 0; i < 20; i++) { System.out.println("我在学多线程"+i); } } }
-
-
总结:注意,线程开启不一定立即执行,由cpu调度执行
多线程同步下载图片
-
新建package为lib,放入commons-io包,右键add as libarily
-
import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class TestThread2 extends Thread{ //练习thread,实现多线程同步下载图片 private String url;//网络图片地址 private String name;//保存的文件名 public TestThread2(String url,String name){ this.url = url; this.name = name; } //下载图片线程的执行体 @Override public void run() { WebDownload webDownload = new WebDownload(); webDownload.download(url,name); System.out.println("下载了文件名:"+ name); } public static void main(String[] args) { TestThread2 t1 = new TestThread2("https://pic.cnblogs.com/face/1418974/20181015113902.png","1.jpg"); TestThread2 t2 = new TestThread2("https://pic.cnblogs.com/face/1418974/20181015113902.png","2.jpg"); TestThread2 t3 = new TestThread2("https://pic.cnblogs.com/face/1418974/20181015113902.png","3.jpg"); t1.start(); t2.start(); t3.start(); } } //下载器 class WebDownload{ //下载方法 public void download(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,download方法出现问题"); } } }
(2) 实现Runnable接口
-
定义MyRunnable 类实现 Runnable 接口
-
**实现run()**方法,编写线程执行体
-
创建线程对象,调用 **start()**方法启动线程
-
public class TestThread3 implements Runnable{ //创建线程方式2:实现Runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法 @Override public void run() { // run 方法线程体 for (int i = 0; i < 200; i++) { System.out.println("我在看代码-----"); } } public static void main(String[] args) { // 创建runnable接口的实现类对象 TestThread3 testThread3 = new TestThread3(); //创建线程对象,通过线程对象来开启我们的线程,代理 // Thread thread = new Thread(testThread3); // thread.start(); new Thread(testThread3).start(); for (int i = 0; i < 1000; i++) { System.out.println("我在学习多线程---"+i); } } }
-
实现图片的多线程下载
-
import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class TestThread2 extends Thread{ //练习thread,实现多线程同步下载图片 private String url;//网络图片地址 private String name;//保存的文件名 public TestThread2(String url,String name){ this.url = url; this.name = name; } //下载图片线程的执行体 @Override public void run() { WebDownload webDownload = new WebDownload(); webDownload.download(url,name); System.out.println("下载了文件名:"+ name); } public static void main(String[] args) { TestThread2 t1 = new TestThread2("https://pic.cnblogs.com/face/1418974/20181015113902.png","1.jpg"); TestThread2 t2 = new TestThread2("https://pic.cnblogs.com/face/1418974/20181015113902.png","2.jpg"); TestThread2 t3 = new TestThread2("https://pic.cnblogs.com/face/1418974/20181015113902.png","3.jpg"); new Thread(t1).start(); new Thread(t2).start(); new Thread(t3).start(); } } //下载器 class WebDownload{ //下载方法 public void download(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,download方法出现问题"); } } }
-
小结
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start();
- 不建议使用:避免oop单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多线程使用
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F6hpNyE7-1666515575309)(F:\study\javaStudy\study\src\2.png)]
- 继承Thread类
初始并发问题
-
// 发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱 public class TestThread4 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) { TestThread4 ticket = new TestThread4(); new Thread(ticket,"小名").start(); new Thread(ticket,"张三").start(); new Thread(ticket,"李四").start(); } }
龟兔赛跑 —— Race
- 首先来个赛道距离,然后要离终点越来越近
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
- 终于乌龟赢得了比赛
-
public class Race 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(10); } 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; }{ if(steps >= 100){ winner = Thread.currentThread().getName(); System.out.println("winner is" + winner); return true; } } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); } }
(3) 实现Calloable接口(了解即可)
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPllo(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果:boolea r1 = result1.get()
- 关闭服务:ser.shutdownNow();
-
多线程下载图片
-
package demo; import com.sun.org.apache.xpath.internal.operations.Bool; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; public class TestCallable implements Callable<Boolean> { private String url;//网络图片地址 private String name;//保存的文件名 public TestCallable(String url,String name){ this.url = url; this.name = name; } //下载图片线程的执行体 @Override public Boolean call() { WebDownload webDownload = new WebDownload(); webDownload.download(url,name); System.out.println("下载了文件名:"+ name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable t1 = new TestCallable("https://pic.cnblogs.com/face/1418974/20181015113902.png","1.jpg"); TestCallable t2 = new TestCallable("https://pic.cnblogs.com/face/1418974/20181015113902.png","2.jpg"); TestCallable t3 = new TestCallable("https://pic.cnblogs.com/face/1418974/20181015113902.png","3.jpg"); // 1. 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(3); // 2. 提交执行: Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); // 3. 获取结果: boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get(); System.out.println(rs1); System.out.println(rs2); System.out.println(rs3); // 4. 关闭服务: ser.shutdownNow(); } } //下载器 class WebDownload{ //下载方法 public void download(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,download方法出现问题"); } } }
静态代理
-
总结:
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实对象
- 好处
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
-
public class StaticProxy { public static void main(String[] args) { new Thread( ()-> System.out.println("我爱你")).start(); You you = new You(); WeddingCompay weddingCompay = new WeddingCompay(you); weddingCompay.HappyMarry(); } } interface Marry{ void HappyMarry(); } //真实角色,你去结婚 class You implements Marry{ @Override public void HappyMarry() {// alt+inst 这就是真实对象 System.out.println("要结婚了"); } } //代理角色,帮助你结婚 class WeddingCompay implements Marry{ // 代理谁 ----》 真实目标角色 private Marry target; public WeddingCompay(Marry target) { this.target = target; } @Override public void HappyMarry() { before(); this.target.HappyMarry(); after(); } private void before() {//alt+ent System.out.println("结婚之前,布置现场"); } private void after() { System.out.println("结婚之后,收尾款"); } }
Lamda表达式
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opsEVODF-1666515575310)(F:\study\javaStudy\study\src\3.png)]
-
理解Functional Interface(函数式接口)是学习java8 Lambda表达式的关键所在。
-
函数式接口的定义:
-
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
-
public interface Runnable{ public abstract void run(); }
-
对于函数式接口, 我们可以通过lambda表达式来创建该接口的对象。
-
案例1
-
package lambda; /* 推导Lambda表达式 */ public class TestLambda { public static void main(String[] args) { ILike like = new Like(); like.lambda(); } } // 1,定义一个函数式接口 interface ILike{ void lambda(); } //2. 实现类 class Like implements ILike{ @Override public void lambda() { System.out.println(" i like lambda"); } }
优化
-
package lambda; /* 推导Lambda表达式 */ public class TestLambda { //3. 静态内部类 static class Like2 implements ILike{ @Override public void lambda() { System.out.println("i like lambda2"); } } public static void main(String[] args) { ILike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); // 4.局部内部类 class Like3 implements ILike{ @Override public void lambda() { System.out.println("i like lambda3"); } } like = new Like3(); like.lambda(); //5. 匿名内部类,没有类的名称,必须借助接口或者父类 like = new ILike() { @Override public void lambda() { System.out.println("i like lambda4"); } }; like.lambda(); // 6. 用lamdba简化 like = ()-> { System.out.println("i like lambda5"); }; like.lambda(); } } // 1,定义一个函数式接口 interface ILike{ void lambda(); } //2. 实现类 class Like implements ILike{ @Override public void lambda() { System.out.println(" i like lambda"); } }
案例2
-
package lambda; public class TestLamdba2 { public static void main(String[] args) { ILove love = (int a)-> { System.out.println("i love you" + a); }; love.love(2); } } interface ILove{ void love(int a); }
-
总结
- lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码 块包裹
- 前提是接口为函数式接口
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加括号
简化
-
package lambda; public class TestLamdba2 { public static void main(String[] args) { ILove love = null; // 1. lambda表示简化 // ILove love = (int a)-> { // System.out.println("i love you" + a); // }; // 简化1. 参数类型 love = (a)->{ System.out.println("i love you" + a); }; // 简化2。简化括号 love = a->{ System.out.println("i love you" + a); }; // 简化3. 去掉花括号 love = a-> System.out.println("i love you" + a); // 总结: // lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码 块包裹 // 前提是接口为函数式接口 // 多个参数也可以去掉参数类型,要去掉就都去掉,必须加括号 love.love(521); } } interface ILove{ void love(int a); }
3. 线程状态
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVd1ZwCy-1666515575310)(F:\study\javaStudy\study\src\4.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAS89Zj6-1666515575311)(F:\study\javaStudy\study\src\5.png)]
-
线程方法
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jSbplLi-1666515575311)(F:\study\javaStudy\study\src\6.png)]
(1)停止线程
-
不推荐使用JDK提供的 stop()、destory()方法。【已废弃】
-
推荐线程自己停止下来
-
建议使用一个标志位进行终止变量当flag=false,则终止线程运行。
-
package state; //测试stop //1. 建议 线程正常停止 ---> 利用次数,不建议死循环。 //2. 建议使用标志位 ---》 设置一个标志位 //3. 不要使用stop或者destory等过时或者JDK不建议使用的方法 public class TestStop implements Runnable{ // 1. 设置一个标识位 private boolean flag = true; @Override public void run() { int i = 0; while (flag){ System.out.println("run……Thread" + i++); } } // 2.设置一个公开的方法停止线程,转化标志位 public void stop() { this.flag = false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 1000; i++) { System.out.println("main" + i); if(i==900){ // 调用stop方法切换标志位,让线程停止 testStop.stop(); System.out.println("线程该停止了"); } } } }
(2)线程休眠
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFBZJu4z-1666515575311)(F:\study\javaStudy\study\src\7.png)]
案例1
-
package state; //模拟网络延时:放大问题的发生性 public class TestSleep 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) { TestSleep ticket = new TestSleep(); new Thread(ticket,"小名").start(); new Thread(ticket,"张三").start(); new Thread(ticket,"李四").start(); } }
案例2
-
package state; import java.text.SimpleDateFormat; import java.util.Date; //模拟倒计时 public class TestSleep2 { public static void main(String[] args) { // try { // tenDown(); // } catch (InterruptedException e) { // e.printStackTrace(); // } // 打印当前时间 Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间 try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:MM:SS").format(startTime)); startTime = new Date(System.currentTimeMillis());//更新当前时间 } catch (InterruptedException e) { e.printStackTrace(); } } // 模拟倒计时 public static void tenDown() throws InterruptedException { int num = 10; while (true){ Thread.sleep(1000); System.out.println(num--); if(num<=0){ break; } } } }
(3)线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功!看CPU心情;
案例
-
package state; public class TestYield { public static void main(String[] args) { MyYielf myYielf = new MyYielf(); new Thread(myYielf,"a").start(); new Thread(myYielf,"b").start(); } } class MyYielf implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield();//礼让 System.out.println(Thread.currentThread().getName()+"线程停止执行"); } }
(4)Join
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6A3pcy9r-1666515575312)(F:\study\javaStudy\study\src\8.png)]
案例
-
package state; //测试join方法,想象为插队 public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("线程Vip来了" + i); } } public static void main(String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); // 主线程 for (int i = 0; i < 500; i++) { if(i==200){ thread.join();//插队 } System.out.println("main" + i); } } }
(5)线程状态观测
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9MWh8qQ2-1666515575312)(F:\study\javaStudy\study\src\9.png)]
案例
-
package state; public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread( ()-> { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(""); }); // 观察状态 Thread.State state = thread.getState(); System.out.println(state); //观察启动 后 thread.start();//启动 state = thread.getState(); System.out.println(state); while (state != Thread.State.TERMINATED){//只要线程不终止,就一直输出状态 Thread.sleep(1000); state = thread.getState();//更新线程状态 System.out.println(state); } // thread.start(); 结束的进程是不能在 被启动的 } }
(6)线程优先级
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZtsaTHR-1666515575312)(F:\study\javaStudy\study\src\10.png)]
- 优先级低只是意味着获取调度的概率低。并不是优先级低就不会被调用了。着都是看cpu的调度 。
案例
-
package state; //测试线程的优先级 public class TestPriority { public static void main(String[] args) { // 主线程默认优先级 System.out.println(Thread.currentThread().getName()+ "---->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); Thread t6 = new Thread(myPriority); // 先设置优先级,在启动 t1.start(); t2.setPriority(1); t2.start(); t3.setPriority(4); t3.start(); t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10 t4.start(); t5.setPriority(8); t5.start(); t6.setPriority(7); t6.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+ "---->" + Thread.currentThread().getPriority()); } }
(7)守护(daemon)线程
- 线程分为 用户线程 和 守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后台造作日志、监控内存、垃圾回收等待
案例
-
package state; //测试守护线程 //上帝守护你 public class TestDaemon { public static void main(String[] args) { God god = new God(); You you = new You(); Thread thread = new Thread(god); thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程 thread.start();//上帝守护线程启动 new Thread(you).start();//你 用户线程启动 } } //上帝 class God implements Runnable{ @Override public void run() { while (true) { System.out.println("上帝保佑着你"); } } } //你 class You implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("你一生呢个都开心的活着"); } System.out.println("=====goodbye!========"); } }
4. 线程同步
- 多个线程操作同一个资源
- 并发: 同一个对象 被 多个线程 同时造作
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbbKibZt-1666515575320)(F:\study\javaStudy\study\src\11.png)]
队列和锁
- 形成条件:队列+ 锁
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQdDpjiJ-1666515575320)(F:\study\javaStudy\study\src\12.png)]
三大不安全案列
案例一(买票)
-
package syn; //不安全的买票 //线程不安全有负数 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"苦逼的我").start(); new Thread(station,"牛逼的你们").start(); new Thread(station,"可恶的黄牛党").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNums = 10; boolean flag = true;//外部停止方式 @Override public void run() { // 买票 while (flag) { try { buy(); } catch (Exception e) { throw new RuntimeException(e); } } } private void buy() throws InterruptedException { //判断是否有票 if(ticketNums <= 0){ flag = false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+ "拿到" + ticketNums--); } }
-
不安全的买票
-
线程不安全有负数
案例二(取钱)
-
package syn; //不安全的取钱 //两个人去引号取钱,账户 public class UnsafeBank { public static void main(String[] args) { //账户 Account account = new Account(100,"结婚基金"); Drawing you = new Drawing(account,50,"你"); Drawing girFriend = new Drawing(account,100,"girFriend"); you.start(); girFriend.start(); } } //账户 class Account{ int money;//余额 String name;// 卡名 public Account(int money, String name) { this.money = money; this.name = name; } } //银行:模拟取款 class Drawing extends Thread{ Account account;//账户 //取了多少钱 int drawingMoney; //现在手里有 多少钱 int nowMoney; public Drawing( Account account,int drawingMoney,String name){ super(name); this.account = account; this.drawingMoney = drawingMoney; } @Override public void run() { //判断有没有钱 if(account.money-drawingMoney<0){ System.out.println(Thread.currentThread().getName()+ "钱不够,取不了"); return; } //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } //卡内余额 = 余额 - 你取的钱 account.money = account.money - drawingMoney; //你手里的钱 nowMoney = nowMoney + drawingMoney; System.out.println(account.name + "余额为" + account.money); //Thread.currentThread().getName() = this,getName() System.out.println(this.getName()+"手里的钱"+nowMoney); } }
案例三(List)
-
package syn; import java.util.ArrayList; import java.util.List; //线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
同步方法及同步块
(1)同步方法
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-whVn3Ll3-1666515575321)(F:\study\javaStudy\study\src\13.png)]
- 当操作系统没有对进程的资源进行限制时将会发生死锁。
- 方法里面需要修改的内容才需要锁,锁的太多,浪费资源。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dVQ6fHPc-1666515575321)(F:\study\javaStudy\study\src\14.png)]
案例一(买票)
-
package syn; //不安全的买票 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"苦逼的我").start(); new Thread(station,"牛逼的你们").start(); new Thread(station,"可恶的黄牛党").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNums = 10; boolean flag = true;//外部停止方式 @Override public void run() { // 买票 while (flag) { try { buy(); } catch (Exception e) { throw new RuntimeException(e); } } } //synchronized同步方法,锁的是this private synchronized void buy() throws InterruptedException { //判断是否有票 if(ticketNums <= 0){ flag = false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+ "拿到" + ticketNums--); } }
(2)同步块
案例二(取钱)
-
package syn; //不安全的取钱 //两个人去引号取钱,账户 public class UnsafeBank { public static void main(String[] args) { //账户 Account account = new Account(100,"结婚基金"); Drawing you = new Drawing(account,50,"你"); Drawing girFriend = new Drawing(account,100,"girFriend"); you.start(); girFriend.start(); } } //账户 class Account{ int money;//余额 String name;// 卡名 public Account(int money, String name) { this.money = money; this.name = name; } } //银行:模拟取款 class Drawing extends Thread{ Account account;//账户 //取了多少钱 int drawingMoney; //现在手里有 多少钱 int nowMoney; public Drawing( Account account,int drawingMoney,String name){ super(name); this.account = account; this.drawingMoney = drawingMoney; } //synchronized 默认锁的是this @Override public void run() { //锁的对象就是变化的量,增删改 synchronized (account){ //判断有没有钱 if(account.money-drawingMoney<0){ System.out.println(Thread.currentThread().getName()+ "钱不够,取不了"); return; } //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } //卡内余额 = 余额 - 你取的钱 account.money = account.money - drawingMoney; //你手里的钱 nowMoney = nowMoney + drawingMoney; System.out.println(account.name + "余额为" + account.money); //Thread.currentThread().getName() = this,getName() System.out.println(this.getName()+"手里的钱"+nowMoney); } } }
案例三(List)
-
package syn; import java.util.ArrayList; import java.util.List; //线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
CopyOnWriteArrayList
-
package syn; import java.util.concurrent.CopyOnWriteArrayList; //测试JUC安全类型的集合 public class TestJUC { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
5. 死锁
-
索格线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
-
package syn; //死锁:多个线程互相抱着对方需要的资源,然后相互僵持 public class DeadLock { public static void main(String[] args) { Makeup g1 = new Makeup(0,"灰姑娘"); Makeup g2 = new Makeup(1,"白雪公主"); g1.start(); g2.start(); } } //口红 class Lipstick{ } //镜子 class Mirror{ } class Makeup extends Thread{ //需要的资源只有一份,用static来保证只有一份 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice;//选择 String girName;//使用化妆品的人 Makeup(int choice,String girName){ this.choice = choice; this.girName = girName; } @Override public void run() { super.run(); //化妆\ try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } //化妆,互相持有对方的锁,就是需要拿到对方的资源 private void makeup() throws InterruptedException { if(choice == 0){ synchronized (lipstick){//获得口红的锁 System.out.println(this.girName+ "获得口红的锁"); Thread.sleep(1000); // synchronized (mirror){//一秒钟后想获得镜子 // System.out.println(this.girName+ "获得镜子的锁"); // } } synchronized (mirror){//一秒钟后想获得镜子 System.out.println(this.girName+ "获得镜子的锁"); } } else { synchronized (mirror) {//获得镜子的锁 System.out.println(this.girName + "获得镜子的锁"); Thread.sleep(2000); // synchronized (lipstick) {//2秒钟后想获得口红 // System.out.println(this.girName + "获得镜子的锁"); // } } synchronized (lipstick) {//2秒钟后想获得口红 System.out.println(this.girName + "获得镜子的锁"); } } } }
-
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
-
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生。
6. Lock(锁)
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxUPO2rA-1666515575321)(F:\study\javaStudy\study\src\15.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aud4waYN-1666515575321)(F:\study\javaStudy\study\src\16.png)]
-
package syn; import java.util.concurrent.locks.ReentrantLock; //测试Lock锁 public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); } } class TestLock2 implements Runnable{ int ticketNums = 10; //定义lock锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { lock.lock();//加锁 if (ticketNums>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); }else{ break; } } finally { lock.unlock(); } } } }
synchronized 与Lock的对比
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qi2gU78S-1666515575322)(F:\study\javaStudy\study\src\17.png)]
7. 线程协作
生产者消费者
-
生产者消费者(producer-consumer)(有界缓冲区(bounded-buffer))模式
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pa3DZKSy-1666515575322)(F:\study\javaStudy\study\src\18.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FZg6tIQC-1666515575322)(F:\study\javaStudy\study\src\19.png)]
线程通信-分析
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEnmswkq-1666515575323)(F:\study\javaStudy\study\src\20.png)]
解决方式1(管程法)
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bVLpfTWX-1666515575323)(F:\study\javaStudy\study\src\21.png)]
-
package syn; //测试:生产者消费者模型---》 利用缓冲区解决:管程法 //生产者,消费者,产品,缓冲区 public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } } //生产者 class Productor extends Thread{ SynContainer container; public Productor(SynContainer container){ this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { container.push(new Chicken(i)); System.out.println("生产了"+i+"只鸡"); } } } //消费者 class Consumer extends Thread{ SynContainer container; public Consumer(SynContainer container){ this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了----》" + container.pop().id+"只鸡"); } } } //产品 class Chicken{ int id;//产品编号 public Chicken(int id) { this.id = id; } } //缓冲区 class SynContainer{ //需要一个容器大小 Chicken[] chickens = new Chicken[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken){ //如果容器满了,就需要等待消费这消费 if(count == chickens.length){ //通知消费者消费,生产者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满,我们就需要丢入产品 chickens[count] = chicken; count++; //可以通知消费者消费了 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop(){ //判断能否消费 if(count==0){ //等待生产者生产。消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Chicken chicken = chickens[count]; //吃完了,通知生产生产 this.notifyAll(); return chicken; } }
解决方式2(信号灯法)
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1HmsgBmz-1666515575323)(F:\study\javaStudy\study\src\22.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zxZk6LkH-1666515575323)(F:\study\javaStudy\study\src\23.png)]package syn; //测试生产者问题2:信号灯法,标志位解决 public class TestPC2 { public static void main(String[] args) { Tv tv = new Tv(); new Player(tv).start(); new Watcher(tv).start(); } } //生产者--》演员 class Player extends Thread{ Tv tv; public Player(Tv tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if(i%2==0){ this.tv.play("快乐大本营"); }else{ this.tv.play("抖音"); } } } } //消费者---》观众 class Watcher extends Thread{ Tv tv; public Watcher(Tv tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品---》节目 class Tv{ //演员表演观众等待 //观众观看演员等待 String voice;//表演节目 boolean flag = true; //表演 public synchronized void play(String voice){ if(!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了"+voice); //通知观众观看 this.notify(); this.voice = voice; this.flag = !this.flag; } //观看 public synchronized void watch(){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观看了"+voice); //通知演员表演 this.notify(); this.flag = !this.flag; } }
8. 线程池
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O23G4jPU-1666515575323)(F:\study\javaStudy\study\src\23.png)]
-
使用线程迟
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbWHuRST-1666515575324)(F:\study\javaStudy\study\src\24.png)]
-
package syn; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestPool { public static void main(String[] args) { //1, 创建服务,创建线程池 // newFixedThreadPool 参数为:线程池大小 ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //2.关闭链接 service.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
总结:
-
package syn; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; //回顾总结线程的创建 public class ThreadNew { public static void main(String[] args) { new MyThread1().start(); new Thread(new MyThread2()).start(); FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3()); new Thread(futureTask).start(); try { Integer integer = futureTask.get(); System.out.println(integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } //1,继承Thread类 class MyThread1 extends Thread{ @Override public void run() { System.out.println("My Thread1"); } } //2. 实现RUnnable接口 class MyThread2 implements Runnable{ @Override public void run() { System.out.println("My Thread2"); } } //3. 实现callable接口 class MyThread3 implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("My Thread3"); return 100; } }
二. javaWEB
1. 数据库(MySQL)
-
注册Mysql服务
mysql -install
-
启动mysql
net start mysql
-
停止mysql
net stop mysql
-
修改默认账户密码
mysqladmin -u root password 1234
-
登录mysql
mysql -uroot -p1234
-
退出mysql
exit/quit
-
登录参数
mysql -u用户名 -p密码 -h要连接mysql的ip地址 -P端口号(3306)
-
卸载mysql
mysqld -remove mysql
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mzJpr8pW-1666515575324)(F:\study\javaStudy\study\src\25.png)]
聚合函数
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWs56rMC-1666515575324)(F:\study\javaStudy\study\src\26.png)]
分页查询
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HrXoaDbz-1666515575324)(F:\study\javaStudy\study\src\27.png)]
约束
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwgZw01C-1666515575324)(F:\study\javaStudy\study\src\28.png)]
外键
-
概念:外键用来让两个表的数据之间建立链接,保证数据的一致性和完整性。
-
语法:
-
添加约束
-
-- 创建表时添加外键约束 CREATE TABLE 表名{ 列名 数据类型, …… [CONSTRAINT][外键名称] FOREIGN KEY(外键列名) REFERENCES 主表(主表列名) };
-
-- 建完表后添加外键约束 ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY(外键字段名称) REFERENCES主表名称(主表列名称);
-
-
删除约束
-
ALTER TABLE表名 DROP FOREIGN KEY 外键名称;
-
-
数据库设计
- 软件的研发步骤
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9KQPH58-1666515575325)(F:\study\javaStudy\study\src\29.png)]
- 数据库设计概念
- 数据库设计就是根据业务系统的具体需求,结合我们所选用的DBMS,为这个业务系统构造出最优的数据存储模型。
- 建立数据库中国的 表结构 以及 表与表之间的关联关系 的过程
- 有那些表?表里有那些字段?表和表之间有什么关系?
- 数据设计的步骤
- 需求分析(数据是什么?数据具有那些属性?数据与属性的特点是什么)
- 逻辑分析(通过ER图对数据库进行逻辑建模,不需要考虑我们所选用的数据库管理系统)
- 物理设计(根据数据库自身的特点把逻辑设计转换为物理设计)
- 维护设计(1.对新的需求进行建表;2.表优化)
- 表关系
- 一对一
- 一对多(多对一)
- 多对多
多表查询(笛卡尔积)
-
笛卡尔积:有A,B两个集合取A,B所有的组合情况(有无效数据)
-
消除无效数据
-
连接查询
-
内连接:相当于查询A B交集数据
-
--隐式内连接 SELECT 字段列表 FROM表1,表2... WHERE条件; --显示内连接 SELECT字段列表FROM 表1[INNER]JOIN 表2 ON条件;
-
-
外连接
-
左外连接:相当于查询A表所有数据和交集部分数据
-
右外连接:相当于查询B表所有数据和交集部分数据
-
--左外连接 SELECT字段列表FROM表1 LEFT [ouTER] JOIN 表2 ON条件; -右外连接 SELECT字段列表FROM表1 RIGHT [OUTER] JOIN 表2 ON条件;
-
-
子查询
-
子查询概念:
- 查询中嵌套查询,称嵌套查询为子查询
-
子查询根据查询结果不同,作用不同:
- 单行单列
- 多行单列·多行多列
-
子查询根据查询结果不同,作用不同:
-
单行单列:作为条件值,使用=!= ><等进行条件判断
-
SELECT字段列表FROM 表 wHERE 字段名 = (子查询);
-
多行单列:作为条件值,使用in等关键字进行条件判断
-
SELECT字段列表 FROM 表 WHERE 字段名 in (子查询);
-
多行多列:作为虚拟表
-
SELECT字段列表 FROM(子查询)WHERE条件;
-
-
-
事务
-
事务简介
- 数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令
- 事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么同时成功,要么同时失败
- 事务是一个不可分割的工作逻辑单元
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j0O7CRDk-1666515575325)(F:\study\javaStudy\study\src\30.png)]
-
事务四大特征
- 原子性(Atomicity):事务是不可分割的最小操作单位,要么同时成功,要么同时失败
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态
- 隔离性(lsolation) :多个事务之间,操作的可见性
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
-
MySQL事务默认自动提交
-
--查看事务的默认提交方式 SELECT @@autocommit; -- 1自动提交 0手动提交 --修改事务提交方式 set @@autocommit = 0;
-
2. JDBC
-
JDBC就是使用Java语言操作关系型数据库的一套API
-
//1.注册非动 class.forName ( "com.mysql.jdbc.Driver" ); //2.获取连接对象 String url = "jdbc :mysql://127.0.0.1:3306/db1?usesSL=false" ; String username = "root"; String password = "1234"; Connection conn = DriverHanager.getConnection(url,username,password); //3.定义SQL string sql = "update account set money = 2000 where id = 1"; //4.获取执行sql的对象 statement stmt = conn.createstatement(); //5.执行sql int count = stmt.executeUpdate(sql); // 6.处理结果 // System.out.println(count) ; //7.释放资源 stmt.close(); conn.close();
-
JDBC概念
-
JDBC就是使用java语言操作关系型数据库的一套API
-
全称(Java DataBase Connectivity)Java数据库连接
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XmAlinsf-1666515575325)(F:\study\javaStudy\study\src\31.png)]
-
步骤
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z82c1ko1-1666515575325)(F:\study\javaStudy\study\src\32.png)]
-
package com.zjxweb; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class JDBCDemo { public static void main(String[] args) throws Exception { //1. 注册驱动 //Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1"; String username="root"; String password= "123456"; Connection conn = DriverManager.getConnection(url,username,password); //3. 定义sql语句 String sql = "update db set sum = 2 where id=8"; //4. 获取执行sql的对象 Statement Statement stmt = conn.createStatement(); //5. 执行sql int count = stmt.executeUpdate(sql);//影响的行数 //6. 处理结果 System.out.println(count); //7.释放资源 stmt.close(); conn.close(); } }
-
(1) JDBC API详解
Driver Manager
-
DriverManager(驱动管理类)作用:
- 注册驱动
- 获取数据库连接
-
获取连接
-
static Connection getConnection (string url,string user,string password)
-
参数
-
url:连接参数
语法: jdbc:mysql:/lip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2... 示例: jdbc:mysql://127.0.0.1:3306/db1 细节: ·如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对 ·配置usesSL=false参数,禁用安全连接方式,解决警告提示 String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
-
user:用户名
-
password:密码
-
-
Connection
- Connection(数据库连接对象)作用:
-
获取执行SQL的对象
- 普通执行sql对象
Statement createStatement()
- 预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement (sql)
- 执行存储过程的对象
CallableStatement prepareCall (sql)
-
管理事务
-
Mysql事务管理
-
开启事务:BEGIN; /STARTTRANSACTION; 提交事务:COMMIT; 回滚事务:ROLLBACK; MYSQL默认白动提交事冬
- JDBC事务管理:Connection接口中定义了3个对应的方法
-
开启事务: setAutoCommit(boolean autoCommit): true为自动提交事务;false为手动提交事务,即为开启事务 提交事务:commit() 回滚事务:rollback()
-
-
-
package com.zjxweb; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JDBCDemo { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username="root"; String password= "123456"; Connection conn = DriverManager.getConnection(url,username,password); //3. 定义sql语句 String sql = "update db set sum = 2 where id=8"; String sql1 = "update db set sum = 2 where id=9"; //4. 获取执行sql的对象 Statement Statement stmt = conn.createStatement(); try { //开启事务 conn.setAutoCommit(false); //5. 执行sql int count = stmt.executeUpdate(sql);//影响的行数 System.out.println(count); int count1 = stmt.executeUpdate(sql1);//影响的行数 //6. 处理结果 System.out.println(count1); //提交事务 conn.commit(); } catch (Exception e) { conn.rollback(); } //7.释放资源 stmt.close(); conn.close(); } }
-
Statement
-
Statement作用
-
执行sql语句
-
int executeUpdate(sql):执行DML、DDL语句 返回值: (1)DML语句影响的行数(2) DDL语句执行后,执行成功也可能返回0
-
ResultSet executeQuery(sql):执行DQL语句 返回值: ResultSet结果集对象
-
ResultSet
-
ResultSet(结果集对象)作用:
-
封装了DQL查询语句的结果
-
ResultSet stmt.executeQuery(sql):执行DQL语句,返回ResultSet对象
-
-
获取查询结果
-
boolean next(): (1)将光标从当前位置向前移动一行(2)判断当前行是否为有效行 返回值: true:有效行,当前行有数据 false:无效行,当前行没有数据
-
xXx getXxx(参数):获取数据 xXx:数据类型;如:int getInt(参数) ; String getString(参数) 参数: . int:列的编号,从1开始 . String: 列的名称
-
-
使用步骤
- 游标向下移动一行,并判断该行是否有数据:next()
- 获取数据:getXxx(参数)
-
//循环判断游标是否是最后一行末尾 while(rs.next()){ //获取数据 rs.getXxx(参数); }
-
package com.zjxweb; import java.sql.*; public class JDBCDemo1 { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username="root"; String password= "123456"; Connection conn = DriverManager.getConnection(url,username,password); //3. 定义sql语句 String sql = "select * from db"; //4. 获取执行sql的对象 Statement Statement stmt = conn.createStatement(); //5.指向SQL ResultSet rs = stmt.executeQuery(sql); //6. 处理结果。遍历rs中的所有数据 //6.1 光标向下移动一行,并且判断当前行是否有数据 while(rs.next()){ //6.2 获取数据 String data = rs.getString(1); String name = rs.getString(2); String local = rs.getString(3); System.out.println(data); System.out.println(name); System.out.println(local); System.out.println("---------------"); } //释放资源 rs.close(); stmt.close(); conn.close(); } }
-
案例
- 记得重写toString()方法
-
package com.zjxweb.pojo; public class Account { private String data; private String name; private String local; public String getData() { return data; } public void setData(String data) { this.data = data; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLocal() { return local; } public void setLocal(String local) { this.local = local; } // public String toString(){ // return data+name+local; // } @Override public String toString() { return "Account{" + "data='" + data + '\'' + ", name='" + name + '\'' + ", local='" + local + '\'' + '}'; } }
-
package com.zjxweb; import com.zjxweb.pojo.Account; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * 查询account账户表数据,封装为Account对象中,兵并且存储到ArratList集合中 * 1. 定义实体类Account * 2. 查询数据,封装到Account对象中 * 3. 将Account对象存入ArrayList集合中 */ public class JDBCDemo1 { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username="root"; String password= "123456"; Connection conn = DriverManager.getConnection(url,username,password); //3. 定义sql语句 String sql = "select * from db"; //4. 获取执行sql的对象 Statement Statement stmt = conn.createStatement(); //5.指向SQL ResultSet rs = stmt.executeQuery(sql); //创建集合 List<Account> list = new ArrayList<>(); //6. 处理结果。遍历rs中的所有数据 //6.1 光标向下移动一行,并且判断当前行是否有数据 while(rs.next()){ Account account = new Account(); //6.2 获取数据 String data = rs.getString(1); String name = rs.getString(2); String local = rs.getString(3); //赋值 account.setData(data); account.setName(name); account.setLocal(local); //存入集合 list.add(account); } System.out.println(list); //释放资源 rs.close(); stmt.close(); conn.close(); } }
PreparedStatement
-
PreparedStatement作用
- 预编译SQL语句并执行:预防SQL注入问题
-
SQL注入
- SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行 攻击 的方法。
-
案列
-
package com.zjxweb; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JDBCDemo6_UserLogin { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username="root"; String password= "123456"; Connection conn = DriverManager.getConnection(url,username,password); String name = "zhanshan"; // String pwd = "123456"; String pwd = "' or ' 1 '= ' 1 "; String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'"; // 获取stmt对象 Statement stmt = conn.createStatement(); //执行sql ResultSet rs = stmt.executeQuery(sql); //判断登录是否成功 if(rs.next()){ System.out.println("登录成功"); }else { System.out.println("登录失败"); } //7.释放资源 rs.close(); stmt.close(); conn.close(); } }
-
解决sql注入问题
-
获取PreparedStatement对象
-
// SQL语句中的参数值,使用?占位符替代 String sql = "select * from user where username = ? and password = ?"; //通过Connection对象获取,并传入对应的sql语句 PreparedStatement pstmt = conn.prepareStatement(sql);
-
-
设置参数值
-
PreparedStatement对象: setXxx(参数1,参数2):给﹖赋值 Xxx:数据类型;如setlnt(参数1,参数2) 参数: 参数1:?的位置编号,从1开始 参数2:?的值
-
-
执行SQL
-
executeUpdate(); // executeQuery();:不需要再传递sql
-
-
-
案列
-
package com.zjxweb; import java.sql.*; public class JDBCDemo6_UserLogin { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false"; String username="root"; String password= "123456"; Connection conn = DriverManager.getConnection(url,username,password); String name = "zhanshan"; String pwd = "123456"; // String pwd = "' or ' 1 '= ' 1 "; // String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'"; String sql = "select * from tb_user where username = ? and password = ?"; // 获取stmt对象 PreparedStatement pstmt = conn.prepareStatement(sql); // 设置 ? 的值 pstmt.setString(1,name); pstmt.setString(2,pwd); //执行sql ResultSet rs = pstmt.executeQuery(); //判断登录是否成功 if(rs.next()){ System.out.println("登录成功"); }else { System.out.println("登录失败"); } //7.释放资源 rs.close(); pstmt.close(); conn.close(); } }
-
PreparedStatement原理
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAGkBC0k-1666515575325)(F:\study\javaStudy\study\src\33.png)]
-
log-output=FILE general-log=1 general_log_file="D:\mysql.log" slow-query-log=1 slow_query_log_file="D:\mysql_slow.log" long_query_time=2
-
-
(2) 数据库连接池
数据库连接池简介
-
数据库连接池 是个容器,负责分配、管理数据库连接(Connection)
-
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
-
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏好处:
- 资源重用。
- 提升系统响应速度。
- 避免数据库连接遗漏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HM1yXSU-1666515575326)(/34.png)]
-
数据库连接池实现
- 标准接口: DataSource
- 官方(SUN)提供的数据库连接池标准接口,由第三方组织实现此接口。
- 功能:获取连接
Connection getConnection()
- 常见的数据库连接池
- DBCP
- C3P0
- Druid
- Druid(德鲁伊)
- Druid连接池是阿里巴巴开源的数据库连接池项目
- 功能强大,性能优秀,是Java语言最好的数据库连接池之一
- 标准接口: DataSource
-
Druid使用步骤
- 导入jar包,druid-1.1.12.jar
- 定义配置文件
- 加载配置文件
- 获取数据库连接池对象
- 获取连接
3. Maven
- Maven是专门用于管理和构建Java项目的工具,它的主要功能有:
- 提供了一套标准化的项目结构
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jt6Lbr3V-1666515575326)(/35.png)]
- 提供了一套标准化的构建流程(编译,测试,打包,发布…)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ps7h0TOJ-1666515575326)(/36.png)]
- Maven提供了一套简单的命令来完成项目构建
- 提供了一套依赖管理机制
- 依赖管理
- 依赖管理其实就是管理你项目所依赖的第三方资源(jar包、插件…)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SdEPUh04-1666515575326)(/37.png)]
- 依赖管理
- Maven 提供了一套标准化的项目结构,所有IDE使用Maven构建的项目结构完全一样,所有IDE创建的Maven项目可以通用
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PaoJLSV2-1666515575326)(/38.png)]
(1) Maven安装配置
-
解压apache-maven-3.6.1.rar既安装完成
-
配置环境变量MAVEN_HOME为安装路径的bin目录
-
配置本地仓库:修改conf/settings.xml中的为一个指定目录
-
配置阿里云私服:修改conf/settings.xml中的标签,为其添加如下子标签:
-
<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>
-
(2) Maven 基本命令
- compile :编译
- clean:清理
- test:测试
- package:打包
- install:安装
(3) Maven生命周期
- Maven构建项目生命周期描述的是一次构建过程经历经历了多少个事件
- Maven对项目构建的生命周期划分为3套
- clean:清理工作
- default:核心工作,例如编译,测试,打包,安装等
- site: 产生报告,发布站点等
- 同一生命周期内,执行后边的命令,前面的所有命令会自动执行
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6VNZ1ik-1666515575326)(/39.png)]
(4)IDEA配置Maven
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DzoGx9a0-1666515575327)(/40.png)]
Maven坐标详解
- 什么是坐标?
- Maven 中的坐标是资源的唯一标识
- 使用坐标来定义项目或引入项目中需要的依赖
-
Maven坐标主要组成
- groupld:定义当前Maven项目隶属组织名称(通常是域名反写,例如: com.itheima)
- artifactld:定义当前Maven项目名称(通常是模块名称,例如order-service、goods-service)
- version:定义当前项目版本号
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6dwrhZK8-1666515575327)(/41.png)]
-
使用坐标导入jar包
- 在pom.xml中编写标签
- 在标签中使用引入坐标
- 定义坐标的groupld,artifactld,version
- 点击刷新按钮,使坐标生效
-
自动导入
- 选择IDEA中File --> Settings
- 在弹出的面板中找到Build Tools
- 选择Any changes,点击ok即可生效
依赖范围
- 通过设置坐标的依赖范围(scope),可以设置对应jar包的作用范围:编译环境、测试环境、运行环境
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3gIFqggX-1666515575327)(/42.png)]
- 默认值:compile
4. MyBatis
- 什么是mybatis
- MyBatis是一款优秀的持久层框架,用于简化JDBC 开发
MyBatis本是 Apache的一个开源项目iBatis, 2010年这个项目由apache softwarefoundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github - 官网: https://mybatis.org/mybatis-3/zh/index.html
- MyBatis是一款优秀的持久层框架,用于简化JDBC 开发
- 持久层
- 负责将数据到保存到数据库的那一层代码
- JavaEE三层架构:表现层、业务层、持久层
- 框架
- 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
- 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
- JDBC缺点
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LxEkozF5-1666515575327)(/43.png)]
- MyBatis简化
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geBkByHd-1666515575327)(/44.png)]
MyBatis快速入门
- 查询user表中所有数据
- 创建user表,添加数据
- 创建模块,导入坐标
- 编写MyBatis核心配置文件–>替换连接信息解决硬编码问题
- 编写SQL映射文件–>统一管理sql语句,解决硬编码问题
- 编码
- 定义POJO类
- 加载核心配置文件,获取SqlSessionFactory 对象
- 获取SqlSession对象,执行SQL语句
- 释放资源
解决SQL映射文件的警告提示
- 产生原因:Idea和数据库没有建立连接,不识别表信息
- 解决方式:在Idea中设置MySQL数据库连接
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ZsGe7F2-1666515575327)(/45.png)]
Mapper代理开发
- 目的
- 解决原生方式中的硬编码
- 简化后期执行SQL
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zTzwMplA-1666515575328)(/46.png)]
- 使用Mapper代理步骤
- 定义与SQL映射文件同名的Mapper接由,并且将Mapper接口和SQL映射文件放置在同一目录下
- 命名的时候记得用“/”作为分隔符
- 设置SQL映射文件的namespace属性为Mapper接口全限定名
- 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
- 编码
- 通过SqlSession的getMapper方法获取Mapper接口的代理对象
- 调用对应方法完成sql的执行
- 定义与SQL映射文件同名的Mapper接由,并且将Mapper接口和SQL映射文件放置在同一目录下
- 细节:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jscuzemm-1666515575328)(/47.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OK0krFP4-1666515575328)(/48.png)]
Mybatis核心配置 文件
-
environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
-
MyBatis核心配置文件的顶层结构如下:
-
configuration(配置)
-
properties(属性)
-
settings(设置)
-
otypeAliases(类型别名)
-
typeHandlers(类型处理器)
-
objectFactory (对象工厂)o
-
plugins (插件)
-
oenvironments(环境配置)
-
environment(环境变量)
-
transactionManager (事务管理器)-
-
dataSource(数据源)
-
databaseldProvider (数据库厂商标识)
-
mappers(映射器)
-
-
-
设置别名(typeAliases)
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6iIRvhwO-1666515575328)(/49.png)]
-
细节:配置各个标签时,需要遵守前后顺序
-
查询名字不一致问题解决
-
数据库表的字段名称―和―实体类的属性名称不一样,则不能自动封装数据 *起别名:对不一样的列名起别名,让别名和实体类的属性名一样 *缺点:每次查询都要定义一次别名 *sql片段 * resultMap 1.定义<resultMap>标签 2.在<select>标签中,使用resultMap属性替换resultType属性
-
<sql id="brand_column"> id,brand_name as brandName,company_name as companyName,ordered,description,status</sql> <select id="selectAll" resultType="brand" > <select <include refid="brand_column"/>from tb_brand; </select>
-
resultMap
-
<resultMap id=" brandResultMap" type="brand"> <result column="brand_name" property="brandName"/> <result column="company_name" property="companyName"/></resultMap> <select id="selectAll" resultMap="brandResultHapl"> <select * from tb_brand; </select>
-
id:唯一标识
-
type:映射的类型,支持别名
- id:完成主键字段的映射
- column:表的列名
- property:实体类的属性名
- result:完成一般字段的映射
- column:表的列名
- property:实体类的属性名
- id:完成主键字段的映射
-
参数占位符
-
#{ }∶会将其替换为?,为了防止SQL注入
-
${}:拼sql。会存在SQL注入问题
-
使用时机:
- *参数传递的时候:#{}
- *表名或者列名不固定的情况下:${科会存在SQL注入问题
-
参数类型:parameterType:可以省略
-
<select id="selectById"resultMap= "brandResultMap" parameterType="int"> select * from tb_brand where id = #{id}; </select>
-
-
特殊字符处理:
-
转义字符
-
CDATA区
-
<select id="selectById"resultMap= "brandResultMap"> select * from tb_brand where id <![CDATA[ < 1I]]> #{id}; </select>
-
-
条件查询
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ZbwcH3d-1666515575328)(/50.png)]
- SQL语句设置多个参数有几种方式?
- 1**)散装参数**:需要使用@Param(“sQL中的参数占位符名称”)
- 2)实体类封装参数
- *只需要保证sQL中的参数名和实体类属性名对应上,即可设置成功
- map集合
- *只需要保证sQL中的参数名和map集合的键的名称对应上,即可设置成功
动态条件查询
-
多条件
-
SQL语句会随着用户的输入或外部条件的变化而变化,我们称为动态SQL
-
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brandwhere status = #{status} and company_name like #{companyName}and brand_name like #{brandName} </select>
-
MyBatis对动态SQL有很强大的支撑:
-
if:条件判断
-
test:逻辑表达式
-
问题
- 恒等式(1=1)
- 替换 where 关键字
-
<select id="selectByCondition" resultMap="brandResultMap"> <where> select * from tb_brand where <if test="status != null"> status = #{status} </if> <if test="companyName != null and companyName != '' "> and company_name like #{companyName} </if> <if test="brandName != null and brandName != '' "> and brand_name like #{brandName} </if> </where> </ select>
-
-
choose (when, otherwise)
-
trim (where, set)
-
foreach
-
-
-
单条件
-
从多条件中选择一个
-
choose (when, otherwise):选择,类似于Java中的switch语句
-
<select id="selectByConditionSingle" resultMap="brandResultMap"> select * from tb_brand where <choose> <!--类似于switch--> <when test="status != null"><!--类似于case--> status = #{status} </when> <when test="companyName != null and companyName !=""> company_name like #{companyName} </when> <when test="brandName != null and brandName !=" "> brand_name like #{brandName} </when> <otherwise> <!--类似于default--> 1 =1 </otherwise> </choose> </select>
添加
-
编写接口方法:Mapper接口
void add(Brand brand)
- 参数:除了id之外的所有数据
- 结果: void
-
编写SQL语句:SQL映射文件
<insert id="add"> insert into tb_brand (brand_name, company_name, ordered, description, status) values ({brandName},#{companyName},#{ordered},#{description},#{status}); </insert>
-
执行方法,测试
- MyBatis事务:
- openSession():默认开启事务,进行增删改操作后需要使用sqlSession.commit(;手动提交事务
- openSession(true):可以设置为自动提交事务(关闭事务)
- MyBatis事务:
-
添加——主键返回
- 在数据添加成功后,需要获取插入数据库数据的主键的值
- 比如:添加订单和订单项
- 添加订单
- 添加订单项,订单项中需要设置所属订单的id
useGeneratedKeys="true" keyProperty="id"
- 比如:添加订单和订单项
<insert id="addOrder" useGeneratedKeys="true" keyProperty="id">
insert into tb_order (payment,payment_type,status)
values (#{payment},#{paymentType},#{status});
</insert>
<insert id="addOrderltem" >
insert into tb_order_item (goods_name, goods_price, count,order_id)
values (#{goodsName},#{goodsPrice},#{count,t#{orderld});
</insert>
修改
-
修改全部字段
-
编写接口方法: Mapper接口
void update(Brand brand);
-
参数:所有数据
-
结果: void
-
编写SQL语句:SQL映射文件
-
<update id="update">update tb_brand set brand_name = #{brandName}, company_name = #{companyName},ordered = #{ordered}, description = #{description},status = #{status} where id = #{id}; </update>
-
-
执行方法,测试
-
-
修改动态字段
- 编写接口方法: Mapper接口
- 参数:部分数据,封装到对象中
- 结果: void
-
编写SQL语句: SQL映射文件
-
执行方法,测试
<update id="update"> update tb_brand <set> <if test="brandName != null and brandName !=" "> brand_name = #{brandName}, </if> <if test="companyName != null and companyName !=""> company_name = #{companyName}, </if> <if test="ordered != null"> ordered = #{ordered}, <lif> <if test="description != null and description !=""> description = #{description}, </if> <if test="status != null"> status = #{status} <lif> </set> where id = #{id}; </update>
删除
-
删除一个
-
编写接口方法: Mapper
- 接口参数: id
- 结果: void
void deleteByld(int id);
-
编写SQL语句:SQL映射文件
-
<delete id="deleteByld"> delete from tb_brand where id = #{id} </delete>
-
-
执行方法,测试
-
-
批量删除
-
编写接口方法: Mapper接口
- 参数: id数组
- 结果: void
void deleteBylds(@Param("ids") int[]ids);
-
编写SQL语句: SQL映射文件
-
<delete id="deleteBylds"> delete from tb_brand where id in (?,?,?) </delete>
-
mybatis会将数组参数,封装为一个Map集合。
- *默认: array =数组
- *使用@Param注解改变map集合的默认key的名称
-
<delete id="deleteBylds"> delete from tb_brand where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
-
-
执行方法,测试
-
参数传递
-
MyBatis 接口方法中可以接收各种各样的参数,MyBatis底层对于这些参数进行不同的封装处理方式
-
单个参数
- POJO类型:
- Map集合:
- Collection:
- List:
- Array
- 其他类型:
-
多个参数:封装为Map集合
-
map.put( " arg0",参数值1) map.put( " param1",参数值1) map.put( "param2",参数值2) map.put( "agr1",参数值2)
-
User select(@Param("username") String username,@Param("Mpassword")String password);
-
<select id="select" resultType="user"> select * from tb_user where username = #{username}and password = #{password}; </select>
-
-
MyBatis提供了ParamNameResolver类来进行参数封装
-