0%

“死锁”的形成

多个线程各自占有一些公共资源,并且互相等待其它线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.sympa.lesson01;

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock{
public static void main(String[] args) {
Play boy = new Play();
new Thread(boy, "Bob").start();
new Thread(boy, "Alice").start();
}
}

class Toy{

}

class Play implements Runnable {
Toy toy1 = new Toy();
Toy toy2 = new Toy();
@Override
public void run() {
gg();
}
public void gg(){
if(Thread.currentThread().getName().equals("Bob")){
synchronized (toy1){
System.out.println("Bob的toy1");
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
synchronized (toy2){
System.out.println("Bob的toy2");
}
}
}
else{
synchronized (toy2){
System.out.println("Alice的toy2");
try{
Thread.sleep(2000);
} catch (InterruptedException e){
e.printStackTrace();
}
synchronized (toy1){
System.out.println("Alice的toy1");
}
}
}
}
}

不安全的买票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.sympa.lesson01;

//线程不安全,有负数
public class Tickets01 implements Runnable{

private int ticket = 10;
@Override
public void run() {
while(true) {
if (ticket <= 0) break;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread() 获得当前线程对象的引用
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticket-- + "张票");
}
}

public static void main(String[] args){
Tickets01 tickets01 = new Tickets01();
new Thread(tickets01, "黄牛1号").start();
new Thread(tickets01, "黄牛2号").start();
new Thread(tickets01, "黄牛3号").start();
}
}

并发:多个线程访问同个对象

线程同步:队列 + 锁

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

同步方法及同步块

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
  • 同步方法:public synchronized void method(int args){}
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
  • 缺陷:若将一个大的方法声明为synchronized,将会影响效率
  • 方法里需要修改的内容才需要锁,锁的太多,浪费资源
  • 同步块:synchronized(Obj){}
  • Obj称之为同步监视器
    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,
    • 或者是class[反射中讲解]
  • 同步监视器执行过程
  1. 第一个线程访问,锁定同步监视器,执行其中代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没锁,然后锁定并访问

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

停止线程

  • 不推荐使用JDK提供的stop(), destroy()方法
  • 推荐线程自己停止
  • 建议使用标志位flag,flag=false时,终止线程运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestThread03 implements Runnable{
private boolean flag = false;
@Override
public void run(){
//run方法线程体
while(!flag){
System.out.println("我在看片");
}
}
public void stop(){
this.flag = true;
System.out.println("不看了");
}
public static void main(String[] args){
//创建runnable接口的实现类对象
TestThread03 testThread3 = new TestThread03();

//创建线程对象,通过线程对象来开启我们的线程,代理
new Thread(testThread3).start();
for (int i = 0; i < 200; i++) {
if(i == 100) testThread3.stop();
System.out.println("我在学习" + i);
}
}
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.sympa.lesson01;

//模拟网络延时:放大问题的发生性
public class Tickets01 implements Runnable{

private int ticket = 10;
@Override
public void run() {
while(true) {
if (ticket <= 0) break;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread() 获得当前线程对象的引用
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticket-- + "张票");
}
}

public static void main(String[] args){
Tickets01 tickets01 = new Tickets01();
new Thread(tickets01, "黄牛1号").start();
new Thread(tickets01, "黄牛2号").start();
new Thread(tickets01, "黄牛3号").start();
}
}

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 让线程由运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功,看CPU心情

线程强制执行Join

  • Join合并线程,待此线程执行完成后,再执行其它线程,其它线程阻塞
  • 可以想象成插队

观测线程状态

Thread.State

线程状态,线程可以处于以下状态之一:

  • NEW:尚未启动的线程处于此状态
  • RUNNABLE:在Java虚拟机中执行的线程处于此状态
  • BLOCKED:被阻塞等待监视器锁定的线程处于此状态
  • WAITING:正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED:已退出的线程处于此状态

一个线程可以在给定时间点处于一个状态,这些状态是不反映任何操作系统线程状态的虚拟机状态

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有课程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 线程的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用getPriority().setPriority(int xxx)来改变或获取优先级
  • 优先级的设定建议在start()调度前
  • 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,都是看CPU的调度(性能倒置)

守护(daemon)线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等
1
2
3
Thread thread = new Thread();
thread.setDaemon(true); //默认是false代表用户线程,正常线程都是用户线程
thread.start(); //守护线程启动

Lambda表达式的推导过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.sympa.lesson01;

public class lambda01 {

//3.静态内部类
static class Lambda2 implements Love{
@Override
public void lambda() {
System.out.println("lambda2");

}
}

public static void main(String[] args){

Love lam = new Lambda1();

//4.局部内部类
class Lambda3 implements Love{
@Override
public void lambda() {
System.out.println("lambda3");
}
}

//5.匿名内部类,没有类的名称,只能借助接口和父类
lam = new Love(){
@Override
public void lambda() {
System.out.println("lambda4");
}
};

//lambda表达式
lam = () -> {
System.out.println("lambda5");
};
}
}

//1.定义一个函数式接口
interface Love{
void lambda();
}
//2.实现类
class Lambda1 implements Love{
@Override
public void lambda() {
System.out.println("lambda1");
}
}

实现runnable接口。重写run方法,执行线程需要丢入runnable接口实现类,调用start方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.sympa.lesson01;

