Java基础(全面下)

13 IO流

13.1 IO流

在程序中,对象、集合这些数据存储是暂时的,一旦程序结束,数据就会丢失

为了能永久的保存程序创建的数据,需要将这些数据存放到磁盘中,以就可以持久性的使用数据了

在Java中,通过I/O流技术可以将数据保存到本地磁盘的文件中或者二进制文件中,可以达到永久存储数据的要求

在Java中,所有的数据都可以使用流来完成读写

流就是一组有序的数据序列,将数据从一个地方带到另一个地方

根据流向的不同,可以分输入流(Input Stream) 和输出流(Output Stream)

输入流: 就将数据从输入设备(键盘、文件)中内容读取到内存中

输出流 :将数据写入到输出设备(显示器、文件、磁盘)

13.1.1 Java中,I/O流的分类

按照流向 主要分为 : 输入流和输出流

按照流数据单位的不同分为 :字节流和字符流

按照功能可以分为 : 节点流 和处理流

13.1.2 Java中的系统流

Java的System类,封装了程序运行时的3个系统流,分别是 :in 、 out 、err

System.in : 标准输入流,默认设备是 键盘 ,是InputStream类的一个对象

System.out: 标准输出流,默认设备是控制台

System.err : 标准错误流,默认设备是控制台

System.out和System.err 是 PrintStream的对象

