Java-基础
JavaSE的一些基础内容,
关于更基础的如:环境安装,语法,面向对象,数组,异常处理等就没有做多概述
IO
四大基流 :
输入流和输出流 , 字节流和字符流
根据功能划分:
- 节点流:可以从或向一个特定的地方(节点)读写数据,直接连接数据源。如最常见的是文件的FileReader,还可以是数组、管道、字符串,关键字分别为ByteArray/CharArray,Piped,String。
- 处理流(包装流):并不直接连接数据源,是对一个已存在的流的连接和封装,是一种典型的装饰器设计模式,使用处理流主要是为了更方便的执行输入输出工作,如PrintStream,输出功能很强大,又如BufferedReader提供缓存机制,推荐输出时都使用处理流包装。
一些特别的流类型:
- 转换流:转换流只有字节流转换为字符流,因为字符流使用起来更方便,我们只会向更方便使用的方向转化。如:InputStreamReader与OutputStreamWriter。
- 缓冲流:有关键字Buffered,也是一种处理流,为其包装的流增加了缓存功能,提高了输入输出的效率,增加缓冲功能后需要使用flush()才能将缓冲区中内容写入到实际的物理节点。但是,在现在版本的Java中,只需记得关闭输出流(调用close()方法),就会自动执行输出流的flush()方法,可以保证将缓冲区中内容写入。
- 对象流:有关键字Object,主要用于将目标对象保存到磁盘中或允许在网络中直接传输对象时使用(对象序列化)。
操作IO流的模板:
- 创建源或目标对象
- 创建IO流对象
- 进行包装
- 具体的IO操作
- 关闭资源
注意:程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源。如果不关闭该资源,那么磁盘的文件将一直被程序引用着,不能删除也不能更改。所以应该手动调用 close() 方法关闭流资源。
字节流
InputStream 和 OutputStream
InputStream的常见子类有:
- FileInputStream:看这个名字就知道用于从文件中读取信息。
- ByteArrayInputStream: 字节数组输入流,
- ObjectInputStream:序列化时使用 一般和ObjectOutputStream一起使用
- FilterInputStream: 过滤输入流,为基础的输入流提供一些额外的操作。
OutputStream的常见子类有:
- FileOutPutStream: 文件输出流对文件进行操作
- ByteArrayOutputStream: 字节数组输出流
- ObjectOutputStream: 序列化时使用 一般和OjbectInputStream一起使用
- FilterOutputStream:过滤输出流,为基础的输出流提供一些额外的操作。
FileInputStream 和 FileOutPutStream
FileInputStream是文件字节输入流,就是对文件数据以字节的方式来处理,如音乐、视频、图片等。
FileOutPutStream是文件字节输出流,
例子:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//1.根据文件夹的名字来创建对象
FileOutputStream fileOutputStream = new FileOutputStream("D:\\hello.txt");
//往文件里面一个字节一个字节的写入数据
try {
fileOutputStream.write((int) 'h');
fileOutputStream.write((int) 'e');
fileOutputStream.write((int) 'l');
fileOutputStream.write((int) 'l');
fileOutputStream.write((int) 'o');
String s = " world";
//入文件里面一个字节数组的写入文件
fileOutputStream.write(s.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
fileOutputStream.close();
//2.传文件夹的名字来创建对象
FileInputStream fileInputStream = new FileInputStream("D:\\hello.txt");
int by = 0;
//一个字节一个字节的读出数据
while ((by = fileInputStream.read()) != -1) {
System.out.println((char) by);
}
//关闭流
fileInputStream.close();
//3.通过File对象来创建对象
fileInputStream = new FileInputStream(new File("(D:\\hello.txt"));
byte[] bytes = new byte[10];
//一个字节数组的读出数据
while ((by = fileInputStream.read(bytes)) != -1) {
for (int i = 0; i < by; i++) {
System.out.print((char) bytes[i]);
}
}
//关闭流
fileInputStream.close();
}
}
ByteArrayInputStream 和 ByteArrayOutputStream
ByteArrayInputStream是字节数组输入流,它里面包含一个内部的缓冲区(就是一个字节数组 ),该缓冲区含有从流中读取的字节。
ByteArrayOutputStream是字节数组输出流
例子:
public class Test {
public static void main(String[] args) throws IOException {
//1.创建一个字节输出流对象
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//2.一个字节一个字节的写入数据
byteArrayOutputStream.write('h');
byteArrayOutputStream.write('e');
byteArrayOutputStream.write('l');
byteArrayOutputStream.write('l');
byteArrayOutputStream.write('o');
//3.一个字节数组的写入数据
byteArrayOutputStream.write(" world".getBytes());
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray();
//4.从这个流中读取数据
int b = 0;
//从这个流中一个字节一个字节的读数据
while ((b = byteArrayInputStream.read()) != -1) {
System.out.println((char) b);
}
byteArrayInputStream = new ByteArrayInputStream(bytes);
byte[] bs = new byte[10];
//5.从这个流中一次性读取bs.length的数据
while ((b = byteArrayInputStream.read(bs)) != -1) {
for (int i = 0; i < b; i++) {
System.out.print((char) bs[i]);
}
System.out.println();
}
}
}
ObjectInputStream 和 ObjectOutpuStream
ObjectInputStream是反序列化流,一般和ObjectOutputStream配合使用。
用ObjectOutputStream将java对象序列化然后存入文件中,然后用ObjectInputStream读取出来
这个类的作用,我的理解是有些类在这个程序生命周期结束后,还会被用到所以要序列化保存起来
例子:
class Data implements Serializable {
private int n;
public Data(int n){
this.n=n;
}
@Override
public String toString(){
return Integer.toString(n);
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Data w=new Data(2);
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("worm.out"));
//序列化对象,把对象写到worm.out里面
out.writeObject("Worm storage\n");
//序列化对象,把对象写到worm.out里面
out.writeObject(w);
out.close();
//从worm.out里面读取对象
ObjectInputStream in=new ObjectInputStream(new FileInputStream("worm.out"));
//读取String对象
String s=(String)in.readObject();
//读取Data对象
Data d=(Data)in.readObject();
System.out.println(s+"Data = "+d);
}
}
FilterInputStream 和 FilterOutputStream
FilterInputStream和FilteOutputStream分别是过滤输入流和过滤输出流,他们的作用是为基础流提供一些额外的功能
FilterInputStream常用子类
- DataInputStream:可以从流中读取基本数据类型,与DataOutpuStream配合一起使用
- BufferedInputStream:可以从缓冲区中读取数据,不用每次和文件的操作都进行实际操作了。
FilterOutputStream常用子类
- DataOutputStream:可以向文件中写入基本类型的数据
- PrintStream:用于产生格式化的输出
- BufferedOutputStream:通过缓冲区像文件中写入数据。
例子:
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("D:\\hello.txt"));
// 写入byte类型数据
dataOutputStream.writeByte(20);
// 写入short类型数据
dataOutputStream.writeShort(30);
// 写入int类型
dataOutputStream.writeInt(900);
// 写入float类型
dataOutputStream.writeFloat(12.3f);
// 写入long类型
dataOutputStream.writeLong(800L);
// 写入double类型
dataOutputStream.writeDouble(14.23);
//写入boolean类型
dataOutputStream.writeBoolean(true);
// 写入char类型
dataOutputStream.writeChar('中');
dataOutputStream.close();
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("D:\\hello.txt"));
System.out.println(dataInputStream.readByte());
System.out.println(dataInputStream.readShort());
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readFloat());
System.out.println(dataInputStream.readLong());
System.out.println(dataInputStream.readDouble());
System.out.println(dataInputStream.readBoolean());
System.out.println(dataInputStream.readChar());
dataInputStream.close();
//创建一个对象
PrintStream printStream = new PrintStream("D:\\hello.txt");
//写入一个字节数组
printStream.write("helloworld".getBytes());
//写入一个换行符号
printStream.println();
//格式化写入数据
printStream.format("文件名称:%s","hello.txt");
printStream.println();
printStream.append("abcde" );
printStream.close();
}
}
字符流
Reader 和 wirter
字符流:就是在字节流的基础上,加上编码,形成的数据流
字符流出现的意义:因为字节流在操作字符时,可能会有中文导致的乱码,所以由字节流引申出了字符流。
字符流最基础的两个类就是 Reader和 wirter,根据这两个派生而来类都含有read()和write() 的基本方法。
处理图片、视频、音乐的时候还是用字节流吧,处理文本文件的时候用字符流会好很多。
Reader类常见子类有:
- FileReader:文件输入流
- BufferedReader: 带缓冲区的字符输入流
Writer类常见子类有:
- FileWriter:文件输出流
- BufferedWriter:带缓冲区的字符输出流
FileReader 和 FileWriter
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
FileWriter fileWriter = new FileWriter("D:\\hello.txt");
//向文件里面写入文件
fileWriter.write("123");
//向文件里面写入文件,和writer的区别就是append返回的是FileWriter对象,而write没有返回值
fileWriter.append("hello world");
fileWriter.append("中");
//把流中的数据刷新到文件中,还能继续使用
// 如果没有刷新,也没有关闭流的话 数据是不会写入文件的
fileWriter.flush();
//关闭流
fileWriter.close();
FileReader fileReader = new FileReader("D:\\hello.txt");
int len = 0;
while ((len = fileReader.read()) != -1) {
System.out.println((char) len);
}
//用char数组读数据。
char[] chars = new char[1024];
while ((len = fileReader.read(chars)) != -1) {
System.out.println(chars);
}
fileReader.close();
}
}
BufferedReader 和 BufferedWriter
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
//从控制台得到输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
//创建文件
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\hello.txt"));
String input = null;
while(!(input = bufferedReader.readLine()).equals("exit")){
//将从控制台得到的数据写入文件
bufferedWriter.write(input);
//写入一个当前系统下的空行
bufferedWriter.newLine();
}
bufferedWriter.close();
bufferedReader.close();
}
}
多线程
普通方法调用和多线程
Process与Thread
- 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
- 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
核心概念
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程创建
继承Thread类
线程不一定立即执行,由CPU安排调度执行
实现Runnable
实现Callable接口
1.实现Callable接口, 需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行: Future
6.获取结果: boolean r1 = result1.get()
7.关闭服务: ser.shutdownNow();
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Callable
*
* @author zwt
*/
public class TestCallable implements Callable<String> {
//实现Callable 需要定义返回值类型
@Override
public String call() throws Exception {
//重写call方法 有返回值
System.out.println("执行Call方法");
return "执行完毕";
}
public static void main(String[] args) throws Exception {
//创建对象
TestCallable testCallable = new TestCallable();
//创建线程池
ExecutorService ser = Executors.newFixedThreadPool(2);
//执行
Future<String> submit = ser.submit(testCallable);
//获取结果
String string = submit.get();
//关闭服务
ser.shutdown();
}
}
Callable的运行方式
线程池运行
//创建线程池
ExecutorService ser = Executors.newFixedThreadPool(2);
//执行
Future<String> submit = ser.submit(testCallable);
//获取结果
String string = submit.get();
//关闭服务
ser.shutdown();
通过FutureTaskt运行
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class CallableTest implements Callable<String> {
public static void main(String[] args) {
FutureTask<String> task = new FutureTask<>(new CallableTest());
new Thread(task).start();
try {
String s = task.get();//获取返回值
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public String call() throws Exception {
System.out.println("运行Callable");
return "helloworld";
}
}
静态代理
静态代理,代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用,这样,当我们需要调用被代理类的方法时,可以通过调用代理类的方法来做到。
public class Proxy {
public static void main(String[] args) {
new Wang(new Zhen()).testa();
}
}
//共同实现的接口
interface A{
void testa();
}
//真实对象
class test implements A{
@Override
public void testa() {
System.out.println("我是真实对象");
}
}
//代理对象
class test1 implements A{
//注入共同的接口
private A a;
public Wang(A a) {
this.a=a;
}
@Override
public void testa() {
bbbbb();
a.testa();
ccccc();
}
private void ccccc() {
System.out.println("方法执行前");
}
private void bbbbb() {
System.out.println("方法执行后");
}
}
在Thread线程类中,实现方式跟静态代理十分相似
public class Proxy {
public static void main(String[] args) {
new test(new test()).testa(); //实现A接口
new Thread(()-> System.out.println("真实对象")).start();//实现Runnable接口
}
}
Lamda表达式
1.避免匿名内部类定义过多
2.质属于函数式编程
3.可以让代码看起来很简洁
例如:
new Thread(()-> System.out.println("真实对象")).start();//Lamada表达式
函数式接口
Functional interface(函数式接口)是JDK1.8新特性Lamda表达式的关键
定义:任何一个接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
如:
public interface Runnable{
public abstract void run ();
}
对于函数式接口,我们都可以通过Lamada表达式来创建该接口的对象
在Lambda表达式中,只有一行代码才可以简化成一行,如果有多行就需要用代码块包裹
public class TestLambda {
public static void main(String[] args) {
//只有一行代码的情况
Test test = () -> System.out.println("hello,world");
//多行代码的情况
Test test2 = () -> {
System.out.println("hello");
System.out.println("world");
};
test.a();
test2.a();
}
}
interface Test{
void a();
}
如果传参为单个参数,括号可以简化不写,但如果是多个参数,就必须把括号加上
public class TestLambda {
public static void main(String[] args) {
//只有一个参数可以简化括号
Test test1 = i -> System.out.println("hello,world"+i);
//多个参数必须把括号加上
Test2 test2 = (i,b) -> {
System.out.println("hello"+i);
System.out.println("world"+b);
};
test1.a();
test2.a();
}
}
interface Test{
void a(int i);
}
interface Test2{
void a(int i,int b);
}
线程的五大状态
创建状态:new Thread()线程对象一旦创建就进入到新生状态
就绪状态:当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行
运行状态:进入运行状态,线程才真正执行线程体中的代码块
阻塞状态:当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞时间解除后,重新进入就绪状态,等待cpu调度执行
死亡状态:线程中断或结束,一旦进入死亡状态,就不能再次启动
线程的方法
setPriority(int newPriority) #更改线程优先级
static void sleep(long millis)#线程休眠
void join() #线程合并
static void yield() #停止当前正在执行的线程 并执行其他线程
void interrupt() #中断线程,别用这种方式
boolean isAlive() #测试线程是否处于活动状态
线程停止
/**
* 测试线程停止
* 建议线程正常停止 利用次数 不建议死循环
* 建议使用标志位 设置一个标志位
* 不要使用JDK提供的停止线程方法
*/
public class StopTest implements Runnable {
//设置一个标识位
private boolean flag=true;
public static void main(String[] args) {
StopTest stop = new StopTest();
//启动线程
new Thread(stop).start();
for (int i = 0; i < 1000 ; i++) {
if (i==900){
//调用线程停止方法 让线程停止
stop.stop();
}
}
}
//准备线程停止方法
public void stop(){
this.flag=false;
}
@Override
public void run() {
int i=0;
while (flag){
i++;
System.out.println("run..Thread:"+i);
}
}
}
线程休眠
sleep
(时间)指定当前线程阻塞的毫秒数sleep
存在异常InterruptedExcetionsleep
时间达到后,线程进入就绪状态sleep
可以模拟网络延迟,倒计时等- 每个对象都有一个锁,
sleep
不会释放锁
/**
* 测试线程休眠
*/
public class SleepTest {
public static void main(String[] args) {
try{
tenDown();
}catch (Exception e){
e.printStackTrace();
}
}
private static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
};
}
线程礼让
Thread.yield()
1.礼让线程,让当前正在执行的线程暂停,但不阻塞
2.将线程从运行状态转为就绪状态
3.让CPU重新调度,但礼让不一定成功,看CPU心情
/**
* 测试线程礼让
*/
public class YeildTest {
public static void main(String[] args) {
MyYicld myYicld = new MyYicld();
new Thread(myYicld,"a").start();;
new Thread(myYicld,"b").start();
}
}
class MyYicld implements Runnable{
@Override
public void run() {
System.out.println("线程执行");
Thread.yield();//礼让
System.out.println("线程结束");
}
}
线程合并(插队)
thread.join()
Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
可以想象成插队
/**
* 线程合并join
*/
public class JoinTets implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("vip");
}
}
public static void main(String[] args) throws InterruptedException {
//开启线程
JoinTets join = new JoinTets();
Thread thread = new Thread(join);
thread.start();
for (int i = 0; i <1000 ; i++) {
if (i==50) thread.join();//插队
}
}
}
观测线程状态
通过thread.getStates()
观察线程的五大状态
/**
* 通过Thread.State观察线程的状态
*/
public class StateTest {
public static void main(String[] args) {
Thread thread =new Thread(() ->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);//一秒执行一次
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("/");
}
});
Thread.State state = thread.getState();//New 新生状态
System.out.println(state);
thread.start();//开启线程
state = thread.getState();//Run
while (state!=Thread.State.TERMINATED ){//只要线程不终止就一直运行
state= thread.getState();
System.out.println(state);
}
}
}
线程优先级
通过以下方式改变或获取优先级:
getPriority() setPriority(int xxx)
Java提供一个线程调度器来监控程序种启动后进入就绪状态的所有线程,线程调度器安装优先级确定应该调度哪个线程来执行
线程的优先级用数字表示.范围1-10
Thread.MIN_PRIORITY=1;
Thread.MAN_PRIORITY=10;
Thread.NNRM_PRIORITY=5;
/**
* 测试线程的优先级
*/
public class PriorityTest {
public static void main(String[] args) {
//主线程的优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority priority = new MyPriority();
Thread thread = new Thread(priority);
Thread thread1 = new Thread(priority);
Thread thread2 = new Thread(priority);
//先设置优先级再启动
thread.setPriority(1);
thread.start();
thread1.setPriority(10);
thread1.start();
thread2.setPriority(5);
thread2.start();
}
}
class MyPriority implements Runnable{
//打印名字和优先级
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程
通过thread.setDaemon(true)
;设置线程为守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不必等待守护线程执行完毕
如:GC垃圾回收线程,监控内存等
/**
* 测试守护线程
*/
public class DaemonTest {
public static void main(String[] args) {
You you = new You();
God god = new God();
Thread thread = new Thread(god);
thread.setDaemon(true);//设置成守护线程
thread.start();//守护线程启动
new Thread(you).start();//用户线程启动
}
}
//守护线程
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("守护线程");
}
}
}
//用户线程
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("正在执行");
}
System.out.println("执行完毕");
}
}
线程同步
synchronized锁
简介
synchronized是解决线程不安全的关键,它的实现原理就是队列和锁
由于我们可以通过private关键词来保证数据对象,只能被方法访问,所以我们只需要针对方法提出一套机制.这套机制就是synchronized关键字.
实现原理
synchronized方法控制对对象的访问,每个对象对应一把锁,每一个synchronized方法都必须获得调用该方法对象的锁才能执行,否则会线程阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续往下执行
缺陷:如果一个大的方法被申明synchronized将会影响效率
具体用法
它的用法有两种:
1.synchronized同步方法
//同步方法
public synchronized void method(int ages){}
synchronized方法 锁的是对象本身this
谁获得这个对象的锁才能执行这个方法
synchronized方法 锁的是对象本身this
谁获得这个对象的锁才能执行这个方法
2.synchronized同步块
synchronized(obj){}
同步块锁的是obj
Ojb称为同步监视器:
同步块锁的是obj
Ojb称为同步监视器:
他可以是任何对象,但是推荐使用共享资源对象
同步方法种无需指定同步监视器,因为同步方法的同步监视器就是this就是这个对象的本身
死锁
某一个同步块同时拥有 “两个以上对象的锁” 时,就可能会发生“死锁”的问题.
死锁避免方法
1.互斥条件:一个资源每次只能被一个进程使用。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
Lock锁
- 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock(可重复锁),可以显式加锁、释放锁。
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
TestLoca2 loca2 = new TestLoca2();
new Thread(loca2).start();
new Thread(loca2).start();
new Thread(loca2).start();
}
}
class TestLoca2 implements Runnable{
int ticket = 10;
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();//加锁
if (ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket--);
}else {
break;
}
}finally {
lock.unlock();//解锁
}
}
}
}
ReentrantLock与synchronized对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
线程通信
管程法
创建一个数据缓存区,生产者只需要往里面放数据,消费者只需要在里面取数据,两者互不干预
/**
*
* 管程法
* 测试生产者消费者模型-->利用缓冲区模型解决
*/
public class PCTest {
public static void main(String[] args) {
SysContainer s = new SysContainer();
//启动生产者
new Thread(new Productor(s)).start();
//启动消费者
new Thread(new Consumer(s)).start();
}
}
//生产者
class Productor implements Runnable{
SysContainer container;
public Productor(SysContainer container) {
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("当前生产:"+i);
}
}
}
//消费者
class Consumer implements Runnable{
SysContainer container;
public Consumer(SysContainer container) {
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken php = container.php();
System.out.println("消费:"+php.id);
}
}
}
//产品
class Chicken{
int id; //产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓存区
class SysContainer{
//容器大小
Chicken[] chickens= new Chicken[10];
//容器计数
int count =0;
//生产者放入产品
public synchronized void push(Chicken chicken){
if(count==chickens.length){
//产品放满了需要等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器没满需要继续生产
chickens[count]=chicken;
count++;
//通知消费者消费
this.notifyAll();
}
public synchronized Chicken php(){
//容器没产品时 需要等待
if (count==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken= chickens[count];
//消费完了通知生产者生产
this.notifyAll();
return chicken;
}
}
信号灯法
通过定义一个共有的变量,判断它是否为true
或false
,来决定线程是否执行或者是wait
等待
/**
* 测试生产者消费者问题二:信号灯法 设置标志位解决
*
*/
public class PCTest2 {
public static void main(String[] args) {
TV tv =new TV();
//启动线程
new Thread(new Player(tv)).start();
new Thread(new Watcher(tv)).start();
}
}
//生产者-->演员
class Player implements Runnable{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
tv.play("糖果超甜");
}else {
tv.play("姜还是老的Rua");
}
}
}
}
//消费者-->观众
class Watcher implements Runnable{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.look();
}
}
}
//产品
class TV{
//演员表演 观众等待 True
//观众观看 演员等待 false
String voice;//表演节目
boolean flag =true;//信号灯 标志位
//表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("大家好我们是:"+voice);
//通知观众观看
this.notifyAll();
this.voice=voice;
//修改状态
this.flag=false;
}
//观看
public synchronized void look(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:"+this.voice);
this.notifyAll();
//修改状态
this.flag=true;
}
}
线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试线程池
*/
public class TestPool {
public static void main(String[] args) {
//创建线程池
ExecutorService ser = Executors.newFixedThreadPool(10);
//无返回值时执行execute()方法 一般用来执行Runnable
// 有返回值时使用submit()方法 一般用来执行Callable
ser.execute(new MyThread());
ser.execute(new MyThread());
ser.execute(new MyThread());
ser.execute(new MyThread());
//关闭连接
ser.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
注解和反射
Annotation是从JDK5.0开始引入的新技术.
Annotation的作用:
不是程序本身,可以对程序作出解释.(这一点和注释(comment)没什么区别)
可以被其他程序(比如:编译器等)读取.
Annotation的格式:
注解是以"@注释名"在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value="unchecked").//抑制警告
Annotation在哪里使用?
可以附加在package , class , method , field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
内置注解
@Override: 定义在java.lang.Override中,此注释只适用于修辞方法﹐表示一个方法声明打算重写超类中的另一个方法声明.
@Deprecated: 定义在java.lang.Deprecated中,此注释可以用于修辞方法﹐属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择.(表示已过时的方法)
@suppressWarnings :定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息.与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了.
元注解
元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明.
这些类型和它们所支持的类在java.lang.annotation包中可以找到.〔(@Target , @Retention ,@Documented , @lnherited )
@Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
@Retention:表示需要在什么级别保存该注释信息﹐用于描述注解的生命周期
(SOURCE<CLASS< RUNTIME)
@Document:说明该注解将被包含在javadoc中(生成使用文档)
@Inherited:说明子类可以继承父类中的该注解
自定义注解
package cn.annotation;
import java.lang.annotation.*;
//自定义注解
@MyAnnotation(value = "test",age = 13)
public class Test {
//注解可以显示赋值,如果没有默认值 就必须给注解赋值
@MyAnnotation(value = "test1",id = 666)
public void Test1(){}
}
//定义注解 @Target注解可以定义在方法和类上
@Target(value ={ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME) //定义注解在运行时有效
@Documented //生成文档
@Inherited //子类可以继承父类的注解
@interface MyAnnotation {
//注解的参数:参数类型+参数名+(); 如果没用默认值会报错 可以通过default 定义默认值
String [] value() default "";
int age() default 0;
int id () default -1;//如果有默认值为-1 代表不存在
}
反射
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.String");
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
反射机制提供的功能
1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时判断任意一个类所具有的成员变量和方法
4.在运行时获取泛型信息
5.在运行时调用任意一个对象的成员变量和方法
6.在运行时处理注解
7.生成动态代理
反射的优缺点
优点
可以实现动态创建对象和编译,体现出很大的灵活性
缺点
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
反射相关API
java.lang.Class :代表一个类
java.lang.reflect.Method :代表类的方法
java.lang.reflect.Field :代表类的成员变量
java.lang.reflect.Constructor :代表类的构造器
Class类获取Class实例
一个类在内存中只有一个class对象
获取Class实例的方式
1.若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。
Class c1 =User.class;
2.已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class c1 =user.getClass();
3.已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出
ClassNotFoundException
Class c1 = Class.forName("cn.User");
4.内置基本数据类型可以直接用类名.Type
Class c1 = Integer.TYPE;
5.获取父类类型
Student student = new Student();
//获取父类类型
Class c2 = student.getClass();
Class superclass = c2.getSuperclass();
6.还可以利用ClassLoader
类加载与ClassLoader(类加载器)
类加载概念
加载:
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法去的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
链接:
将Java类型的二进制代码合并到JVM的运行状态之中的过程
验证:确保加载的类信息符合JVM规范,没有安全方面的问题
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
初始化:
执行类构造器
类构造器类构造器
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
虚拟机会保证一个类的
类的加载需要完成三步:
1.加载:将Class加载到 方法区,产生一个对应的Class对象(每一个类都会产生Class对象)
2.链接:将对象放进堆中,为变量设置默认值
3.初始化:在 栈 中通过
什么时候会发生类的初始化?
- 类的主动引用(一定会发送类的初始化)
- 当虚拟机启动,先初始化main方法所在的类new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发送类的初始化)
-
当访问一个静态域时,只有真正声名这个域的类才会初始化,如:当通过子类引用父类的静态变量,不会导致子类初始化
-
通过数组定义类引用,不会触发此类的初始化
-
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
类加载器的作用
类加载器分为三种:
1.引导类加载器 :用C++编写的,JVM自带的类加载器 负责Java平台的核心库
2.扩展类加载器 :负责jrellib/ext目录下的jar包或-D va.ext.dirs指定目录下的jar包装入工作库
3.系统类加载器 :负责java-classpath或-Djava.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
测试:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
//获取扩展类加载器的父类加载器-->根加载器 (获取不到返回null)
ClassLoader parent1 = parent.getParent();
//测试当前类是哪个加载器加载的
Class<?> name = Class.forName("java.lang.Object");
System.out.println(name);
//测试JDK内置类是哪个加载器加载的
ClassLoader classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
}
}
反射API基本使用
获取类的信息
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//获取类的信息
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> aClass = Class.forName("java.lang.Object");
//获取类的名字
System.out.println(aClass.getName());//获得包名 类名
System.out.println(aClass.getSimpleName());//获得类名
//获得类的属性
Field[] fields = aClass.getFields(); //只能找到Public属性
Field[] f2 = aClass.getDeclaredFields();//找到全部的属性
for (int i = 0; i < f2.length; i++) {
System.out.println(f2[i]);
}
//获取指定属性
Field id = aClass.getField("name");//只能获取public属性
Field id1 = aClass.getDeclaredField("id");//获取私有属性
//获取类的方法
Method[] methods = aClass.getMethods();//获取本类或者父类的全部Public方法
Method[] methods1 = aClass.getDeclaredMethods();//获取本类的所有方法
//获取指定方法
Method getName = aClass.getMethod("getName", null);
Method setName = aClass.getMethod("setName", String.class);
//获取构造器
Constructor<?>[] constructors = aClass.getConstructors();//获取本类或父类全部public构造方法
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();//获取本类构造方法
//获取指定构造器
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, Integer.class, Integer.class);
}
}
通过反射创建Class对象
方式一:
调用Class对象的newInstance()
方法
//获取Class对象
Class<?> c1 = Class.forName("java.lang.Object");
//构造一个对象
User o = (User) c1.newInstance();
此方法前提类必须有一个无参构造方法且构造器的访问权限足够
方式二:
通过构造方法来创建对象
//通过构造器创建对象
Class<?> c2 = Class.forName("cn.annotation.User");
//获取构造器
Constructor<?> constructor = c2.getDeclaredConstructor(String.class, int.class, int.class);
User user = (User)constructor.newInstance("王根基", 001, 12);
通过反射操作方法
使用setName.invoke();
激活一个方法的使用
//获取Class对象
Class<?> c1 = Class.forName("cn.annotation.User");
//构造一个对象
User o = (User) c1.newInstance();
//获取指定方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke激活 参数(对象,方法的值)
setName.invoke(o,"郑在稿");
System.out.println(o.getName());
通过反射操作属性
Class<?> c1 = Class.forName("cn.annotation.User");
//通过反射操作属性
User o1 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true);//关闭权限检测
name.set(o1,"王根基与郑根基");
System.out.println(o1.getName());
测试关闭安全检测对性能的提升
//测试关闭安全检测
public class TestAccessible {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test1();
test2();
test3();
}
//普通方法
public static void test1(){
long one = System.currentTimeMillis();
User user = new User();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long two = System.currentTimeMillis();
System.out.println("普通方法:"+(two-one));
}
//反射调用
public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
long one = System.currentTimeMillis();
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long two = System.currentTimeMillis();
System.out.println("反射调用:"+(two-one));
}
//关闭安全检测
public static void test3() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
long one = System.currentTimeMillis();
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long two = System.currentTimeMillis();
System.out.println("关闭安全检测:"+(two-one));
}
}
通过反射操作泛型
- Java采用泛型擦除的机制来引入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是, 一旦编译完成,所有和泛型有关的类型全部擦除
- 为了通过反射操作这些类型, Java新增了ParameterizedType , GenericArrayType ,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型.
- ParameterizedType :示-种参数化类型,比如Collection
- GenericArrayType :表示一 种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable :是各种类型变量的公共父接口
- WildcardType :代表一 种通配符类型表达式
//通过反射操作泛型
public class Test06 {
//入参的泛型
public void test01(Map<String,User> map, List<User> list){
System.out.println("hellworld");
}
//返回值的泛型
public Map<String,User> test02(){
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
//获取Class对象
Method method = Test06.class.getDeclaredMethod("test01", Map.class, List.class);
//获取传入方法参数类型
Type[] types = method.getGenericParameterTypes();//获取方法入参的参数类型
//遍历
for (Type t: types) {
System.out.println("#"+t);//打印类型 Map List
if (t instanceof ParameterizedType){//泛型的参数类型 是否等于结构化参数类型
Type[] type = ((ParameterizedType) t).getActualTypeArguments();//如果是则强转 获取到泛型
for (Type y: type) {
System.out.println(y);//打印泛型
}
}
}
//获取Class对象
Method method2 = Test06.class.getMethod("test02",null);
Type types1 = method2.getGenericReturnType();//获取返回值参数类型
if (types1 instanceof ParameterizedType){//泛型的参数类型 是否等于结构化参数类型
Type[] type = ((ParameterizedType) types1).getActualTypeArguments();//如果是则强转 获取到泛型
for (Type y: type) {
System.out.println(y);//打印泛型
}
}
}
}
通过反射操作注解
//定义注解
//类名注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
String value();
}
//属性的注解
//类名注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldName{
String columnName();
String type();
int length();
}
//将注解设置在类名和属性上
//实体类
@Table("db_user")
class User{
@FieldName(columnName = "name",type = "varchar",length = 10)
private String name;
@FieldName(columnName = "id",type = "int",length = 10)
private int id;
@FieldName(columnName = "age",type = "int",length = 10)
private int age;
public User(){
}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setId(int id) {
this.id = id;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
//通过反射获取注解上的值
//测试反射
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> aClass = Class.forName("cn.tony.testjdbc.vo.User");
//获取类上的全部注解
Annotation[] annotations = aClass.getAnnotations();
for (Annotation a:
annotations ) {
System.out.println(a);
}
//获得注解Value值
Table table = (Table)aClass.getAnnotation(Table.class);
System.out.println("获取注解上的值:"+table.value());
//获取指定字段上注解的值
Field name = aClass.getDeclaredField("name");
FieldName annotation = name.getAnnotation(FieldName.class);
System.out.println(annotation.columnName());
System.out.println(annotation.length());
System.out.println(annotation.type());
}
}
网络编程
JavaWeb和网络编程的区别
- JavaWeb:网页编程 B/S. (Brower/Server)
- 网络编程:TCP/IP 使用 C/S 架构 (Client/Server)
IP地址在 JavaInetAddress
类中
唯一定位一台网络上的计算机
本机127.0.0.1: localhost.
小测试:
import java.net.InetAddress;
import java.net.UnknownHostException;
//测试IP
public class TestInetAddress {
public static void main(String[] args) {
//查询本机地址
try {
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
System.out.println(inetAddress);
InetAddress inetAddress1 = InetAddress.getByName("localhost");
System.out.println(inetAddress1);
InetAddress inetAddress2 = InetAddress.getLocalHost();
System.out.println(inetAddress2);
//查询网站IP地址
InetAddress inetAddress3 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress3);
//常用方法
System.out.println(inetAddress3.getAddress());
System.out.println(inetAddress3.getCanonicalHostName());//规范的名字
System.out.println(inetAddress3.getHostAddress());//ip
System.out.println(inetAddress3.getHostName());//域名,或者自己电脑的名字
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
端口分类
- 公有端口 0~1023
HTTP:80
HTTPS:443
FTP:21
Telent: 23 - 程序注册端口:1014~49151,分配给用户或者程序
Tomcat:8080
MySQL:3306
Oracle:1521
动态、私有端口:49152~65535
netstat -ano #查看所有端口
netstat -ano|findstr "端口号" #查看指定端口
tasklist|findstr "端口号" #查看指定端口的进程
TCP通信协议
客户端
- 连接服务器
Socket
- 发送消息
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* TCP客户端
*/
public class TcpClientDemo {
public static void main(String[] args) {
Socket socket =null;
OutputStream outputStream =null;
try {
InetAddress byName = InetAddress.getByName("127.0.0.1");
int port= 9999;
//创建Socket连接服务端
socket = new Socket(byName,port);
//发送消息IO流
outputStream = socket.getOutputStream();
//等待键盘输入
String next = new Scanner(System.in).next();
//向客户端输出
outputStream.write(next.getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端
- 建立服务的端口
ServerSocket
- 等待用户的连接
accept
- 接收用户的消息
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* TCP服务端
*/
public class TcpServerDemo {
public static void main(String[] args){
ServerSocket serverSocket =null;
Socket accept =null;
InputStream is =null;
ByteArrayOutputStream bos=null;
try {
//提供地址给客户端
serverSocket = new ServerSocket(9999);
while (true){ //循环监听
//等待客户端连接
accept = serverSocket.accept();
//读取客户端的消息
is = accept.getInputStream();
//管道流
bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
bos.write(buffer,0,len);
}
System.out.println(bos.toString());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(bos!=null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept!=null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
TCP实现文件上传
客户端
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* TCP 文件上传 客户端
*/
public class TcpClientDemo02 {
public static void main(String[] args) throws Exception {
//创建socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
//创建输出流
OutputStream out = socket.getOutputStream();
//读取文件
FileInputStream is = new FileInputStream(new File("demo.png"));
//写出文件
byte[] buffer = new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
out.write(buffer,0,len);
}
//通过服务器 我已经结束了
socket.shutdownOutput();
//确定服务器接收完毕,才能断开连接
InputStream in = socket.getInputStream();
//String byte[] 不能直接识别的东西需要通过管道流才能认识它
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while ((len2=in.read(buffer2))!=-1){
baos.write(buffer2,0,len2);
}
System.out.println(baos.toString());
//关闭连接
out.close();
is.close();
socket.close();;
}
}
服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* TCP文件上传服务端
* @author Tu_Yooo
* @Date 2021/2/19 21:58
*/
public class TcpServerDemo02 {
public static void main(String[] args) throws Exception {
//创建服务
ServerSocket socket = new ServerSocket(9000);
//监听客户端的连接
Socket accept = socket.accept();
//获取输入流
InputStream is = accept.getInputStream();
//文件输出
FileOutputStream fos = new FileOutputStream(new File("demo01.png"));
byte[] bytes = new byte[1024];
int len;
while ((len=is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
//通知客户端接受文件完毕
accept.getOutputStream().write("文件接收完毕,可以断开连接了".getBytes());
//关闭连接
fos.close();
is.close();
socket.close();
accept.close();
}
}
UDP通信协议
发短信:需要知道服务器地址,但不需要连接服务器
UDP没有真正的客户端和服务端的概念
只有接收端与发送端,任何一方都可以同时充当两个角色
- 通过
DatagramSocket
建立UDP连接 - 通过
DatagramPacket
准备需要发送的数据
发送端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP发送端
* 不需要连接服务器
*/
public class UdpClientDemo {
public static void main(String[] args) throws Exception {
//建立Socket
DatagramSocket socket = new DatagramSocket();
//建立服务器地址
InetAddress byName = InetAddress.getByName("127.0.0.1");
int port =9090;
//需要发送的数据
String msg = "helloword";
//建立个包 参数: 数据本身 数据从0开始 数据的结束 服务器地址 端口号
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, byName, port);
//发送数据
socket.send(packet);
//关闭连接
socket.close();
}
}
接收端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDP接收端
* 接收数据
* 等待接收端的连接
*/
public class UdpServerDemo {
public static void main(String[] args) throws Exception {
//开发端口
DatagramSocket socket = new DatagramSocket(9090);
//接收数据包
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
//阻塞等待数据
socket.receive(packet);
//打印数据
System.out.println(packet.getAddress().getHostAddress());//ip地址
System.out.println(new String(packet.getData(),0,packet.getLength()));
//关闭连接
socket.close();
}
}
循环发送消息
发送端
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
/**
* UDP模拟聊天
* 发送
*/
public class UdpSenderDemo {
public static void main(String[] args) throws Exception {
//创建连接 开放端口
DatagramSocket socket = new DatagramSocket(8080);
while (true){
//准备数据 从控制台读取
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String data = reader.readLine();//获取控制台输入
byte[] bytes = data.getBytes();//转换成字节
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress("localhost", 9000));
//发送数据
socket.send(packet);
if (data.equals("stop"))
break;
}
//关闭连接
socket.close();
}
}
接收端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDP模拟聊天
* 接收
*/
public class UdpReceiveDemo {
public static void main(String[] args) throws Exception {
//开放端口
DatagramSocket socket = new DatagramSocket(9000);
while (true){ //循环接收消息
//准备接受数据
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
//阻塞等待数据
socket.receive(packet);
//将数据byte[]数组转换成String类型
String data = new String(packet.getData(), 0, packet.getData().length);
System.out.println(data);
if (data.equals("stop"))
break;
}
socket.close();
}
}
通过URL下载网络资源
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 通过URL下载网络上的资源
*/
public class TestUrl {
public static void main(String[] args) throws Exception {
//创建URL连接
URL url = new URL("https://img2020.cnblogs.com/blog/2465789/202107/2465789-20210715231213689-1596184502.png");
//连接这个地址的资源 HTTP
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
//获取HTTP 输入流
InputStream input = urlConnection.getInputStream();
//创建文件输出流
FileOutputStream outp = new FileOutputStream("24f.m4a");
//读取文件
byte[] bytes = new byte[1024];
int len;
while ((len=input.read(bytes))!= -1){
outp.write(bytes,0,len);
}
//关闭连接
outp.close();
input.close();
urlConnection.disconnect();//断开连接
}
}
JUC并发编程(1)
并发编程的本质:充分利用CPU资源
线程六大状态
阅读源码可知,线程有六大状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//死亡
TERMINATED;
}
wait/sleep区别
1.来自不同的类
wait来着Object
sleep来着线程类
//在实际工作中,不会使用sleep让线程睡眠
//使用TimeUnit让线程睡眠
TimeUnit.DAYS.sleep(1);//睡一天
TimeUnit.SECONDS.sleep(2);//睡两秒
2.关于锁的释放
wait会释放锁
sleep不会释放锁
3.使用范围不同
wait必须在同步代码块中使用
sleep任何地方都可以睡眠
4.是否需要捕获异常
wait不需要捕获异常
sleep必须捕获异常
锁
synchronized
synchronized是解决线程不安全的关键,它的实现原理就是 队列和锁
由于我们可以通过private关键词来保证数据变量(对象),只能被方法访问,所以我们只需要针对方法提出一套机制.这套机制就是synchronized关键字.
实现原理
synchronized方法控制对对象的访问,每个对象对应一把锁,每一个synchronized方法都必须获得调用该方法对象的锁才能执行,否则会线程阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续往下执行
缺陷:如果一个大的方法被申明synchronized将会影响效率
具体用法
它的用法有两种:
它的用法有两种:
1.synchronized同步方法
//同步方法
public synchronized void method(int ages){}
- synchronized方法 锁的是对象本身this
- 谁获得这个对象的锁才能执行这个方法
2.synchronized同步块
synchronized(obj){}
同步块锁的是obj
Ojb称为同步监视器:
- 他可以是任何对象,但是推荐使用共享资源对象
- 同步方法种无需指定同步监视器,因为同步方法的同步监视器就是
this
就是这个对象的本身
Lock锁
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition
。
Lock默认有三个实现类
ReentrantLock()源码:
公平锁:先来后到,不可插队
非公平锁: 可插队
例子:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
public static void main(String[] args) {
Tikect tikect = new Tikect();
new Thread(() -> {
for (int i = 0; i < 20; i++) tikect.numTest();
}).start();
new Thread(() -> {
for (int i = 0; i < 20; i++) tikect.numTest();
}).start();
new Thread(() -> {
for (int i = 0; i < 20; i++) tikect.numTest();
}).start();
}
}
class Tikect {
private Integer num = 20;
//通过显示声明锁 进行手动加锁 和解锁
private Lock lock = new ReentrantLock();
public void numTest() {
lock.lock();//加锁
try {
if (num > 0) {
num--;
System.out.println(num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
synchronized与Lock区别
- Synchronized 内置的Java关键字,Lock是一个Java类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock必须要手动释放锁!如果不释放锁,死锁
- Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等) ; Lock锁就不一定会等待下去;
- Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以判断锁,非公平(可以自己设置);
- Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
ReadWriteLock读写锁
ReadWriteLock只有唯一的一个实现类ReentrantReadWriteLock
//自定义缓存
class myCache{
private Map<String,Object> map = new HashMap<>();
//读写锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
//存的时候只有一个线程 存 取的时候可以多个线程取
//存
public void put(String key,Object value){
lock.writeLock();//写锁
try {
//业务代码
map.put(key,value);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();//释放锁
}
}
//取
public Object get(String key){
lock.readLock();//读锁
System.out.println(3);
try {
//业务代码
return map.get(key);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
return null;
}
}
生产者与消费者
synchronized版
public class ThreadTest {
public static void main(String[] args) {
Tikect tikect = new Tikect();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
tikect.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A");
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
tikect.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B");
}
}
class Tikect{
private int num =0;
public synchronized void increment() throws InterruptedException {
if (num!=0) {
this.wait();//睡眠
}
System.out.println(num++);
this.notifyAll();//唤醒睡眠的线程
}
public synchronized void decrement() throws InterruptedException {
if (num==0) {
this.wait();
}
System.out.println(num--);
this.notifyAll();
}
}
//修改后
class Tikect{
private int num =0;
public synchronized void increment() throws InterruptedException {
//防止虚假唤醒 使用while判断
while (num!=0) {
this.wait();//睡眠
}
System.out.println(num++);
this.notifyAll();//唤醒睡眠的线程
}
public synchronized void decrement() throws InterruptedException {
while (num==0) {
this.wait();
}
System.out.println(num--);
this.notifyAll();
}
}
如果只是两个线程之间进行通信是没有问题的,如果多个线程之间四个五个六个之间并发执行就会产生虚假唤醒
因为if()
只会判断一次
我们应该根据官方文档,改成while()
循环判断,来防止虚假唤醒
Lock版
通过Condition替代了Object中睡眠唤醒方法
通过lock找到Condition
class Tikect{
private int num =0;
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try{
//防止虚假唤醒 使用while判断
while (num!=0) {
condition.await();//睡眠
}
num++;
condition.signalAll();//唤醒睡眠的线程
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try{
while (num==0) {
this.wait();
}
num--;
this.notifyAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
Condition实现精准通知唤醒,让线程顺序执行
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
// A 执行完 唤醒B B执行完 唤醒C 顺序唤醒
public static void main(String[] args) {
TestCondition condition = new TestCondition();
new Thread(() -> {
condition.prinltTest1();
}, "A").start();
new Thread(() -> {
condition.prinltTest2();
}, "B").start();
new Thread(() -> {
condition.prinltTest3();
}, "C").start();
}
}
class TestCondition {
private Lock lock = new ReentrantLock();
//准备三个监视器
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1;
//使用同一把锁进行加锁
public void prinltTest1() {
lock.lock();
try {
// 判断是否执行-> 执行具体业务 -> 唤醒其他线程
while (number != 1) {
condition1.await();//睡眠
}
//业务
number++;
System.out.println("A执行了");
//唤醒B
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void prinltTest2() {
lock.lock();
try {
// 判断是否执行-> 执行具体业务 -> 唤醒其他线程
while (number != 2) {
condition2.await();//睡眠
}
//业务
number++;
System.out.println("B执行了");
//唤醒C
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void prinltTest3() {
lock.lock();
try {
// 判断是否执行-> 执行具体业务 -> 唤醒其他线程
while (number != 1) {
condition3.await();//睡眠
}
//业务
number = 1;
System.out.println("C执行了");
//唤醒A
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
实现精准唤醒-> 需要准备多个监视器,通过condition1.signal();
唤醒指定的监视器
不安全的集合类
在多线程下操作集合容易出现
java.util.concurrentModificationException并发修改异常!
详情可以访问我的博客文章 源码分析 : https://www.cnblogs.com/zwtblog/tag/源码/
从上到下都把源码说清楚了,看完自然知道怎么安全的了,不然看完本文的“总结”,也还是糊涂的。
List
解决方案一:Vector
使用线程安全的类
//解决方案:
List<String> list = new Vector<>();
Vector在底层源码中使用synchronized
来进行修饰的.,所以它是线程安全的
解决方案二:Collections工具类
使用工具类转化线程不安全的类
//Collections转化线程安全的类
List<String> list = Collections.synchronizedList(new ArrayList<>());
解决方案三:CopyOnWriteArrayList
CopyOnWriteArrayList是JUC下线程安全的类
List<Object> objects = new CopyOnWriteArrayList<>();
CopyOnWrite的意思是:
写入时复制List 简称COW,它是计算机程序设计领域的一种优化策略
多个线程调用时,List读取的时候,固定的,产生写入(覆盖)
copyOnWrite 的解决方案是 在写入时进行复制 避免覆盖 造成的数据问题
好在哪儿?
我们查看底层源码可知:
在CopyOnWriteArrayList中使用的是高性能lock锁
Set
hashSet的底层是什么?
查看底层源码会发现hashSet本质上是一个hashMap , add方法的本质就是往map中put值
解决方案一:Collections工具类
与list相似,我们可以使用工具类的方式来转换线程不安全的类
Set<Object> set = Collections.synchronizedSet(new HashSet<>());
解决方案二:CopyOnWriteArraySet
Set<Object> set1 = new CopyOnWriteArraySet<>();
Map
解决方案一:Hashtable
Map<Object, Object> objectHashtable = new Hashtable<>();
HashTable是线程安全的:
HashTable和HashMap的实现原理几乎一样
差别:HashTable不允许key和value为null;
HashTable是线程安全的:
HashTable和HashMap的实现原理几乎一样,
差别:1.HashTable不允许key和value为null;
解决方案二:CopyOnWriteArraySet
Set