//创建线程方式2:实现runnable接口。重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread03 implements Runnable{
@Override
public void run(){
//run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看片\n" + i);
}
}
public static void main(String[] args){
//创建runnable接口的实现类对象
TestThread1 testThread3 = new TestThread1();

//创建线程对象,通过线程对象来开启我们的线程,代理
new Thread(testThread3).start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习\n" + i);
}
}
}
  • 继承Thread类

    • 子类继承Thread类具备多线程能力
    • 启动线程:子类对象.start()
    • 不建议使用:OPP单继承局限性
  • 实现Runnable接口

    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start()
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

继承Thread类,重写run()方法,调用start并开启线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.sympa.lesson01;

//创建线程方式一:继承Thread类,重写run()方法,调用start并开启线程
public class TestThread1 extends Thread{
@Override
public void run(){
//run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看片\n" + i);
}
}
public static void main(String[] args){
//main线程,主线程

//创建一个线程对象
TestThread1 testThread1 = new TestThread1();

//调用start()方法开启线程
testThread1.start();
//线程开启不一定立即执行,由CPU调度执行
for (int i = 0; i < 200; i++) {
System.out.println("我在学习\n" + i);
}
}
}

练习Thread,实现多线程下载图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.sympa.lesson01;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//练习Thread,实现多线程下载图片
public class TestThread02 extends Thread{
private String url;
private String name;
public TestThread02(String url, String name){
this.url = url;
this.name = name;
}

@Override
public void run(){
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载了文件名为:" + name);
}

public static void main(String[] args){
TestThread02 t1 = new TestThread02("https://sympawang.com/images/7%E7%A7%8Djoin.png", "01.jpg");
TestThread02 t2 = new TestThread02("https://sympawang.com/images/7%E7%A7%8Djoin.png", "02.jpg");
TestThread02 t3 = new TestThread02("https://sympawang.com/images/7%E7%A7%8Djoin.png", "03.jpg");
t1.start();
t2.start();
t3.start();
}
}

//下载器
class WebDownloader{
//下载方法
public void downloader(String url, String name){
try{
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e){
e.printStackTrace();
System.out.println("IO异常\n");
}
}
}

jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。

Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sql语句,executeUpdate执行完后,将会返回一个整数(即增删改语句导致了数据库几行数据发生了变化)。

Statement.executeQuery方法用于向数据库发送查询语句,executeQuery方法返回代表查询结果的ResultSet对象。

CRUD操作

  • create,使用executeUpdate(String sql)方法完成数据添加操作,实例操作:
1
2
3
4
5
6
Statement st = conn.createStatement();
String sql = "insert into user(...) values(...)";
int num = st.executeUpdate(sql);
if(num > 0){
System.out.println("插入成功");
}
  • delete,数据删除操作,实例操作:
1
2
3
4
5
6
Statement st = conn.createStatement();
String sql = "delete from user where [...]";
int num = st.executeUpdate(sql);
if(num > 0){
System.out.println("删除成功");
}
  • update,数据修改操作,实例操作:
1
2
3
4
5
6
Statement st = conn.createStatement();
String sql = "update user set name='' where name=''";
int num = st.executeUpdate(sql);
if(num > 0){
System.out.println("修改成功");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.sympa.lesson01;

import java.sql.*;

//我的第一个JDBC程序
public class JdbcFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载驱动
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.cj.jdbc.Driver"); //固定写法,加载驱动

//2.用户信息和url
// jdbc:mysql://主机地址:端口号(mysql默认3306)/数据库名?参数1&参数2&....
String url = "jdbc:mysql://127.0.0.1:3306/jdbcstudy?serverTimezone=UTC&userUnicode=true&characterEncoding=utf8&useSSL=false";
String username = "root";
String password = "";

//3.连接成功,数据库对象 Connection 代表数据库
//数据库级别操作 设置自动提交,事务提交,事务回滚
//rollback() commit() setAutoCommit()
Connection connection = DriverManager.getConnection(url, username, password);

//4.执行SQL的对象
//查询返回结果集 .executeQuery()
//更新插入删除,返回影响行数 .executeUpdate()
//任何SQL .execute()
Statement statement = connection.createStatement();

String sql = "select * from users";
ResultSet resultSet = statement.executeQuery(sql); //返回的结果集
//获得指定数据类型
//不知道列类型.getObject() 知道列类型.getInt()等
while(resultSet.next()){
System.out.println("id=" + resultSet.getObject("id"));
System.out.println("name=" + resultSet.getObject("name"));
System.out.println("password=" + resultSet.getObject("password"));
System.out.println("email=" + resultSet.getObject("email"));
System.out.println("birthday=" + resultSet.getObject("birthday"));
}
//5.释放连接
resultSet.close();
statement.close();
connection.close();
}
}

用户管理

  • SQL可视化管理

  • SQL命令操作

    用户表:mysql.user

    本质:对用户表增删改查

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    -- 创建用户
    create user [用户名] identified by [密码]

    -- 修改密码(当前用户)
    set password = password([])

    -- 修改密码(指定)
    set password for [] = password([])

    -- 重命名
    rename user [] to []

    -- 用户授权(授予全部权限,除了给别的用户授权)
    grant all privileges on [库名].[表名] to [用户名]

    -- 查询权限
    show grants for [用户]

    -- 撤销全部权限
    revoke all privileges on [库名].[表名] to [用户名]

    -- 删除用户
    drop user []

MySQL备份

MySQL数据库备份方式

  • 拷贝物理文件
  • 在Sqlyog这种可视化工具中手动导出
    • 在想要导出的表或者库中选择备份或导出
    • 导出后拉进来就行了
  • 使用命令行 mysqldump
    • mysqldump -主机名 -用户名 -密码 表名 >路径