public class DemoIO {
    public static void main(String[] args) {

        byte[] bytes = new byte[1024];

        System.out.println("请输入内容:");
        try {
            System.in.read(bytes); //通过read()方法也能实现文件的读取
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("输入的内容是:");
        for (int i = 0; i < bytes.length; i++) {
            System.out.print((char) bytes[i]);
        }
    }
}

13.2 File类

文件其实就是用来存放数据的,将来文件既可以作为输入设备,也可以作为输出设备。

文件和目录路径名的抽象表示。

构造方法

File(File parent, String child)

从父抽象路径名和子路径名字符串创建新的 File实例。

File(String pathname)

通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例

File(String parent, String child)

从父路径名字符串和子路径名字符串创建新的 File实例。

File(URI uri)

通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。

普通方法

boolean delete()

删除由此抽象路径名表示的文件或目录。

boolean exists()

测试此抽象路径名表示的文件或目录是否存在。

String getAbsolutePath()

返回此抽象路径名的绝对路径名字符串。

String getName()

返回由此抽象路径名表示的文件或目录的名称。

String getPath()

将此抽象路径名转换为路径名字符串。

boolean isDirectory()

测试此抽象路径名表示的文件是否为目录。

boolean isFile()

测试此抽象路径名表示的文件是否为普通文件。

long length()

返回由此抽象路径名表示的文件的长度。

boolean mkdir()

创建由此抽象路径名命名的目录。

boolean createNewFile()

当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。

Java中,路径地址的写法 C:\\xxx\\xxx ,写在字符串中的\有转义符的意思,写地址需要写两个\\

或者写成 C:/XX/XX

public class FileDemo {
    public static void main(String[] args) throws IOException {

        //指定文件的路径,创建文件对象
        File file = new File("D:\\hello.txt");
        //判断是否存在
        System.out.println(file.exists());
        //创建文件
        file.createNewFile();
        //判断是否存在
        System.out.println(file.exists());
        //length()
        System.out.println(file.length());
        //getName()
        System.out.println(file.getName());
        //getPath()
        System.out.println(file.getPath());
        //delete()
        System.out.println(file.delete()); //true
        //判断是否存在
        System.out.println(file.exists());//false
        //判断是否是目录
        System.out.println(file.isDirectory()); //false
    }
}

13.3 IO流常用类

按照流向划分的类 :

输入流 : InputStream\Reader

输出流: OutputStream\Writer

按照处理单位划分的类 :

字节流:InputStream\OutputStream

字符流 :Reader\Writer

13.3.1 InputStream类

InputStream是一个抽象类,这个抽象类是表示输入字节流的所有类的超类。

常用子类

ByteArrayInputStream 将字节数组转为字节输入流,从中读取数据

FileInputStream 从文件中读取数据

ObjectInputStream 将对象反序列化

PipedInputStream 连接管道输出流PipedOutputStream

SequenceInputStream 将多个字节输入流串联成一个字节输入流

13.3.1.1 FileInputStream的使用

从文件系统中的文件获取输入字节

构造方法

FileInputStream(File file)

通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

FileInputStream(String name)

通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名name命名。

public class FileInputStreamDemo {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            //创建文件的输入流对象
            //new FileInputStream(new File("D:\\hello.txt"));
            fis = new FileInputStream("D:\\hello.txt");
            System.out.println(fis.available());  //返回可读取文件字节数
            //读取内容read()方法读完内容后,返回-1
            //System.out.println(fis.read());
            //System.out.println(fis.read());
            int data;
            //read()每次读到一个8位字节,把它转成0-255之间的整数返回
            //在循环体中调用read()方法,必须要给方法结果赋值,
            // 不然每次调用read()方法,都是在读取一次内容,
            // 读了之后,需要将结果返回出去
            while ((data = fis.read()) != -1){
                System.out.print((char) data);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fis != null){  //防止空指针异常
                    fis.close(); //关闭资源
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
13.3.2 OutputStream类

这个抽象类是表示字节输出流的所有类的超类

ByteArrayOutputStream 向字节数组中写数据

FileOutputStream 向文件中写数据

ObjectOutputStream 将对象序列化

PipedOutputStream 连接PipedIutputStream

13.3.2.1 FileOutputStream 的使用

构造方法

FileOutputStream(File file)

创建文件输出流以写入由指定的 File对象表示的文件。

FileOutputStream(String name)

创建文件输出流以指定的名称写入文件。

public class FileOutPutDemo {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
            //创建fileOutput对象
            fos = new FileOutputStream("D:\\hello.txt");
            //写内容
            String s = "abcdefg,南京邮电大学";
            //将字符串转为字节数组
            byte[] bytes = s.getBytes();
            //调用write方法,写内容
            fos.write(bytes,0,bytes.length);
            System.out.println("文件已更新!");
        }  catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fos != null){
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
13.3.3 Reader类

用于读取字符流的抽象类

常用子类

BufferedReader 为其他的字符输入流提供读缓冲区

CharArrayReader 将字符数组转为字符输入流,从中读取字符

InputStreamReader 将字节输入流转为字符输入流,可以指定字符编码

StringReader 将字符串转为字符输入流,从中读取字符

FileReader InputStreamReader的子类,一般用来读取文件相关字符流

13.3.3.1 FileReader类

构造方法

FileReader(File file)

创建一个新的 FileReader ,给出 File读取。

FileReader(String fileName)

创建一个新的 FileReader ,给定要读取的文件的名称。

public class FileReaderDemo {
    public static void main(String[] args) {
        FileReader fileReader = null;
        try {
            //先创建FileReader对象
            fileReader = new FileReader("D:\\hello.txt");

            //创建一个字符数组
            char[] chars = new char[1024];
            StringBuffer sbf = new StringBuffer();
            //读取内容到字符数组
            int length;
            while ( (length = fileReader.read(chars)) != -1){
               sbf.append(chars);
            }
            System.out.println(sbf.toString());
            //System.out.println(fileReader.read());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fileReader!= null){
                    fileReader.close();}
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

使用fileReader配合BufferedReader完成内容读取

public class FileReaderDemo02 {
    public static void main(String[] args) {
        //使用fileReader配合BufferedReader完成内容读取
        FileReader fileReader = null;
        BufferedReader bufferedReader = null;
        try {
            //先创建FileReader对象
            fileReader = new FileReader("D:\\hello.txt");
            //创建BufferedReader对象
            bufferedReader = new BufferedReader(fileReader);

            //通过BufferedReader读取内容
            String s;
            while ((s = bufferedReader.readLine()) != null){
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (bufferedReader != null){
                    bufferedReader.close();
                }
                if (fileReader != null){
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        }
    }
}
13.3.4 Writer类

用于写入字符流的抽象类

常用子类

BufferedWriter 为其他输出流提供写缓冲区

CharArrayWriter 向内存缓冲区的字符数组写数据

OutputStreamWriter 将字节输入流,转为字符输出流,可以指定编码格式

FileWriter OutputStreamWriter的子类,用来向文件中写数据

 13.3.4.1 FileWriter的使用

构造方法

FileWriter(File file)

给一个File对象构造一个FileWriter对象。

FileWriter(String fileName)

构造一个给定文件名的FileWriter对象。

public class FileWriteDemo {
    public static void main(String[] args) {
        try {
            //创建fileWriter对象
            FileWriter fileWriter = new FileWriter("D:\\hello.txt");
            //创建BufferedWriter对象
            BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
            //写内容
            //fileWriter.write("你好");
            //fileWriter.write("我是南京邮电大学的学生");
            //fileWriter.write("我今年20岁");
            //fileWriter.flush();
            bufferedWriter.write("你好");
            bufferedWriter.write("我是南京邮电大学的学生");
            bufferedWriter.newLine();
            bufferedWriter.write("我今年20岁");
            bufferedWriter.flush(); //刷新缓冲区

            //再读取内容
            FileReader fileReader = new FileReader("D:\\hello.txt");
            BufferedReader bufferedReader = new BufferedReader(fileReader);
            String s ;
            while ((s = bufferedReader.readLine())!= null){
                System.out.println(s);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //释放资源
        }
    }
}

13.4 序列化、反序列化

1,序列化 : 指的是,将Java对象转换为字节序列的过程,可以持久化存储到磁盘或者在网络上传输

2,反序列化:将字节序列再恢复成Java对象的过程

作用 :在传递或者保存对象的时候,保证对象的完整性和 可传递性

怎么实现序列化和反序列化?

序列化和反序列化的对象,要实现一个Serializable接口

Serializable接口中没有任何方法需要实现类实现,主要的作用就是用来标识可序列化的语义

实现Serializable接口 的类,才可以被 ObjectInputStream和ObjectOutputStream识别,才可以被读取或者写出

public class Demo {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        ObjectOutputStream ous = null;
        try {

            //序列化对象
            ous = new ObjectOutputStream(new FileOutputStream("D:\\student.txt"));

            //创建要被序列化的对象
            Student s1 = new Student(1001, "jack", "1班");
            System.out.println(s1);

            //将对象序列化
            ous.writeObject(s1);

            //将对象反序列化
            //反序列化对象
            ois = new ObjectInputStream(new FileInputStream("D:\\student.txt"));
            //反序列化返回的是Object对象,需要强转为Student
            Object object = ois.readObject();
            Student s2 = (Student) object;

            System.out.println(s2);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {

            try {
                if (ois != null){ois.close();}
                if (ous != null){ ous.close();}
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


public class Student implements Serializable {
    private int sid;
    private String name;
    private String className;

    public Student(int sid, String name, String className) {
        this.sid = sid;
        this.name = name;
        this.className = className;
    }



    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", name='" + name + '\'' +
                ", className='" + className + '\'' +
                '}';
    }
}

通过IO流实现二进制文件的读写

/*
二进制文件的读写(文件复制)
 */
public class BinaryFileIO {
    public static void main(String[] args) {
        //DataInputStream  DataOutputStream
                FileInputStream fis = null;
                DataInputStream dis =null;
                FileOutputStream fos =null;
                DataOutputStream dos =null;
        try {
            //创建文件输入流FileInputStream
            fis = new FileInputStream("D:\\1.jpg");
            //通过文件输入流创建 数据输入流DataInputStream
            dis = new DataInputStream(fis);
            //创建文件输入流FileInputStream
            fos = new FileOutputStream("E:\\abc.jpg");
            //通过文件输入流创建 数据输入流DataInputStream
            dos = new DataOutputStream(fos);
            int data;
            //读的同时,将内容写出去
            while ((data = dis.read()) != -1){
                dos.write(data);
            }
            System.out.println("文件复制成功");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                dos.close();
                fos.close();
                dis.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

13.5 计算机中的编码格式

在计算机中,任何文字都是以指定的编码方式存在的

Java中常见的编码格式 : ISO8859-1,GBK,UTF编码,Unicode

编码格式说明 :

ISO8859-1 : 属于单字节编码,最多只能标识0-255的字符范围

GBK: 中文的国标编码,用来标识汉字,属于双字节编码,可以用来标识简体字和繁体字

UTF: 兼容了 ISO8859-1编码,同时又可以用来表示所有的语言字符,UTF是不定长编码,每个字符长度为1-6个字节不等,一般在中文网页使用此编码,可以节省空间,常用的一般是UTF-8

Unicode: 是一种编码规范,为了解决全球字符通用编码而设计的。UTF-8和UTF-16都是这个规范的一种实现,不兼容ISO8859-1 ,Java内部采用次编码规范

IO流中解决乱码问题:

public class BufferedReaderDemo {
    public static void main(String[] args) {
        try {
            FileInputStream is = new FileInputStream("D:\\hello.txt");

            //指定编码格式,格式跟文件的编码格式对应
            InputStreamReader isr = new InputStreamReader(is, "GBK");
            BufferedReader bufferedReader = new BufferedReader(isr);
            String data ;
            while ( (data = bufferedReader.readLine()) != null){
                System.out.println(data);
            }
            //int data;
            //while ( (data = is.read()) != -1){
            //    System.out.println((char) data);
            //}
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

13.6 网络编程

Java编写的程序,需要在网络上运行,Java程序也需要实现在网络上的数据连通。

Java中提供了一些网络相关的类库,可以让用户实现网络连接

Java实现了一个跨平台的网络库,程序员将来可以使用这种统一的编程环境完成网络连接功能

13.6.1 计算机网络

计算机网络指的是,通过通信线路,将地理位置不同的计算机连接起来,在操作系统、网络管理软件、网络通信协议的管理下,完成信息传递以及资源的共享

网络通信协议:

计算机网络中实现通信功能,必须要遵循的一些约定,就是通信协议

网络编程实通信的关键点:

        1、如何准确的定位网络上的一台或者多台主机?

                通过ip地址定位主机,通过端口号定位主机上的应用

        2、找到主机后,怎么进行高效可靠的数据传输?

                通过规定的传输协议,TCP/UDP协议

13.6.2 网络编程三要素

        1、IP地址:        InetAddress

        2、端口号

        3、TCP/UDP协议

13.6.2.1 InetAddress

此类表示Internet协议(IP)地址。IP地址是每台计算 机在网络上的唯一标识地址

本地主机的IP地址 : 127.0.0.1 ,主机名 : localhost

        IP地址分类 : 分类方式1: IPV4和IPV6

         IPV4:由4个字节,一共32位二进制数值组成, 4 个 0-255范围的数值

        IPV6:128位,16个字节,写成8个无符号整数, 每个整数用4个16进制标识,数字之间用:隔开

        分类方式2: 公网地址和私有地址, 192.168 开头的就是私有 地址

public class IneAddressDemo {
 public static void main(String[] args) throws UnknownHostException {
 //1,直接通过主机名称,来获取主机IneAddress地址对象
 InetAddress address1 = InetAddress.getByName("localhost");
 System.out.println(address1);
 InetAddress address2 = InetAddress.getByName("127.0.0.1");
 System.out.println(address2);
 //2,static InetAddress getLocalHost()
 //返回本地主机的地址。 计算机名称 + ip地址
 InetAddress address3 = InetAddress.getLocalHost();
 System.out.println(address3);
 //3,获取某个网站的地址
 InetAddress address4 = InetAddress.getByName("www.baidu.com");
 System.out.println(address4);
 //其他方法的使用
 System.out.println(address1.getAddress());
 System.out.println(address1.getHostName());
 System.out.println(address1.getHostAddress());
 }
}
13.6.2.2 端口号使用

端口用来表示计算机上运行的程序(具体的某个应用)

网络通信,本质上就是应用程序的通信,端口号,就是 用来区分每个计算机上不同的应用程序的 端口号 :是一个16位的整数,0-65535的范围

TCP和UDP各有65535个端口,每个协议下的端口不能 冲突

公认端口: 0-1023 被预先定义的服务器通信占用端口

常见端口 :

        http: 80 https: 443 ftp: 21 telnet : 23 ssh : 22

注册端口 : 1024 - 49151 分配给用户进程或者应用 程序的

常用的端口:

         tomcat : 8080 mysql : 3306 oracle : 1521

私有端口 :

        49152 - 65535 netstat -ano 命令 用来查看端口

13.6.3 套接字对象Socket

ip地址和端口号组成的一个用来标识主机应用的类.

该类实现客户端套接字(也称为“套接字”)。 套接 字是两台机器之间通讯的端点。

常用类

        Socket 该类实现客户端套接字(也称为“套接 字”)。

        ServerSocket 这个类实现了服务器套接字。 服务器套 接字等待通过网络进入的请求。         InetSocketAddress 该类实现IP套接字地址(IP地址 +端口号)它也可以是一对(主机名+端口号)

public class SocketDemo {
 public static void main(String[] args) throws IOException {
 Socket socket =
 new Socket(InetAddress.getByName("localhost"), 8080);
 InetSocketAddress inetSocketAddress =
 new InetSocketAddress("127.0.0.1", 8181);
 }
}
13.6.4 通信协议

        网络通信协议 :计算机网络中,遵循的一些约定,主 要是对于 数据的传输速度、传输的代码、代码结构、传输 控制步骤、错误信息控制等信息指定的一些标准

        通信双方必须同时遵守这些约定才能完成数据交互 网络协议的实现方式 ?

        通过将网络协议分层,每一个层,处理不同的事情,最 终完成协议的约定

        分层的内容 : 物理层、数据链路层、网络层、传输 层、应用层

        传输层的两个重要的协议 :

                TCP(Transmission Control Protocol) : 传输控制协议

                UDP(User Datagram Protocol) : 用户数据报协议

13.6.4.1 TCP协议

        使用TCP协议之前,必须要建立TCP连接,形成传输数 据通道

        传输前,采用的 三次握手的方式,点对点建立连接, 完成通信,是可靠的,安全的传输协议         在网络编程中,需要创建两个接口 : 客户端接口 、 服务端的接口

        TCP可以进行大量的数据传输

        传输完之后,需要释放建立的连接,效率相对较低一些

13.6.4.2 UDP协议

        将数据、源、目的 封装成数据包,不需要建立连接

        每个数据包大小限制在64kb以内

         发送的时候,不去查看接收方是否准好,接收方收到 后,也不会告诉发送方,收到数据         UDP是一种不可靠的传输

        发送数据结束后,不需要释放资源,开销小,速度快

13.6.4.3 使用TCP完成数据传输

需求 :

        用户在客户端发送消息给服务端,服务端将数 据展示在控制台上

/*
客户端
 */
public class TcpClient {
 public static void main(String[] args) {
 //使用tcp完成数据发送
 //步骤:
 OutputStream os = null;
 Socket socket = null;
 try {
 //1,找到服务器,建立连接
 //获取服务器地址对象,以及端口号
 InetAddress serverIP = InetAddress.getByName("127.0.0.1");
 //指定端口号,不要和本机上的端口号冲突了
 int port = 8888;
 //2,发送数据,通过ip+port 创建 Socket对象发送数据
 socket = new Socket(serverIP, port);
 //获取输出流outputStream
 os = socket.getOutputStream();
 os.write("你好,我是南邮的学生!".getBytes());
 } catch (UnknownHostException e) {
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }finally {
 //3,关闭资源
 try {
 if (os != null){
 os.close();
 }
 if (socket != null){
 socket.close();
 }
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }
/*
服务端
 */
public class TcpServer {
 public static void main(String[] args) {
 ByteArrayOutputStream baos = null;
 InputStream is = null;
 Socket accept = null;
 ServerSocket serverSocket = null;
 //步骤:
 //1,建立服务端的端口,创建ServerSocket对象
 try {
 serverSocket = new ServerSocket(8888);
 //2,等待用户连接
 while (true){
 //侦听用户连接
 accept = serverSocket.accept();
 //3,接收用户消息
 //获取输入流,读到数据
 is = accept.getInputStream();
 //使用ByteArrayOutputStream,将数据写出去
 baos = new ByteArrayOutputStream();
 byte[] buffer = new byte[1024];
 int data;
 while ((data = is.read(buffer)) != -1){
 baos.write(buffer,0,data);
 }
 System.out.println(baos.toString());
 }
 } catch (IOException e) {
 e.printStackTrace();
 }finally {
 try {
 baos.close();
 is.close();
 accept.close();
 serverSocket.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }
}

        使用TCP完成文件的上传,服务器收到上传文件,会返回一 条信息给客户端,提示文件接收成功

/*
客户端
 */
public class TcpClient {
 public static void main(String[] args) {
 //使用tcp完成数据发送
 //步骤:
 OutputStream os = null;
 Socket socket = null;
 try {
 //1,找到服务器,建立连接
 //获取服务器地址对象,以及端口号
 InetAddress serverIP =
 InetAddress.getByName("127.0.0.1");
 //指定端口号,不要和本机上的端口号冲突了
 int port = 8888;
 //2,发送数据,通过ip+port 创建 Socket对象发送数据
 socket = new Socket(serverIP, port);
 //获取输出流outputStream
 os = socket.getOutputStream();
 //获取上传的图文件对象
 FileInputStream fis = new FileInputStream("D:\\1.jpg");
 byte[] buffer = new byte[1024];
 int data;
 //完成图片的读取和写出
 while ((data = fis.read(buffer)) != -1){
 os.write(buffer,0,data);
 }
 //传输完之后,要关闭数据输出
 socket.shutdownOutput();
 //读取服务器返回的消息
 InputStream is = socket.getInputStream();
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 byte[] bytes = new byte[1024];
 int data1;
 while ((data1 = is.read(bytes)) != -1){
 baos.write(bytes,0,data1);
 }
 System.out.println(baos.toString());
 } catch (UnknownHostException e) {
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }finally {
 //3,关闭资源
 try {
 if (os != null){
 os.close();
 }
 if (socket != null){
 socket.close();
 }
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }
}
/*
服务端
 */
public class TcpServer {
 public static void main(String[] args) {
 InputStream is = null;
 Socket accept = null;
 ServerSocket serverSocket = null;
 OutputStream os = null;
 FileOutputStream fos = null;
 //步骤:
 //1,建立服务端的端口,创建ServerSocket对象
 try {
 serverSocket = new ServerSocket(8888);
 //2,等待用户连接
 //while (true){
 //侦听用户连接
 accept = serverSocket.accept();
 //3,接收用户消息
 //获取输入流,读到数据
 is = accept.getInputStream();
 //创建文件输出对象
 fos = new FileOutputStream(new File("abc.jpg"));
 byte[] buffer = new byte[1024];
 int data;
 while ((data = is.read(buffer)) != -1){
 fos.write(buffer,0,data);
 }
 //通知客户端
 os = accept.getOutputStream();
 os.write("文件接收成功".getBytes());
 //}
 } catch (IOException e) {
 e.printStackTrace();
 }finally {
 try {
 os.close();
 fos.close();
 is.close();
accept.close();
 serverSocket.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }
}
13.6.4.4 UDP实现数据传输

        UDP传输需要封装一个数据包,Java中的数据包其实就 是一个对象

        DatagramPacket:该类表示数据报包。 数据包中,包 含发送的 ip、端口、接收端的ip、端口、数据

         DatagramSocket:此类表示用于发送和接收数据报数 据包的套接字,UDP通过此类发送和接收数据

/*
UDP的客户端
 */
public class UdpClient {
 public static void main(String[] args) throws IOException {
 //1,创建DatagramSocket对象
 DatagramSocket socket = new DatagramSocket();
 byte[] data = "你好,udp发送数据".getBytes();
 InetAddress address = InetAddress.getByName("127.0.0.1");
 //2,封装数据包
 DatagramPacket packet =
 new DatagramPacket(data, 0, data.length, address, 9999);
 //3,通过socket发送数据包
 socket.send(packet);
 socket.close();
 }
}
/*
udp的接收方
 */
public class UdpServer {
 public static void main(String[] args) throws IOException {
 //建立DatagramSocket对象,并开放端口
 DatagramSocket socket = new DatagramSocket(9999);
 //接收数据
 byte[] buffer = new byte[100];
 //创建数据包,用来接收数据
 DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.lengt
 //传入数据包接收数据
 socket.receive(packet);
 //把数据从包中取出,转为String
 byte[] data = packet.getData();
 String s = new String(data, 0, data.length);
 System.out.println(s);
 socket.close();
 }
}

使用Udp实现一个模拟在线咨询的案例

用户可以输入一些信息,服务器接收到之后,根据用户 输入的信息,作出自动的回应

/*
咨询方
 */
public class ChatClient {
 public static void main(String[] args) throws IOException {
 System.out.println("已经接入咨询系统,可以发送消息了:");
 
 //创建DatagramSocket对象,发送端的
 DatagramSocket sendSocket = new DatagramSocket(8888);
 
 //创建输入对象
 BufferedReader reader = new BufferedReader(new InputStreamReade
 
 //循环输入消息,实现发送多条消息
 while (true){
 String data = reader.readLine();
 byte[] bytes = data.getBytes();
 //放到数据包中
 DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.
 new InetSocketAddress("localhost", 6666));
 sendSocket.send(packet);
 
 //如果用户发送了 byebye,那么就断开
 if (data.equals("byebye")){
 break;
 }
 }
 }
}
/*
接收方
 */
public class ChatServer {
 public static void main(String[] args) throws IOException {
 DatagramSocket socket = new DatagramSocket(6666);
 while (true){
 //接收数据
 byte[] buffer = new byte[1024];
 DatagramPacket packet = new DatagramPacket(buffer, 0, buffe
 socket.receive(packet);
 //获取数据内容
 byte[] data = packet.getData();
 String s = new String(data, 0, data.length);
 System.out.println(s);
if (s.equals("你好")){
 //数据返回
 }
 if (s.equals("byebye")){
 break;
 }
 }
}

14 反射

14.1 反射

        反射 :Java中的反射,用来将类中的方法或者属性、 构造方法直接获取到,完成它们的调用         反射是框架设计的灵魂

        学习反射之前,了解两个概念 : 编译期 和 运行期

        编译期 : 指的是将源码,交给编译器,编译成计算机 可以执行的文件的过程。在Java中就是把java文件编译 成.class文件的过程。编译期就是做了一些翻译的功能,还 没有将代码在内存中运行。

        运行期 : 把编译后的文件交给计算机,直到程序结 束。其实就是把在磁盘中的代码加载到内存中,执行起来

        反射机制: 在运行期,对于任意的类,都能够获取这 个类的所有属性和方法,这种动态获取信息以及动态调用方 法的功能,称为Java的反射机制

        Java代码在计算机中运行的三个阶段

        反射的好处 :

                 1,可以在程序的运行过程中,获取到Class类 对象中封装的对象,可以操作这些对象                   2,可以 解耦,提高程序的可扩展性

14.2 获取Class类对象的方法

        方式1: 在第一个阶段,还没有加载的时候

                Class.forName("全类名地址") : 将字节码文件直 接加载进内存,返回Class对象,一般将来用于配置文 件,读取文件,加载类

                方式2:通过类名的属性 class获取

                类名.class的方式获取到这个类的类对象

                方式3: 通过实例化后的对象获取

                对象.getClass() : 获取到这个类的类对象

public class TestPerson {
 public static void main(String[] args) throws ClassNotFoundExceptio
 Person person = new Person();
 //方式1 : 通过Class.forName()
 Class personClass1 = Class.forName("com.iweb.airui369.test.Perso
 //方式2 : 通过类名.class
 Class personClass2 = Person.class;
 //方式3: 通过对象.getClass()
 Class personClass3 = person.getClass();
 //比较3个获取的类对象,值都是相同的,表示指向内存中的同一个地址
 //其实就是同一个类对象
 //得出结论 :
 //同一个字节码文件(xxx.class)在同一个程序的运行过程中,
 //只会被加载一次,可以通过不同的方式去找到这个类对象,
 //不管通过哪种方式,获取到的类对象都是同一个
 System.out.println(personClass1 == personClass2);
 System.out.println(personClass2 == personClass3);
 }
}
public class Person {
 //属性
 public int sid;
 private String name;
 private int age;
 //构造函数
 public Person() {
 }
 //成员方法
 public void eat(){}
}

14.3 Class类对象中,封装的其他几个方法

        Field类 :Field类提供有关类或接口的单个字段的信息 和动态访问。 反射的字段可以是类(静态)字段或实例字段。

        Constructor类 :Constructor提供了一个类的单个构 造函数的信息和访问。

         Method类 :Method类提供有关类和接口上单一方法 的信息和访问权限。 反映的方法可以是类方法或实例方法 (包括抽象方法)。

14.4 Class类对象的常用方法(功能)

14.4.1 获取成员变量()

        Field getField(String name)

        返回一个 Field对象,它反映此表示的类或接口的指定 公共成员字段 类对象。

        Field[] getFields()

        返回包含一个数组 Field对象反射由此表示的类或接口 的所有可访问的公共字段 类对象。         Field getDeclaredField(String name)

        返回一个 Field对象,它反映此表示的类或接口的指定 已声明字段 类对象。

        Field[] getDeclaredFields()

        返回的数组 Field对象反映此表示的类或接口声明的所 有字段 类对象。

//获取成员变量相关的方法
 //Field getField(String name) 获取指定的public的属性
 Field sid = personClass1.getField("sid");
 System.out.println(sid);
 //Field[] getFields() 获取所有public的属性
 Field[] fields = personClass1.getFields();
 for (Field field : fields) {
 System.out.println(field);
 }
 System.out.println("----------------");
 //Field getDeclaredField(String name) 获取所有(包含私有化)的指定的
 Field name = personClass1.getDeclaredField("name");
 System.out.println(name);
 System.out.println("----------------");
 //Field[] getDeclaredFields() 获取所有的属性
 Field[] fields1 = personClass1.getDeclaredFields();
 for (Field field1 : fields1) {
 System.out.println(field1);
 }
 //通过Field对象,完成对象属性值的获取和赋值
 System.out.println("----------------");
 Person p = new Person();
 p.sid = 1001;
 //获取p对象的sid属性值
 Object o = sid.get(p);
 System.out.println(o);
 //通过sid属性对象,完成person对象的sid属性赋值
 sid.set(p,1002);
 System.out.println(p.sid); //1002

        通过暴力反射,完成私有属性的赋值

//通过Field对象,结合setAccessible()方法对私有属性的赋值或者获取值
 System.out.println("----------------");
 name.setAccessible(true); //暴力反射
 name.set(p,"jack"); //反射给属性赋值
 Object o1 = name.get(p); //反射获取属性值
 System.out.println(o1);
14.4.2 获取构造方法()

        Constructor getConstructor(Class... parameterTypes)

        返回一个 Constructor对象,该对象反映

        Constructor 对象表示的类的指定的公共 类函数。

        Constructor[] getConstructors()

        返回包含一个数组 Constructor对象反射由此表示的类 的所有公共构造 类对象。

         Constructor getDeclaredConstructor(类... parameterTypes)

        返回一个 Constructor对象,该对象反映

        Constructor 对象表示的类或接口的指定 类函数。

         Constructor[] getDeclaredConstructors()

        返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组 类

public class ConstructorDemo {
 public static void main(String[] args) throws NoSuchMethodException, I
 //获取类对象
 Class<Person> personClass = Person.class;
 //获取构造函数对象
 //Constructor<T> getConstructor(T<?>... parameterTypes)
 //传入构造函数的参数的类型,获取指定类对象的无参构造
 Constructor<Person> constructor = personClass.getConstructor();
 Person person = constructor.newInstance();
 System.out.println(person);
 //获取指定类对象的有参构造
 Constructor<Person> constructor1 = personClass.getConstructor(Stri
 Person person1 = constructor1.newInstance("jack",20);
 System.out.println(person1);
 //Constructor<?>[] getConstructors() 获取所有的public的构造函数
 Constructor<?>[] constructors = personClass.getConstructors();
 for (Constructor<?> constructor2 : constructors) {
 System.out.println(constructor2);
 }
 //Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
 Constructor<Person> constructor3 = personClass.getDeclaredConstruct
 System.out.println(constructor3);
 //可以通过暴力反射,完成私有构造方法的调用,并且可以完成属性赋值
 constructor3.setAccessible(true);
 Person person2 = constructor3.newInstance("tom");
 System.out.println(person2);
 //类对象可以通过newInstance()方法,直接完成对象的创建
 Person person3 = personClass.newInstance();
 System.out.println(person3);
 }
}

        使用反射完成对象创建和属性赋值

//创建一个Student对象,有3个私有属性,学号、姓名、班级,有一个toString()方法
//利用反射,创建1个无参Student对象,利用反射,给这个Student对象的属性赋值
//最后,将这个Student对象输出
public class Student {
 private int sid;
 private String name;
 private String className;
 @Override
 public String toString() {
 return "Student{" +
 "sid=" + sid +
 ", name='" + name + '\'' +
 ", className='" + className + '\'' +
 '}';
 }
}
public class StudentTest {
 public static void main(String[] args) throws IllegalAccessExceptio
 //获取类对象
 Class<Student> studentClass = Student.class;
 //通过类对象调用方法
 Student s1 = studentClass.newInstance();
 //通过类对象获取属性
 Field sid = studentClass.getDeclaredField("sid");
 sid.setAccessible(true);
 Field name = studentClass.getDeclaredField("name");
 name.setAccessible(true);
 Field className = studentClass.getDeclaredField("className");
 className.setAccessible(true);
 //给指定的对象属性赋值
 sid.set(s1,1001);
 name.set(s1,"jack");
 className.set(s1,"1班");
 System.out.println(s1);
 //创建另一个对象
 Student s2 = studentClass.newInstance();
 sid.set(s2,1002);
 name.set(s2,"tom");
 className.set(s2,"2班");
 System.out.println(s2);
 }
14.4.3 获取成员方法()

        Method getDeclaredMethod(String name, 类... parameterTypes)

        返回一个 方法对象,它反映此表示的类或接口的指定 声明的方法 类对象。

        Method[] getDeclaredMethods()

        返回包含一个数组 方法对象反射的类或接口的所有声 明的方法,通过此表示 类对象,包括公共,保护,默 认(包)访问和私有方法,但不包括继承的方法。

        Method getMethod(String name, 类... parameterTypes)

        返回一个 方法对象,它反映此表示的类或接口的指定 公共成员方法 类对象。

         Method[] getMethods()

        返回包含一个数组 方法对象反射由此表示的类或接口 的所有公共方法 类对象,包括那些由类或接口和那些 从超类和超接口继承的声明。

public class MethodDemo {
 public static void main(String[] args) throws NoSuchMethodExc
 //Method getMethod(String name, Class<?>... parameterType
 //返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类
 //name参数表示 :方法名
 //Class<?>... parameterTypes :参数类.class
 //... 表示可变长的参数列表
 //创建类对象
 Class<Student> studentClass = Student.class;
 //通过类对象获取无参数方法
 Method sleep = studentClass.getMethod("sleep");
 //创建对象
 Student s1 = studentClass.newInstance();
 //通过Method类中的方法,完成对象对方法的调用
 sleep.invoke(s1);
 //通过类对象获取有参数方法
 Method eat = studentClass.getMethod("eat", String.class);
 eat.invoke(s1,"鸡腿");
 Method add = studentClass.getMethod("add", int.class, int
 add.invoke(s1,10,20);
 }
}
public class Student {
 private int sid;
 private String name;
 private String className;
 public void sleep(){
 System.out.println("学生睡觉!");
 }
 public void eat(String food){
 System.out.println("学生在吃" + food);
 }
 public void add(int a,int b){
 System.out.println(a + b);
 }
 @Override
 public String toString() {
 return "Student{" +
 "sid=" + sid +", name='" + name + '\'' +
 ", className='" + className + '\'' +
 '}';
 }
14.4.4 获取类名()
String className = studentClass.getName();
 System.out.println(className);

        总结 :

                获取类对象 :类名 .class

                类对象常用方法 获取私有属性对象Field :Field

                 getDeclaredField(String name)

                获取构造方法对象Constructor :

                Constructor getConstructor(Class... parameterTypes)

                获取普通方法对象Method :Method getMethod(String name, Class... parameterTypes)                 几个对象的常用方法:

                         Filed类 :

                                 setAccessible(true) 开启暴力反射,可以操作私 有属性

                                get() 获取属性值

                                set(Object obj, Object value) 设置属性值

                        Constructor类:

                                newInstance(Object... initargs) 用来构建对象

                                如果是调用无参构造来创建,直接通过类对象调 用 newInstance() 方法                         Method类

                                invoke(Object obj, Object... args) 执行方法

                                getName() 获取方法名

                        模拟框架案例 :

                                  需求 :写一个框架,将来再不改变任何代码的情况 下,可以帮助我们创建任意类的对象,并且能够执行其中的 任意方法。

                        properties配置文件

className=com.iweb.airui369.reflecttest2.Student
methodName=sleep
public class Person {
 public void eat(){
 System.out.println("eat.....");
 }
}
public class Student {
 public void sleep(){
 System.out.println("sleep.....");
 }
}
public class TestReflect {
 public static void main(String[] args) throws IOException, ClassNot
 //Person person = new Person();
 //person.eat();
 //Student student = new Student();
 //student.sleep();
 //需要在不改变任何代码的情况下,可以创建对象,并执行方法
 //把类和方法的信息,配置在一个配置文件中,通过代码读取到配置文件中的信
 //完成加载和调用
 //1,在src目录下,创建一个info.properties 配置文件,在文件中写入需要加
 //类的信息和方法的信息
 //完成配置文件加载
 Properties properties = new Properties();
 //获取src目录下的配置文件,使用inputStream完成文件加载
 InputStream is =
 TestReflect.class.getClassLoader().
 getResourceAsStream("info.properties");
 //properties对象加载is流对象中的数据
 properties.load(is);
 //去获取到properties对象中的配置数据信息
 String className = properties.getProperty("className");
 String methodName = properties.getProperty("methodName");
 //获取类对象
 Class cls = Class.forName(className);
 //创建一个对象
 Object o = cls.newInstance();
 //获取方法对象
 Method method = cls.getMethod(methodName);
 //执行方法
 method.invoke(o);
 }

15 多线程

程序、进程、线程

        程序:程序其实是指令和数据的有序集合,本身没有特殊含 义,是一个静态概念

        进程:进程是执行程序的一次执行过程,它是一个动态的概 念,本质上其实是系统的资源分配单位,当我们运行一个程 序后,这个程序的进程就会启动。

        线程:线程其实是进程中的CPU调度和执行的单位,进程中 可以包含若干个线程,一个进程最少有一个线程。

         在同一个进程中,可以执行多个任务,而每个任务就可 以看成是一个线程

        比如: 放音乐的时候,可以一边放音乐,一边点击菜 单,或者一边下载其他音乐

        总结 :

                进程其实就是指运行中的程序,特点 :动态、独立、 并发

                线程是进程内部的一个执行单元,是程序中某一个功能 的单一顺序控制流程

面试题:进程和线程的区别

        1,根本区别 :

                进程是资源的分配单位 ,线程是资源 的执行单位

        2,开销:

                进程具有独立的代码和数据空间,进程间的切 换开销较大

                线程可以看做是轻量的进程,每个线程有独立 的运行区域,线程之间开销小

        3,所处环境:

                进程是运行在操作系统中的多个任务 线程是在同一个程序中,有多个顺序流同时执 行         4,内存分配 :

                进程在运行的时候,系统会为每个进程分配不 同的内存区域

                线程的内存是由CPU分配的

        5,包含关系:

                进程包含线程 没有线程的进程,可以看做是单线程的,如果 一个进程有多个线程,执行过程就是多条线的

Java中的线程

主线程

        Java中main()方法就是主线程的入口

         其他的子线程运行,必须在main()线程中,所以, main()方法必须是最后完成执行的,因为它要执行各种关闭 动作。

        即使在程序运行的时候,没有自定线程,程序中也包含 多个线程,比如 gc线程

        将来,在一个程序中,如果开辟了多个线程,那么线程 的运行由CPU(调度器)来完成调度,先后顺序不能人为干预

        将来有多个线程的时候,如果对同一份资源操作,会存 在资源抢夺的问题(线程不安全问题),需要加入并发控 制。

Java中线程的创建和启动

        Java中创建线程的方式 一共有四种:

                1,继承Thread类

                2,实现Runnable接口

                3,实现Callable接口

                4,使用线程池Executors工具类创建线程池

继承Thread类实现多线程

        构造方法

        Thread()         分配一个新的 Thread对象。

        Thread(Runnable target)         分配一个新的 Thread对象。

        Thread(Runnable target, String name)         分配一个新的 Thread对象。

        Thread(String name)         分配一个新的 Thread对象。

        继承Thread类创建多线程步骤 :

                1,自定义一个类,继承Thread,并且在类中,重写 run方法,run方法中,存放的是你需要实现的具体的逻辑代 码。

                 2,创建自定义的线程子类对象

                3,通过线程子类对象,调用start()方法,来启动子线 程

                为什么是调用start()方法 ?

                线程调用start()方法之后,表示该线程已经准备好被执 行了,然后再有JVM调用run()方法,

                如果直接在main()方法中,调用run()方法,那么run() 方法就被当做普通方法执行了

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("子线程 :" + Thread.currentThread().getName() + "在运行");
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        //获取当前线程的线程对象
        Thread thread = Thread.currentThread();
        System.out.println(thread.isAlive());  //true
        System.out.println("当前线程的线程是:"+thread.getName());

        //给当前线程重新设置个名字
        thread.setName("主线程");
        System.out.println("当前运行的线程:"+thread.getName());

        //创建子线程对象,通过start()方法启动线程
        //第一种
//        MyThread myThread = new MyThread();
//        myThread.start();

        //第二种
        new Thread(new MyThread(),"创建的子线程").start();

    }
}

        继承Thread创建多线程,并创建2个子线程,一个线程叫 : 小明,一个线程叫:小红,

         分别让这两个线程循环5次,输出以下内容: 当前线程名 : xxx ,正在被调用

public class ThreadDemo1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("当前线程名:" + Thread.currentThread().getName() + ",正在被调用!");

            try {
                Thread.sleep(1000l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {

        new Thread(new ThreadDemo1(),"小明").start();

        new Thread(new ThreadDemo1(),"小红").start();

    }
}

实现Runnable接口创建线程

实现步骤:

        1,自定义一个类,实现Runnable接口,重写run()方 法

        2,创建实现类的对象,并以这个对象,作为new Thread()的参数,构造出Thread类的对象         3,通过Thread类的对象调用start()方法

public class RunnableDemo  implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "----" + i);
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {

        //直接创建出来的Runnable接口对象,不能直接调用strat()方法
        RunnableDemo runnableDemo = new RunnableDemo();
        //先创建Thread对象,将线程对象传进去

        new Thread(runnableDemo,"线程2").start();

        new Thread(new RunnableDemo(),"线程2").start();


    }
}

        使用匿名内部类实现

public class RunnableDemo1 {
    public static void main(String[] args) {

        //通过匿名内部类方式实现多线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "----" + i);
                }
            }
        }).start();

        new Thread(
                ()->{
                    for (int i = 0; i < 5; i++) {
                        System.out.println(Thread.currentThread().getName() + "----" + i);
                    }
                }
        ).start();
    }
}

        继承Thread和实现Runnable接口的区别

                1,继承Thread类

                        将来就不能继承其他类了

                        编写简单,可以直接操作线程对象,无须再通过 Thread类去调用其他方法

                 2,实现Runnable接口

                        将来还可以继承其他类

                        可以实现对象的数据共享(会存在并发问题)

                将来使用Runnable接口的方式实现多线程比较常用

初识线程安全问题
public class TicketRunnable implements Runnable{
    private int ticketNum = 30;
    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0){
                break;
            }
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "----->抢到了第" + ticketNum-- + "张票");
        }
    }

    public static void main(String[] args) {
        //这里操作的不是相同的对象,所有他们输出的是30条内容
//        new Thread(new TicketRunnable(),"小明").start();
//        new Thread(new TicketRunnable(),"小红").start();
//        new Thread(new TicketRunnable(),"黄牛").start();

        //在外面创建对象,将对象传入new Thread中,创建多个子线程
        //这时候,多个子线程就会共享这个对象中的数据
        TicketRunnable ticketRunnable = new TicketRunnable();

        new Thread(ticketRunnable,"小明").start();
        new Thread(ticketRunnable,"小红").start();
        new Thread(ticketRunnable,"黄牛").start();

    }
}

线程的生命周期

        新生状态 :使用new关键字建立一个线程对象,该线程对 象就处于新生状态

                处于新生状态的线程,拥有自己的内存空间,通过 调用start方法进入就绪状态

        就绪状态 : 新建的线程,通过调用start方法,进入就绪状 态

                就绪状态表示,线程可运行了,但是还没被分配到 cpu,处于就绪队列,等待系统分配CPU

                当系统选定一个等待的线程,就会从就绪进入执行 状态,这个动作也称为cpu调度                 运行状态:运行状态的线程,获得了cpu时间片,执行自己 的run()方法中的代码,

                        直到完成任务死亡或者等资源而阻塞

        阻塞状态:运行状态的线程,在某些情况下,会进入阻塞状 态

                阻塞分为三种 :

                        1,等待阻塞 : 调用wait方法会出现

                         2,同步阻塞: 调用方法的时候,方法上加了 synchronized关键字

                        3,其他阻塞 : 调用sleep()方法、join()方法、或 者io阻塞等,会发生 死亡状态: 线程的生命周期最后一个阶段,死亡的原因 :

                        1,所有内容正常执行完毕了,线程会死亡

                         2,线程执行被强制终止

                        3,线程抛出异常未被捕获

public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        System.out.println("线程执行了!");
        try {
            Thread.sleep(3000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程经过短暂的休眠后,又被执行了!");
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();

        Thread thread = new Thread(runnableDemo);

        System.out.println("线程被创建!");

        thread.start();

        System.out.println("线程准备就绪!");




    }
}

线程调度相关方法

        void setPriority(int newPriority)         更改此线程的优 先级。

public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "----" + i);
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new RunnableDemo(), "线程1");
        Thread thread2 = new Thread(new RunnableDemo(), "线程2");

        thread1.setPriority(Thread.MAX_PRIORITY);  //设置线程1,的优先级
        thread2.setPriority(Thread.MIN_PRIORITY);

        //设置优先级并不代表,优先级高的会先执行完
        thread1.start();
        thread2.start();
    }
}

        void join()         等待这个线程死亡。

        static void yield()         对调度程序的一个暗示,即当前 线程愿意出让当前使用的处理器。

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {

        //创建子线程并执行
        Thread thread = new Thread(new RunnableDemo(), "子线程");
        thread.start();

        //主线程main执行的内容
        for (int i = 0; i < 10; i++) {
            if (i == 5){
                //thread.join();  //使用join方法,完成插队操作
                thread.yield();  //线程礼让
                //执行yield()方法,不代表后面的执行全部让给其他线程
                //只是会让出下一次执行机会,执行完,CPU会继续调用
            }
            System.out.println(Thread.currentThread().getName() + "----" + i);
        }
    }
}

        void setDaemon(boolean on)         将此线程标记为 daemon线程或用户线程。 当程序中正在运行的线程都是 守护线程,那么JVM将退出(程序结束)

        这个方法,必须要在线程启动前调用

        常用的JVM垃圾回收线程、内存管理线程,都是守护线 程

public class DaemonDemo implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("守护线程在运行!");
            try {
                Thread.sleep(1000l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class DaemonTest {
    public static void main(String[] args) throws InterruptedException {

        System.out.println("程序开始");
        DaemonDemo daemonDemo = new DaemonDemo();
        Thread thread = new Thread(daemonDemo);
        thread.setDaemon(true);  //设定守护线程

        thread.start();  //线程执行

        thread.sleep(5000l);
        System.out.println("主线程结束");
    }
}

线程的安全性问题

        在多线程运行的环境下,程序运行的结果,和我们预期 的结果不相符(排除错误问题),就可以看作是线程安全问 题。

        线程安全问题出现后,不太好解决,因为每次运行的结 果可能都会不一样,问题不容易复现,解决比较困难,所以,我们应该在编写代码阶段,就将问题解决。

        出现线程安全问题的根本原因,就是因为它们执行的步 骤不是原子性的操作,所有会有其他线程读取到中间的执行 步骤。

        将来可以通过加上synchronized关键字解决线程安全 问题

public class ThreadA {
    //临界资源,多个线程共享线程资源
    int num;

    public int getNum() {
        return num;
    }

    public synchronized int updateNum() {
        //临街资源发生写的操作
        //先计算num+1
        //吧结果赋值给num
        return num++;
    }
}
public class ThreadB extends Thread{
    ThreadA ta = new ThreadA();

    @Override
    public void run() {
        while (true){
            System.out.println("现在运行的是:" +
                    Thread.currentThread().getName() + ",num的值===" + ta.updateNum());
            try {
                Thread.sleep(1000l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadB tb = new ThreadB();
        new Thread(tb).start();
        new Thread(tb).start();
        new Thread(tb).start();
        new Thread(tb).start();

    }
}

        线程安全问题发生的条件 ?

                1,多线程的环境下

                2,存在临界资源,并且多个线程会去共享访问这个资 源

                3,存在并发写临界资源的情况

线程同步

        为什么要并发编程

        充分利用多核CPU的计算能力,通过并发可以将多核 CPU计算的性能发挥到极致

        方便业务的拆分,提升系统的性能 并发编程可能会遇到各种问题,比如 :线程安全、死锁、 内存泄露...

        并发编程的三要素 :

                 原子性:一个或者多个操作,要么全部执行成功,要么 全部执行失败

                可见性:一个线程对共享变量的修改,另一个线程能够 立刻看到

                有序性:程序执行的顺序,按照代码先后顺序执行

        并发编程的问题怎么解决?

                 使用线程同步,给线程执行的代码加锁

                线程同步 :就是线程的一种等待机制,如果将来有多 个线程需要访问同一个对象,就会进入这个对象的等待池, 形成队列,前面的线程使用完了,下个线程再使用

        Java中实现线程同步:

                Java中,为了保证数据在方法中访问的正确性,加入了 锁机制(synchronized、Lock),将来当一个线程获取到对象 的锁之后,就把资源独占,其他线程必须等这个线程将锁释 放,才能使用

          线程同步的问题:

                1,一个线程获得锁,其他的线程只能等待,挂起,相 对来说,运行速度会慢一些

                2,在多线程竞争下,加锁、释放锁、都会导致比较多 的上下文切换、调度的延迟,引起性能问题

                3,优先级高的线程,可能会等待一个优先级低的线 程,导致优先级倒置,引起性能问题

Synchronized的用法

        synchronized关键字,是Java中用来控制线程同步的

        将来可以用来修饰方法、变量

        synchronized在jdk1.6之后,被引入了大量的优化,用 来减少锁操作的开销,所以现在synchronized比较常用

        1,直接将synchronized用在方法上,称为同步方法

                写法: public synchronized void method(){ }

                synchronized修饰的方法,锁的是这个类的对 象,每个对象对应了一把锁,每个synchronized方 法,都需要获取到调用该方法的对象的锁,才能执行, 否则线程就会阻塞

        2,synchronized修饰静态方法 : 是给 Class类对象加 锁,也就是给当前类加锁,也会作用于类的每个实例对象

                写法: public synchronized static void method(){ }

        3,synchronized用来修饰代码块,称为同步代码块

                写法: synchronized(对象){} 修饰的对象可以称为同步监视器,一般使用都是将 共享资源对象,放到这里,作为同步监视器

public class TicketRunnable implements Runnable{
    private int ticketNum = 10;
    boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buyTicket();
        }
    }
    //写一个买票方法
    public synchronized void buyTicket(){
        //买票结束的判断
        if (ticketNum <=0 ){
            flag = false;
            return;
        }
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()
                +"---->抢到了第"+ticketNum-- +"张票");
    }
    public static void main(String[] args) {
        //在外面创建对象,将对象传入new Thread中,创建多个子线程
        //这时候,多个子线程就会共享这个对象中的数据
        TicketRunnable ticketRunnable = new TicketRunnable();
        new Thread(ticketRunnable,"小明").start();
        new Thread(ticketRunnable,"小红").start();
        new Thread(ticketRunnable,"黄牛").start();
    }
}
public class TicketRunnable1 implements Runnable{
    private int ticketNum = 10;
    boolean flag = true;
    Object obj = new Object();
    @Override
    public void run() {
        while (flag) {
            buyTicket();
        }
    }
    //写一个买票方法
    public synchronized void buyTicket(){
        synchronized (obj){
            //买票结束的判断
            if (ticketNum <=0 ){
                flag = false;
                return;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    +"---->抢到了第"+ticketNum-- +"张票");
        }

    }
    public static void main(String[] args) {
        //在外面创建对象,将对象传入new Thread中,创建多个子线程
        //这时候,多个子线程就会共享这个对象中的数据
        TicketRunnable ticketRunnable = new TicketRunnable();
        new Thread(ticketRunnable,"小明").start();
        new Thread(ticketRunnable,"小红").start();
        new Thread(ticketRunnable,"黄牛").start();
    }
}

Lock(锁)

从Jdk5开始,Java提供了更强大的线程同步机制,通过显式 定义同步锁对象来实现同步,这里就是使用Lock对象

锁是用于通过多个线程控制对共享资源的访问的工具。

通常,锁提供对共享资源的独占访问:一次只能有一个线程 可以获取锁,并且对共享资源的所有访问都要求首先获取 锁。

这里,常用的是Lock接口的子实现类 ReentrantLock

一个ReentrantLock(可重入互斥锁)具有与使用 synchronized方法和语句访问的隐式监视锁相同的基本行 为和语义,但具有扩展功能。可以实现 显式的加锁、释放 锁

语法:

        class X {

         private final ReentrantLock lock = new ReentrantLock();

        // ...

        public void m() {

         lock.lock(); //加锁

        try { // ... method body }

        finally {

        lock.unlock() //解锁

         } } }

示例:多线程抢票通过Lock加锁

public class LockDemo implements Runnable {
    private int ticketNum = 20;
    boolean flag = true;

    //声明一个显示的锁
    private final  ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (flag) {
            buyTicket();
        }
    }
    //写一个买票方法
    //synchronized ()修饰方法
    public void buyTicket(){

        try {
            //获得锁
            lock.lock();
        //买票结束的判断
        if (ticketNum <=0 ){
            flag = false;
            return;
        }

            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName()
                +"---->抢到了第"+ticketNum-- +"张票");
    }
    public static void main(String[] args) {
        //在外面创建对象,将对象传入new Thread中,创建多个子线程
        //这时候,多个子线程就会共享这个对象中的数据
        LockDemo lockDemo = new LockDemo();
        new Thread(lockDemo,"小明").start();
        new Thread(lockDemo,"小红").start();
        new Thread(lockDemo,"黄牛").start();
    }
}
synchronized和reentrantlock区别是什么?

synchronized 是和 if、else、for、while 一样的关键字, ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更 多更灵活的特性,可以被继承、可以有方法、可以有各种各 样的类变量

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

主要区别如下:

        ReentrantLock 使用起来比较灵活,但是必须有释放锁的配 合动作;

        ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;         ReentrantLock 只适用于代码块锁,而 synchronized 可以 修饰类、方法、变量等。

Java中每一个对象都可以作为锁,这是synchronized实现 同步的基础:

普通同步方法,锁是当前实例对象

静态同步方法,锁是当前类的class对象

同步方法块,锁是括号里面的对象

Lock锁和synchronized锁,到低用哪个?

Lock所在刚出来的时候,很多的性能方面确实比 synchronized要好,但是从JDK6开始synchronized也被 做了各种的优化

优化:适应自旋锁、锁消除、轻量级锁、偏向锁...

所以,现在Lock锁和synchronized锁,性能差别不是很 大,

synchronized使用起来更加简单,所以大部分时候还 是使用synchronized,如果需要使用Lock锁的特有的特 性,才会使用Lock锁

死锁

死锁是指两个或两个以上的进程(线程)在执行过程中,由 于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若 无外力作用,它们都将无法推进下去。此时称系统处于死锁 状态或系统产生了死锁,这些永远在互相等待的进程(线 程)称为死锁进程(线程)。

多个线程同时被阻塞,它们中的一个或者全部都在等待某个 资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他 们同时都想申请对方的资源,所以这两个线程就会互相等待 而进入死锁状态。

出现死锁后,不会出现异常,不会出现提示,只是所有的线 程都处于阻塞状态,无法继续了。

使用多线程的时候,要避免死锁的发生

示例:

使用死锁模拟生活中抢资源的案例: 茶杯和牙膏

/*茶杯对象*/
public class TeaCup {


}


/*牙膏对象*/
public class ToothPaste {

}


/*刷牙*/
public class Brushing implements Runnable{
    //线程的选择
    int choice;  //0 差别   1 牙刷

    String name;   //线程名称

    static TeaCup teaCup = new TeaCup();
    static ToothPaste toothPaste  = new ToothPaste();

    public Brushing(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    //定义一个刷牙方法
    public void brushing() throws InterruptedException {
        //如果线程,先拿了茶杯,那就需要在拿牙膏
        if (choice == 0){
//            synchronized (teaCup){
                System.out.println(this.name+"拿到茶杯,在等牙膏!");

                Thread.sleep(1000l);
                synchronized (toothPaste){
                    System.out.println(this.name+"拿到牙膏,开始刷牙");
                }
//            }
        }else{
            //先拿牙膏,在拿茶杯
            synchronized (toothPaste){
                System.out.println(this.name+"拿到牙膏,在等茶杯!");

                Thread.sleep(1000l);
                synchronized (teaCup){
                    System.out.println(this.name+"拿到茶杯,开始刷牙");
                }
            }
        }
    }
    @Override
    public void run() {
        try {
            brushing();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Test {
    public static void main(String[] args) {
//        Brushing brushing1 = new Brushing(0, "jack");
//        Brushing brushing2 = new Brushing(1, "Alice");
//        new Thread(brushing1).start();
//        new Thread(brushing2).start();



        new Thread(new Brushing(0, "jack")).start();
        new Thread(new Brushing(1, "Alice")).start();
    }
}

形成死锁四个必要条件是什么?

互斥条件:线程(进程)对于所分配到的资源具有排它性,即 一个资源只能被一个线程(进程)占用,直到被该线程(进程) 释放

请求与保持条件:一个线程(进程)因请求被占用资源而发生 阻塞时,对已获得的资源保持不放。

不剥夺条件:线程(进程)已获得的资源在末使用完之前不能 被其他线程强行剥夺,只有自己使用完毕后才释放资源。

循环等待条件:当发生死锁时,所等待的线程(进程)必定会 形成一个环路(类似于死循环),造成永久阻塞

如何避免线程死锁

我们只要破坏产生死锁的四个条件中的其中一个就可以了。

破坏互斥条件这个条件我们没有办法破坏,因为我们用锁本来就是想让他 们互斥的(临界资源需要互斥访问)。

破坏请求与保持条件 一次性申请所有的资源。

破坏不剥夺条件 占用部分资源的线程进一步申请其他资源时,如果申请不 到,可以主动释放它占有的资源。

破坏循环等待条件 靠按序申请资源来预防。按某一顺序申请资源,释放资源则 反序释放。破坏循环等待条件。

volatile关键字

Java 提供了 volatile 关键字来保证可见性和禁止指令重排

volatile 修饰的变量,具有以下特点 :

1,volatile关键字保证可见性

2,不保证原子性,仍然存在线程安全问题

3,禁止指令重排,保证有序性

volatile关键字保证可见性

可见性,就是一个线程在操作完某个变量之后,这个值会对 其他线程可见

public class SynchronizedDemo {
    public int num = 0;
    //volatile可以保证类属性的可见性。也就是一个线程修改完值之后,另一个线
    //能拿到这个被修改后的值
    //public volatile boolean flag = false;
    //使用synchronized 实现可见性
    public boolean flag = false;
    public synchronized boolean getFlag() {
        return flag;
    }
    public synchronized void setFlag() {
        this.flag = true;
    }
    public int getNum(){
        return num;
    }
    public void addNum(){
        num++;
    }
    public static void main(String[] args) {
        SynchronizedDemo t1 = new SynchronizedDemo();
        //创建多线程
        //第一个多线程中,调用t1对象的addNum方法,完成num数字的++操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    t1.addNum();
                    System.out.println("addNum次数===" + i);
                    try {
                        Thread.sleep(500L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //t1.flag = true;
                t1.setFlag();
                System.out.println("flag已经设为true了");
            }
        }).start();
        //第二个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2在执行....");
                //无限循环
                //while (!t1.flag){
                while (!t1.getFlag()){

                }
                System.out.println("第二个线程获取到的num值:" + t1.getFlag());
            }
        }).start();
    }
   }

Volatile不能保证原子性
public class VolatileDemo1 {
    //volatile不能保证原子性
    public volatile int num = 0;
    public int getNum(){
        return num;
    }
    //public synchronized void addNum(){
    public synchronized void addNum(){
        num++;
    }
    public static void main(String[] args) {
        VolatileDemo1 t2 = new VolatileDemo1();
        //循环创建10个线程,每个线程分别执行100次addNum方法的调用
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j <100 ; j++) {
                        t2.addNum();
                        try {
                            Thread.sleep(1L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        //如果是理想情况下,这里的num值应该是1000
        //这个循环是主线程执行的内容
        for (int i = 0; i < 10; i++) {
            System.out.println("num值是:" + t2.getNum());
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
synchronized和volatile的区别

synchronized 表示只有一个线程可以获取作用对象的锁, 执行代码,阻塞其他线程。

volatile 表示变量在 CPU 的寄存器中是不确定的,必须从 主存中读取。保证多线程环境下变量的可见性;禁止指令重 排序。

区别

volatile 是变量修饰符;synchronized 可以修饰类、方 法、变量。

volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。

volatile 不会造成线程的阻塞;synchronized 可能会造成 线程的阻塞。

volatile标记的变量不会被编译器优化;synchronized标记 的变量可以被编译器优化。

volatile关键字是线程同步的轻量级实现,所以volatile性能 肯定比synchronized关键字要好。

通过实现Callable接口来创建线程

/* 通过实现Callable接口,创建线程 步骤:

1,创建实现类,实现Callable接口

2,以实现类为参数,创建FutureTask对象

3,将FutureTask作为参数,创建Thread对象

4,调用线程对象的start()方法 */

public class CallableDemo implements Callable {
    @Override
    public Object call() throws Exception {

        Thread thread = Thread.currentThread();
        thread.setName("线程1");
        System.out.println(Thread.currentThread().getName() + "      call()方法");
        return 1;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建FutureTask对象
        FutureTask<Integer> futureTask
                = new FutureTask<Integer>(new CallableDemo());
        //通过futureTask对象创建线程
//        Thread thread = new Thread(futureTask);
//        thread.start();
        new Thread(futureTask).start();


        //运行后通过futureTask对象获得值
        System.out.println("返回值:" + futureTask.get());
        System.out.println(Thread.currentThread().getName()+"线程正在执行");

    }
}
Callable和Runnable的区别

相同点 : 都是接口

都可以编写多线程程序

都需要通过Thread.start()方法来启动线程

区别:

Runnable接口run()方法没有返回值,Callable接口call方 法,有返回值,是个泛型,和Future、FutureTask配合可以 获取异步执行的结果

Runnable接口run()方法只能捕获运行时异常,且无法抛出 处理,Callable接口的call方法,允许抛出异常,可以获取 异常信息

Callable接口支持返回执行结果,需要调用futureTask.get() 得到返回值,这个方法会阻塞线程,不调用的话不会阻塞

Future接口

Futrue接口表示异步任务,是一个可能还没有完成的异步任 务的结果,Callable用于产生结果,Future用于获取结果

线程池

线程池的基本思想,其实就是一种对象池的思想

开辟一块内存空间,里面存放了众多的(未死亡的)线程,池 中的线程的调度,由池管理器来处理,当有线程任务的时 候,从池子中取一个线程,执行完成后,线程对象归池

这样做,可以避免反复创建线程对象所带来的性能开销,节 省了系统的资源。

JDK5的线程池,分为: 固定尺寸的线程池,可变尺寸连接 池,

相关的 API :Executors(线程池工具类) 和 ExecutorSevice(线程池对象接口)

Executors 面提供了一些静态工厂方法,生成一些常用的线 程池:

(1)newSingleThreadExecutor:创建一个单线程的线程 池。这个线程池只有一个线程在工作,也就是相当于单线程 串行执行所有任务。如果这个唯一的线程因为异常结束,那 么会有一个新的线程来替代它。此线程池保证所有任务的执 行顺序按照任务的提交顺序执行。

(2)newFixedThreadPool:创建固定大小的线程池。每 次提交一个任务就创建一个线程,直到线程达到线程池的最 大大小。线程池的大小一旦达到最大值就会保持不变,如果 某个线程因为执行异常而结束,那么线程池会补充一个新线 程。如果希望在服务器上使用线程池,建议使用 newFixedThreadPool方法来创建线程池,这样能获得更好 的性能

(3) newCachedThreadPool:创建一个可缓存的线程 池。如果线程池的大小超过了处理任务所需要的线程,那么 就会回收部分空闲(60 秒不执行任务)的线程,当任务数 增加时,此线程池又可以智能的添加新线程来处理任务。此 线程池不会对线程池大小做限制,线程池大小完全依赖于操 作系统(或者说 JVM)能够创建的最大线程大小。

(4)newScheduledThreadPool:创建一个大小无限的线 程池。此线程池支持定时以及周期性执行任务的需求。

public class ExecutorsDemo extends Thread {

         @Override

        public void run() {

System.out.println(Thread.currentThread().getName()+ "正在执行...");

        //try {

        // Thread.sleep(1000);

        //} catch (InterruptedException e) {

        // e.printStackTrace(); //}

}

public static void main(String[] args) {

        //可变大小的线程池

        //ExecutorService pool = Executors.newCachedThreadPool();

        //线程池指定的大小为2的固定大小

        ExecutorService pool = Executors.newFixedThreadPool(2);

        //创建几个线程

        Thread t1 = new ExecutorsDemo();

        Thread t2 = new ExecutorsDemo();

        Thread t3 = new ExecutorsDemo();

        Thread t4 = new ExecutorsDemo();

        //把线程加入到线程池中,并执行线程中的具体的实现

        pool.execute(t1);

        pool.execute(t2);

        pool.execute(t3);

        pool.execute(t4);

        //关闭线程池

        pool.shutdown(); } }

线程池值ThreadPoolExecutor详解

Executors和ThreaPoolExecutor创建线程池的区别

《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方 式,这样的处理方式让写的同学更加明确线程池的运行规 则,规避资源耗尽的风险

Executors 各个方法的弊端:

newFixedThreadPool 和 newSingleThreadExecutor: 主 要问题是堆积的请求处理队列可能会耗费非常大的内存,甚 至 OOM。

newCachedThreadPool 和 newScheduledThreadPool: 主要问题是线程数最大数是 Integer.MAX_VALUE,可能会 创建数量非常多的线程,甚至 OOM。

ThreaPoolExecutor创建线程池方式只有一种,就是走它的 构造函数,参数自己指定

你知道怎么创建线程池吗?

创建线程池的方式有多种,这里你只需要答 ThreadPoolExecutor 即可。

ThreadPoolExecutor() 是最原始的线程池创建,也是阿里 巴巴 Java 开发手册中明确规范的创建线程池的方式。

ThreaPoolExecutor构造函数重要参数
ThreaPoolExecutor:3个重要参数

corePoolSize :核心线程数,线程数定义了最小可以同时 运行的线程数量。

maximumPoolSize :线程池中允许存在的工作线程的最大 数量

workQueue:当新任务来的时候会先判断当前运行的线程 数量是否达到核心线程数,如果达到的话,任务就会被存放 在队列中。

ThreaPoolExecutor其他常见参数

keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不 会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;

unit :keepAliveTime 参数的时间单位。

threadFactory:为线程池提供创建新线程的线程工厂

handler :线程池任务队列超过

maxinumPoolSize 之后的 拒绝策略

一个简单的线程池Demo:Runnable+ThreadPoolExecutor

public class MyRunnable implements Runnable{
    private String name;

    public MyRunnable(String s) {
        this.name = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始时间"+new Date());
        try {
            Thread.sleep(1000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束时间" +new Date());
    }
}

public class Test {
    public static final int CORE_POOL_SIZE = 5;
    public static final int MAX_POOL_SIZE = 10;
    public static final long KEEP_POOL_SIZE = 10;
    public static final int WORK_QUEUE = 100;

    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_POOL_SIZE,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(WORK_QUEUE));

        for (int i = 0; i < 10; i++) {
            MyRunnable myRunnable = new MyRunnable("线程" + i);
            threadPoolExecutor.execute(myRunnable);
        }

        threadPoolExecutor.shutdown();
    }
}

线程交互

wait()方法 notify()方法 notifyAll()方法

这三个方法,都是Object类的方法,不是线程的方法

wait()方法:释放占有的对象锁,线程进入等待池,释放 cpu,而其他正在等待的线程可以抢占此锁,获取到锁的线 程即可运行程序。

它和sleep()方法不同,线程调用sleep()方法,会休眠一段 时间,休眠期间会暂时释放cpu,但是并不释放对象锁,也 就是说,在休眠期间,其他线程无法进入此代码内部,休眠 结束,线程重新获得cpu,执行代码

wait()和sleep()方法的最大区别:wait()方法会释放对象 锁,sleep()不会

notify()方法:该方法会唤醒因为调用对象的wait()方法而等 待的线程,其实就是对 对象锁的唤醒,从而使得wait()方法 的线程可以有机会获取对象锁,调用notify()方法后,不会 立即释放锁,而是继续执行当前代码,直到synchronized 中的代码全部执行完毕后,才会释放对象锁

注意:wait()方法和notify()方法都需要在synchronized代 码块中调用

为什么 wait(), notify()和 notifyAll()必须在同步方法或者同 步块中被调用?

当一个线程需要调用对象的 wait()方法的时候,这个线程必 须拥有该对象的锁,接着它就会释放这个对象锁并进入等待 状态直到其他线程调用这个对象上的 notify()方法。

同样的,当一个线程需要调用对象的 notify()方法时,它会 释放这个对象的锁,以便其他在等待的线程就可以得到这个 对象锁。

由于所有的这些方法都需要线程持有对象的锁,这样就只能 通过同步来实现,所以他们只能在同步方法或者同步块中被 调用。

示例:

public class WaitDemo implements Runnable{
    int count = 1;

    @Override
    public void run() {

        while (count <= 50){
            synchronized (ThreadDemo.obj){  //锁一个对象
                try {

                    if (count != 0) {
                        //线程第一次执行的时候,不需要唤醒,排除count=0的情况
                        ThreadDemo.obj.notify();
                    }

                    if (count % 2 == 1){
                        System.out.println("线程A1" +"---"+count);
                    }else{
                        System.out.println("线程B1" +"---"+count);
                    }
                    ThreadDemo.obj.wait();
                    count++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
public class WaitDemo1 implements Runnable{
    int count = 1;

    @Override
    public void run() {

        while (count <= 50){
            synchronized (ThreadDemo.obj){  //锁一个对象
                try {

                ThreadDemo.obj.notify();         //唤醒A
                    if (count % 2 == 1){
                        System.out.println("线程A2" +"---"+count);
                    }else{
                        System.out.println("线程B2" +"---"+count);
                    }

//                Thread.sleep(1000l);
                    if (count != 50) {
                        ThreadDemo.obj.wait();      //B进入等待}
                    }
                        count++;
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
public class ThreadDemo {
    public static final Object obj = new Object();

    public static void main(String[] args) {

        new Thread(new WaitDemo()).start();
        new Thread(new WaitDemo1()).start();
    }


}

* B站面试题: * 创建两个线程,一个线程输出A一个线程输出B

* 最后结果输出为AABBAABB....循环打印一百次 * 要求每次输出一个线程睡眠一个线程唤醒

面试题: sleep() 和 wait() 有什么区别?

两者都可以暂停线程的执行

类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。

是否释放锁:sleep() 不释放锁;wait() 释放锁。

用途不同:Wait 通常被用于线程间交互/通信,sleep 通常 被用于暂停执行。

用法不同:wait() 方法被调用后,线程不会自动苏醒,需要 别的线程调用同一个对象上的 notify() 或者 notifyAll() 方 法。

sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。

生产消费模型(不是面向对象设计模式)

更准确说法,应该是 "生产者--消费者---仓库" 模型

1,生产者仅仅在仓储未满的时候,生产,仓储满了,停止 生产

2,消费者,在仓储有产品的时候,才能消费,仓储空了, 则等待

3,当消费者发现仓储没有产品,会通知生产者去生产

4,生产者生产出可消费的产品,通知消费者去消费

这个模型下,需要哪些对象(类)?

生产、消费、仓库、产品

生产:生产方法

消费:消费方法

产品

仓库: 添加数据,需要判断,仓库满了没有,满了等待消费,没有 满,通知生产者生产

减少数据,判断,没有产品了,等待生产,消费完了,通知 生产者生产

/*
消费者
 */
public class Consumer extends Thread{
 //仓库对象,生产者生产的商品放入仓库中
 Factory factory;
 //构造方法
 public Consumer(Factory factory) {
 this.factory = factory;
 }
 @Override
 public void run() {
 //循环消费产品
 for (int i = 1; i <= 50; i++) {
 //每次消费产品,其实就是在从仓库中拿取商品
 //拿取的只能生产者生成出来
 Product product = factory.getProduct();
 System.out.println("消费消费了:" + product.num + "号产品");
 }
 }
}
/*
生产者
 */
public class Producer extends Thread {
 //仓库对象,生产者生产的商品放入仓库中
 Factory factory;
 //构造方法
 public Producer(Factory factory) {
 this.factory = factory;
 }
 @Override
 public void run() {
 //循环生成产品
 for (int i = 1; i <= 50; i++) {
 //每次生产产品,其实就是在往仓库中添加商品
 //每次添加的商品都是新生产的,可以加个编号作为表示
 factory.push(new Product(i));
 System.out.println("生产者生产了:" + i + "号产品");
 }
 }
}
/*
仓库
 */

public class Factory {
 //声明一个存放商品的空间 10个
 Product[] products = new Product[10];
 //计数器
 int count = 0;
 //生产方法
 public synchronized void push(Product product){
 //如果产品数量满了,就等待,停止生产,消费者消费了,才能继续生产
 while (count >= products.length-1){
 try {
 this.wait(); //生产者等待
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 //如果仓库没有满,继续生产
 products[count] = product;
 count++;
 this.notifyAll(); //唤醒
 }
 public synchronized Product getProduct(){
 //如果仓库中没有商品了,这个时候,消费者等待
 while (count <= 0){
 try {
 this.wait(); //消费者等待
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 count--; //消费一次,就是数量少一个
 //拿到仓库中的商品,并返回
 try {
 Thread.sleep(10L);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 Product product = products[count];
 this.notifyAll();
 return product;
 }
}
/*
商品
 */
public class Product {
 int num;
public Product(int num) {
 this.num = num;
 }
}
public class Test {
 public static void main(String[] args) {
 Factory factory = new Factory();
 //创建生产者和消费者线程,同时操作仓库
 new Producer(factory).start();
 new Consumer(factory).start();
 }
}

银行取钱案例

/*
账户类
 */
public class Account {
 int money; //余额
 String name; //卡名
 public Account(int money, String name) {
 this.money = money;
 this.name = name;
 }
}



package com.iweb.airui369.thread07;
public class Bank extends Thread {
 Account account; //账户对象
 int drawMoney; //取钱的值
 int haveMoney; //有多少钱了
 //标记变量
 boolean flag = true;
 public Bank(String name,Account account, int drawMoney) {
 super(name);
 this.account = account;
 this.drawMoney = drawMoney;
 }
 @Override
 public void run() {
 while (flag){
 takeMoney();
 }
 }
 //取钱
 public void takeMoney(){
 if (account.money <= 0){
 flag = false;
 return;
 }
 //锁定账户对象
 synchronized (account){
 //如果账户余额不足,就不能取了
 if (drawMoney > account.money){
 System.out.println(Thread.currentThread().getName()+
 "想取钱,但是余额不足了");
 return;
 }
 try {
 Thread.sleep(100);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //新余额 = 旧余额-每次取的前
 account.money = account.money - drawMoney;
 //手里的钱 = 手里已有的钱 + 新取的钱
 haveMoney = haveMoney + drawMoney;
 System.out.println(
 this.getName() + "本次取了"+ drawMoney + ",现在手里一
 +haveMoney + ",目前的账户余额是:"+ account.money);
 }
}
}



public class Test {
 public static void main(String[] args) {
 Account account = new Account(1000, "建行卡");
 Bank t1 = new Bank("你", account, 50);
 Bank t2 = new Bank("女朋友", account, 200);
 t1.start();
 t2.start();
 }
}

JBDC

概念:

        JDBC(Java Data Base Connectivity) Java数据库连 接,是一种用于执行SQL语句的Java的API

        它是由一组使用Java语言编写的类和接口组成。

        本质上是Sun公司定义的一套操作所有关系型数据库的 规则(接口),将来每个数据库厂商,去写实现类实现接口, 提供数据库jar包,程序员可以使用提供的jar包进行编程。

JDBC快速实现

public class JDBCDemo {
    public static void main(String[] args)  {
        Connection conn = null;
        Statement statement = null;

        try {
            //1,导入jar包

            //2,加载驱动
           Class.forName("com.mysql.jdbc.Driver");


            //3,通过注册驱动,获得数据库连接
           conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_mysql", "root", "123456");

            //定义sql语句
            //修改
            // String sql = "update student set sage = '34' where sno = 's001'";

            //增加
            //String sql = "insert into student values('s011','张三',1,'男')";

            //删除
            String sql = "delete from student where sno = 's010'";


            //5,获取执行sql对象
            statement = conn.createStatement();


            //6,通过stmt对象,调用方法,执行sql,如果执行成功,返回一个值,表示执行的行数
            long i = statement.executeLargeUpdate(sql);

            //7,处理结果
            System.out.println(i > 0 ? "执行成功" : "执行失败");
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }

        //8,关闭资源
        try {
            statement.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
}

jdbc:mysql://localhost:3306/数据库名?

serverTimezone=GMT%2B8&useSSL=false&useUnico de=true&characterEncoding=utf-8

oracle连接:

驱动地址 :oracle.jdbc.OracleDriver

connection连接地址: jdbc:oracle:thin:@//192.168.100.160:1521/ORCL

jdbc实现增删改

public class JDBCDemo2 {
    public static void main(String[] args) {

        //获取数据库连接
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_mysql?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8", "root", "123456");

            //
            stmt = conn.createStatement();

            //
            String sql = "select * from student";

            //
            rs = stmt.executeQuery(sql);


            //
            while (rs.next()){
                String id = rs.getString("sno");
                String name = rs.getString("sname");
                int sage = rs.getInt("sage");
                String ssex = rs.getString("ssex");
                System.out.println(id + "--" + name + "--" + sage);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                rs.close();
                stmt.close();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

jdbc实现查询

public class JDBCDemo3 {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //获取数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_mysql", "root", "123456");
            // conn = JBDCUtil.getConnection();
            //conn获取stmt对象
            stmt = conn.createStatement();
            //定义sql
            String sql = "select * from students";
            //执行sql
            rs = stmt.executeQuery(sql);
            //创建一个list集合,用来存放获取到的对象
            ArrayList<Student> list = new ArrayList<>();
            Student student = null;
            //获取rs中的数据
            while (rs.next()){
                student = new Student();
                //获取到数据库中的数据,并把数据赋值给每个student对象
                student.setSid(rs.getInt("sid"));
                student.setSage(rs.getInt("sage"));
                student.setSclass(rs.getString("sclass"));
                student.setSname(rs.getString("sname"));
                student.setSmajor(rs.getString("smajor"));
                student.setSnativeplace(rs.getString("snativeplace"));
                student.setSsex(rs.getString("ssex"));
                student.setSnative(rs.getString("snative"));
                //将获取到值的student对象,加到list中去
                list.add(student);
            }
            System.out.println(list);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
               /* if (rs != null) {
                    rs.close();
                }
                if (stmt != null){
                    stmt.close();
                }
                if (conn != null){
                    conn.close();
                }*/
                JBDCUtil.closeConn(rs,stmt,null,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
            }
        }
    }

    }
public class Student {

    //属性和数据库的字段一致
    private int sid;
    private String sname;
    private int sage;
    private String ssex;
    private String snativeplace;
    private String smajor;
    private String sclass;
    private String snative;

    public Student() {
    }

    public Student(int sid, String sname, int sage, String ssex, String snativeplace, String smajor, String sclass, String snative) {
        this.sid = sid;
        this.sname = sname;
        this.sage = sage;
        this.ssex = ssex;
        this.snativeplace = snativeplace;
        this.smajor = smajor;
        this.sclass = sclass;
        this.snative = snative;
    }

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getSage() {
        return sage;
    }

    public void setSage(int sage) {
        this.sage = sage;
    }

    public String getSsex() {
        return ssex;
    }

    public void setSsex(String ssex) {
        this.ssex = ssex;
    }

    public String getSnativeplace() {
        return snativeplace;
    }

    public void setSnativeplace(String snativeplace) {
        this.snativeplace = snativeplace;
    }

    public String getSmajor() {
        return smajor;
    }

    public void setSmajor(String smajor) {
        this.smajor = smajor;
    }

    public String getSclass() {
        return sclass;
    }

    public void setSclass(String sclass) {
        this.sclass = sclass;
    }

    public String getSnative() {
        return snative;
    }

    public void setSnative(String snative) {
        this.snative = snative;
    }

    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                ", sage=" + sage +
                ", ssex='" + ssex + '\'' +
                ", snativeplace='" + snativeplace + '\'' +
                ", smajor='" + smajor + '\'' +
                ", sclass='" + sclass + '\'' +
                ", snative='" + snative + '\'' +
                '}';
    }
}

JDBC中使用的对象总结

DriverManager:驱动管理对象

        功能:

                1,注册驱动,告诉程序使用哪个数据库驱动jar 包,mysql5版本之后,可以省略这一步                 2,获取数据库连接 Connection:

                        static Connection getConnection(String url, String user, String password)

                        尝试建立与给定数据库URL的连接。

                        参数 : url 表示连接数据库的地址

                                mysql地址 :jdbc:mysql://主 机ip:3306/数据库名

                                oracle地址 : jdbc:oracle:thin:@//主机ip:1521/ 数据库名 user 表示连接数据库的用户名password 表示连接数据库的密码

Connection:数据库连接对象

        功能 :

                1,获取执行sql语句的对象

                Statement createStatement()

                         创建一个 Statement对象,用于将SQL语句发 送到数据库。

                PreparedStatement prepareStatement(String sql)

                        创建一个 PreparedStatement对象,用于将 参数化的SQL语句发送到数据库。

                2,管理事务

                        void setAutoCommit(boolean autoCommit) 将此连接的自动提交模式设置为给 定状态。

                        void commit() 使自上次提交/回滚以来所做 的所有更改

                        void rollback() 撤消在当前事务中所做的所有 更改

Statement:执行sql对象

        功能:

                1,执行sql语句

                        ResultSet executeQuery(String sql) 执行给定的 SQL语句,该语句返回单个 ResultSet对象。

                        int executeUpdate(String sql) 执行给定的SQL 语句,这可能是 INSERT , UPDATE ,或 DELETE语句,或者不返回任何内容,如SQL DDL 语句的SQL语句。

ResutSet:查询返回结果集对象,封装查询结果的

        ResultSet对象保持一个光标指向其当前的数据行。

        最初,光标位于第一行之前。 next方法将光标移动到 下一行,并且由于在ResultSet对象中没有更多行时返回 false ,因此可以在while循环中使用循环来遍历结果集。

                常用方法:

                        1,boolean next() 将光标从当前位置向前移动 一行。

                        2,getXxx(参数) 方法 :获取结果集中具体值的方 法 方法名Xxx : 获取到的数据类型是什么类型, 这里的Xxx就用什么类型

                        比如,字段是int类型的, 那么就用 getInt(),字段是字符串类型,就用 getString()                         字段是小数类型,就用 getDouble() 参数 :参数可以传入两个类型,一个int ,一 个String

                         int代表传入的是获取第几列的数据

                         String代表传入获取指定的字段名的数 据,如果字段名是sname,类型String

                        方法的写法: getString("sname")

PreparedStatement:处理预编译的sql

SQL注入问题 :

        编写SQL语句的时候,使用字符串拼接的方式,拼接数 据,会产生的一些问题

        为了避免这个问题,JDBC中提供了 PreparedStatement对象,可以通过处理预编译SQL的方 式,完成SQL的查询

        用法 :

                1,定义sql的时候,将需要传入的参数,使用 ? 代 替,

                        比如 SELECT * FROM USER WHERE NAME = ? AND pwd = ?

                2,conn对象调用方法,传入sql语句,进行预编 译处理,并返回pstmt对象

                3,通过pstmt对象,完成 ?(占位符) 的赋值,调 用setXxx(参数)方法

                        set方法的名称类型和你要赋值的实际字段类 型一致

                        参数: 第一个参数表示 给第几个 ? 赋值, 第二个参数表示具体的值

                4,通过pstmt对象,调用方法,执行sql

                        executeQuery() 执行查询

                        executeUpdate() 执行增删改

        让用户输入用户名和密码完成登录的判断

public class Login {
    public static void main(String[] args) throws SQLException {

        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入用户名:");
        String name = scanner.next();

        System.out.println("请输入密码:");
        int password = scanner.nextInt();

        //获取数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_mysql?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8", "root", "123456");

        //conn获取stmt对象
         Statement stmt = conn.createStatement();
         //定义sql
         String sql = "SELECT * FROM USER WHERE NAME = '" +username + "' AND
         System.out.println(sql);
         //执行sql
         ResultSet rs = stmt.executeQuery(sql);
         if (rs.next()){
             System.out.println("用户存在,登录成功!");
         }else {
             System.out.println("用户信息错误,请重新输入");
         }
     }
}

输入一下内容,认识SQL注入问题:

         请输入用户名:

                sasdgasg

        请输入密码

                 safsdf'or'a'='a

                SELECT * FROM USER WHERE NAME = 'sasdgasg' AND pwd = 'safsdf'or'a'='a'

        用户存在,登录成功!

        使用pstmt解决SQL注入问题

public class Login {
    public static void main(String[] args) throws SQLException {

        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入用户名:");
        String name = scanner.next();

        System.out.println("请输入密码:");
        int password = scanner.nextInt();

        //获取数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_mysql?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8", "root", "123456");

        //定义sql
        String sql = "SELECT * FROM USER WHERE name = ? AND pwd = ?";

        //conn获取pstmt对象
        PreparedStatement pstmt = conn.prepareStatement(sql);

        //通过pstmt,完成占位符的赋值
        //赋值的数据类型和set方法后面跟的类型是一样的
        pstmt.setString(1,name);
        pstmt.setInt(2,password);

        //执行sql
        ResultSet rs = pstmt.executeQuery();

        if (rs.next()){
            System.out.println("用户存在,登录成功!");
        }else {
            System.out.println("用户信息错误,请重新输入");
        }
    }
}

事务的处理

通过转账案例,模拟事务的处理流程

一次转账中,包含两次修改操作,这两次操作应该在同一个 事务中,如果第一次操作之后,发生了异常,应该将数据回 滚,否则数据的一致性不能得到保证

出现异常的情况

public class JDBCDemo4 {
    public static void main(String[] args) {

        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_mysql?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8", "root", "123456");

            conn.setAutoCommit(false);  //手动开启事务
            //事务1
            String sql1 = "update tb_account set money = money-500 where name = 'tom'";
            pstmt1 = conn.prepareStatement(sql1);
            ResultSet i = pstmt1.executeQuery();

            int a = 3 / 0;  //模拟异常

            //事务2
            String sql2 = "update tb_account set money = money+500 where name = 'jack'";
            pstmt2 = conn.prepareStatement(sql2);
            ResultSet j = pstmt1.executeQuery();

            
        }

    }
}

使用Connection解决事务问题

public class JDBCDemo4 {
    public static void main(String[] args) {

        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_mysql?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8", "root", "123456");

            conn.setAutoCommit(false);  //手动开启事务
            //事务1
            String sql1 = "update tb_account set money = money-500 where name = 'tom'";
            pstmt1 = conn.prepareStatement(sql1);
            ResultSet i = pstmt1.executeQuery();

            int a = 3 / 0;  //模拟异常

            //事务2
            String sql2 = "update tb_account set money = money+500 where name = 'jack'";
            pstmt2 = conn.prepareStatement(sql2);
            ResultSet j = pstmt1.executeQuery();

            //提交事务
            conn.commit();
            //恢复自动提交
            conn.setAutoCommit(true);
        } catch (SQLException e) {
            try {
                //将来发生异常后,进入这个代码块,需要将事务回滚
                conn.rollback();
                //恢复自动提交
                conn.setAutoCommit(true);
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            try {
                /*if (pstmt1 != null){
                    pstmt1.close();
                }
                if (pstmt2 != null){
                    pstmt2.close();
                }
                if (conn != null){
                    conn.close();
                }*/
                JBDCUtil.closeConn(null,null,pstmt1,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

编写JDBC工具类

工具类主要是为了简化书写,将共同的内容抽取到方法 中使用

需要抽取哪些?

        1,注册驱动

         2,获取数据库连接方法

                解决问题:不想每次调用都去传递参数,还得保证 工具类的通用性

        3,关闭资源方法

url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
username=root
password=123456
driver=com.mysql.jdbc.Driver
public class JBDCUtil {
    //想办法,让变量值可以动态的获取,可以根据用户的需求自定义传入的url、user
    //而且传入之后,得让数据能获取,并加载
   /* private static String url;
    private static String username;
    private static String password;
    private static String driver;
    static {
        try {
            //加载配置properties配置文件
            InputStream is = JBDCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(is);
            //获取到配置文件中的数据
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            driver = properties.getProperty("driver");
            Class.forName(driver);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }*/
    //获取连接方法
    public static Connection getConnection() throws SQLException {
        return new JDBCMysql().getConnection();       //DriverManager.getConnection(url, username, password);
    }

    //获取pstmt对象的方法封装
    public static PreparedStatement getPstmt(String sql,Connection conn) throws SQLException {
        return conn.prepareStatement(sql);
    }



    //关闭方法
    public static void closeConn(ResultSet rs, Statement stmt,PreparedStatement pstmt, Connection conn) throws SQLException {
        try {
            if (rs != null){
                rs.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (pstmt != null) {
                pstmt.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
        }
    }
}

数据库连接池

为什么需要数据库连接池?

因为频繁的获取Connection对象,需要消耗很多的资 源,所以可以利用池子技术,在连接池中放入创建好的 Connection对象,下次使用,直接从池子中获取,用完之 后,调用close()方法,将对象再还到池子里。

package com.javaweb.jdbc.privateTest;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.Properties;
import java.util.logging.Logger;

public class JDBCMysql  implements DataSource {

    //创建连接池

    private static LinkedList<Connection> list = new LinkedList<>();

    //希望类的加载的时候,就把连接池中的Connection对象
    static {

        try {
            //获取properties文件的摄输入流
            InputStream is
                    = JDBCMysql.class.getClassLoader().getResourceAsStream("jdbc.properties");

            //创建Properties,并加载输入流
            Properties properties = new Properties();
            properties.load(is);
            String url = properties.getProperty("url");
            String username = properties.getProperty("username");
            String password = properties.getProperty("password");
            String driver = properties.getProperty("driver");
            //注册驱动
            Class.forName(driver);
            //获取conn连接对象,并将连接对象,放入池中
            for (int i = 0; i < 10; i++) {
                Connection conn = DriverManager.getConnection(url, username,password);
                //System.out.println(conn);
                        list.add(conn);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Connection getConnection() throws SQLException {
        System.out.println(list);
        System.out.println("当前池子中,还有:"+list.size()+"个conn对象");
        Connection conn = list.removeFirst();
        System.out.println("conn对象被取走一个,当前池子中,还有:"+list.size()+"个conn对象");
        //让Proxy动态代理Connection对象,判断connection将来执行的方法
        return (Connection) Proxy.newProxyInstance(JDBCMysql.class.getClassLoader(),
                conn.getClass().getInterfaces(),
                new InvocationHandler() {
                    //将来当Connection执行方法的时候,都会经过这个invoke方法
                    //只需要判断使用的是否是close方法,如果close,就单独处理
                    //如果是其他方法,正常执行
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (!method.getName().equals("close")){
                            //其他情况继续执行
                           return method.invoke(conn,args);
                        }
                        //将conn对象还到池子里
                        list.add(conn);
                        System.out.println("池子中归还了一个对象,现在大小是:"+list.size()+"个conn对象");
                        return null;
                    }
                });

    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
public class TestConn {
 public static void main(String[] args) throws SQLException {
 JDBCPool pool = new JDBCPool();
 pool.getConnection(); //剩9
 pool.getConnection(); //剩8
 pool.getConnection(); //剩7
 //这个conn获取后,就归还
 Connection conn = pool.getConnection(); // 剩6
 conn.close(); //剩7
 pool.getConnection();// 剩6
 }
}

工具类结合连接池的使用

package com.javaweb.jdbc.privateTest;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JBDCUtil {
    //想办法,让变量值可以动态的获取,可以根据用户的需求自定义传入的url、user
    //而且传入之后,得让数据能获取,并加载
   /* private static String url;
    private static String username;
    private static String password;
    private static String driver;
    static {
        try {
            //加载配置properties配置文件
            InputStream is = JBDCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(is);
            //获取到配置文件中的数据
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            driver = properties.getProperty("driver");
            Class.forName(driver);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }*/
    //获取连接方法
    public static Connection getConnection() throws SQLException {
        return new JDBCMysql().getConnection();       //DriverManager.getConnection(url, username, password);
    }

    //获取pstmt对象的方法封装
    public static PreparedStatement getPstmt(String sql,Connection conn) throws SQLException {
        return conn.prepareStatement(sql);
    }



    //关闭方法
    public static void closeConn(ResultSet rs, Statement stmt,PreparedStatement pstmt, Connection conn) throws SQLException {
        try {
            if (rs != null){
                rs.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (pstmt != null) {
                pstmt.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
        }
    }
}

BaseDao 中的方法

package com.javaweb.jdbc.emptest.dao;

import com.javaweb.jdbc.emptest.bean.Emp;
import com.javaweb.jdbc.emptest.urli.JBDCUtil;

import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
//增删改语句的通用方法
public class BaseDao {
    //修改通用方法
    public boolean update(String sql,Object...params){

        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            //获取connection方法
            conn = JBDCUtil.getConnection();
            //获取PreparedStatement方法
            pstmt = JBDCUtil.getPstmt(sql, conn);
            //动态绑定参数
            JBDCUtil.bindPstmt(pstmt,params);
            //执行sql
            int i = pstmt.executeUpdate();
            return  i > 0 ? true : false;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                JBDCUtil.closeConn(null,null,pstmt,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    //查询方法的公共方法
    //查询单个
    public Emp QueryOne(String sql,Object...params){

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        Emp emp = null;
        //ArrayList<Emp> list = null;

        try {
            //通过工具类,获取connection方法
            conn = JBDCUtil.getConnection();
            //获取PreparedStatement方法
            pstmt = JBDCUtil.getPstmt(sql, conn);
            //动态绑定参数
            JBDCUtil.bindPstmt(pstmt,params);
            //pstmt执行获得结果集对象
            rs = pstmt.executeQuery();

            //通过rs获取ResultSetMetaData对象
            ResultSetMetaData metaData = rs.getMetaData();

            emp =  new Emp();
            //list = new ArrayList<>();
            while (rs.next()){
                //emp = new Emp();
                //循环的次数根据列的数量来判断
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    //BeamUtils工具类调用方法,传入需要绑定的数据,
                    //完成那个对象数据的动态绑定
                    BeanUtils.setProperty(emp,
                            metaData.getColumnLabel(i+1),
                            rs.getObject(i+1)
                            );
                }
                /*emp.setEmpno(rs.getInt("empno"));
                emp.setEname(rs.getString("ename"));
                emp.setJob(rs.getString("job"));
                emp.setMgr(rs.getInt("mgr"));
                emp.setHiredate(rs.getString("hiredate"));
                emp.setSal(rs.getDouble("sal"));
                emp.setComm(rs.getDouble("comm"));
                emp.setDeptno(rs.getInt("deptno"));*/
                //循环
                //list.add(emp);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            try {
                JBDCUtil.closeConn(rs,null,pstmt,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return emp;
    }

    //查询多个
    public List<Emp> QueryAll(String sql, Object...params){

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        ArrayList<Emp> list = null;

        try {
            //通过工具类,获取connection方法
            conn = JBDCUtil.getConnection();
            //获取PreparedStatement方法
            pstmt = JBDCUtil.getPstmt(sql, conn);
            //动态绑定参数
            JBDCUtil.bindPstmt(pstmt,params);
            //pstmt执行获得结果集对象
            rs = pstmt.executeQuery();

            //通过rs获取ResultSetMetaData对象
            ResultSetMetaData metaData = rs.getMetaData();

            Emp emp = null;
            list = new ArrayList<>();
            while (rs.next()){
                emp = new Emp();
                //循环的次数根据列的数量来判断
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    //BeamUtils工具类调用方法,传入需要绑定的数据,
                    //完成那个对象数据的动态绑定
                    BeanUtils.setProperty(emp,
                            metaData.getColumnLabel(i+1),
                            rs.getObject(i+1)
                    );
                }
               /* emp.setEmpno(rs.getInt("empno"));
                emp.setEname(rs.getString("ename"));
                emp.setJob(rs.getString("job"));
                emp.setMgr(rs.getInt("mgr"));
                emp.setHiredate(rs.getString("hiredate"));
                emp.setSal(rs.getDouble("sal"));
                emp.setComm(rs.getDouble("comm"));
                emp.setDeptno(rs.getInt("deptno"));*/
                //循环
                list.add(emp);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            try {
                JBDCUtil.closeConn(rs,null,pstmt,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return list;
    }


    //通用的方法,将任何的实体类都可以通过这个查询方法查询数据
    public <T> T QueryOne(Class<T> tClass,String sql,Object...params){
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        T bean = null;

        try {
            //通过工具类,获取connection方法
            conn = JBDCUtil.getConnection();
            //获取PreparedStatement方法
            pstmt = JBDCUtil.getPstmt(sql, conn);
            //动态绑定参数
            JBDCUtil.bindPstmt(pstmt,params);
            //pstmt执行获得结果集对象
            rs = pstmt.executeQuery();

            //通过rs获取ResultSetMetaData对象
            ResultSetMetaData metaData = rs.getMetaData();

            while (rs.next()){
                //通过工具类获取需要操作的对象
                 bean = tClass.newInstance();
                //循环的次数根据列的数量来判断
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    //BeamUtils工具类调用方法,传入需要绑定的数据,
                    //完成那个对象数据的动态绑定
                    BeanUtils.setProperty(bean,
                            metaData.getColumnLabel(i+1),
                            rs.getObject(i+1)
                    );
                }
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            try {
                JBDCUtil.closeConn(rs,null,pstmt,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return bean;
    }

    //删除通用方法


}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值