Java-基础

目录

JavaSE的一些基础内容,

关于更基础的如:环境安装,语法,面向对象,数组,异常处理等就没有做多概述

IO

四大基流 :

输入流和输出流 , 字节流和字符流

根据功能划分:

  • 节点流:可以从或向一个特定的地方(节点)读写数据,直接连接数据源。如最常见的是文件的FileReader,还可以是数组、管道、字符串,关键字分别为ByteArray/CharArray,Piped,String。
  • 处理流(包装流):并不直接连接数据源,是对一个已存在的流的连接和封装,是一种典型的装饰器设计模式,使用处理流主要是为了更方便的执行输入输出工作,如PrintStream,输出功能很强大,又如BufferedReader提供缓存机制,推荐输出时都使用处理流包装。

一些特别的流类型:

  • 转换流:转换流只有字节流转换为字符流,因为字符流使用起来更方便,我们只会向更方便使用的方向转化。如:InputStreamReader与OutputStreamWriter。
  • 缓冲流:有关键字Buffered,也是一种处理流,为其包装的流增加了缓存功能,提高了输入输出的效率,增加缓冲功能后需要使用flush()才能将缓冲区中内容写入到实际的物理节点。但是,在现在版本的Java中,只需记得关闭输出流(调用close()方法),就会自动执行输出流的flush()方法,可以保证将缓冲区中内容写入。
  • 对象流:有关键字Object,主要用于将目标对象保存到磁盘中或允许在网络中直接传输对象时使用(对象序列化)。

操作IO流的模板

  1. 创建源或目标对象
  2. 创建IO流对象
    • 进行包装
  3. 具体的IO操作
  4. 关闭资源

注意:程序中打开的文件 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

  1. 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  2. 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
  3. 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。

注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

核心概念

  1. 线程就是独立的执行路径;
  2. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  3. main()称之为主线程,为系统的入口,用于执行整个程序;
  4. 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
  5. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  6. 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程创建

继承Thread类

线程不一定立即执行,由CPU安排调度执行

实现Runnable

实现Callable接口

1.实现Callable接口, 需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行: Future result1 = ser.submit(t1);
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);
        }
    }
}

线程休眠

  1. sleep(时间)指定当前线程阻塞的毫秒数
  2. sleep存在异常InterruptedExcetion
  3. sleep时间达到后,线程进入就绪状态
  4. sleep可以模拟网络延迟,倒计时等
  5. 每个对象都有一个锁,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对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用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;
    }
}

信号灯法

通过定义一个共有的变量,判断它是否为truefalse,来决定线程是否执行或者是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和网络编程的区别

  1. JavaWeb:网页编程 B/S. (Brower/Server)
  2. 网络编程: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();
        }

    }
}



端口分类

  1. 公有端口 0~1023
    HTTP:80
    HTTPS:443
    FTP:21
    Telent: 23
  2. 程序注册端口:1014~49151,分配给用户或者程序
    Tomcat:8080
    MySQL:3306
    Oracle:1521
    动态、私有端口:49152~65535
netstat -ano #查看所有端口
netstat -ano|findstr "端口号" #查看指定端口
tasklist|findstr "端口号" #查看指定端口的进程

TCP通信协议

客户端

  1. 连接服务器Socket
  2. 发送消息
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();
                }
            }
        }

    }


服务端

  1. 建立服务的端口ServerSocket
  2. 等待用户的连接accept
  3. 接收用户的消息
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没有真正的客户端和服务端的概念

只有接收端与发送端,任何一方都可以同时充当两个角色

  1. 通过DatagramSocket建立UDP连接
  2. 通过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){}

  1. synchronized方法 锁的是对象本身this
  2. 谁获得这个对象的锁才能执行这个方法

2.synchronized同步块

synchronized(obj){}

同步块锁的是obj
Ojb称为同步监视器:

  1. 他可以是任何对象,但是推荐使用共享资源对象
  2. 同步方法种无需指定同步监视器,因为同步方法的同步监视器就是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 set1 = new CopyOnWriteArraySet<>();
1
Map
解决方案一:Hashtable
Map<Object, Object> objectHashtable = new Hashtable<>();
1
HashTable是线程安全的:
HashTable和HashMap的实现原理几乎一样,
差别:1.HashTable不允许key和value为null;

HashTable线程安全的策略实现代价却比较大,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞

解决方案二:Collections工具类

Collections.synchronizedMap(new HashMap<>());

解决方案三:ConcurrentHashMap

Map<Object, Object> map = new ConcurrentHashMap<>();

JDK1.7版本: 容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这 样便可以有效地提高并发效率。这就是ConcurrentHashMap所采用的"分段锁"思想

从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,

总结如下:

1、JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8实现降低锁的粒度就是HashEntry(首节点)

2、JDK1.8版本的数据结构变得更加简单,去掉了Segment这种数据结构,使用synchronized来进行同步锁粒度降低,所以不需要分段锁的概念,实现的复杂度也增加了

3、JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档

4、JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock:

低粒度加锁方式,synchronized并不比ReentrantLock差,

粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了

JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然

在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存

Callable

Callable区别:

1.有返回值

2.可以抛出异常

3.方法不同 run()/call()

例子:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCall call = new MyCall();
        //寻常 多线程是如下方式开启 ,但是在Thread中不能直接传入Callable
       /* new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();*/

        //那么如何执行Callable呢?
        //我们知道在Runnable的实现类FutureTask 构造方法中可以传入Callable 于是可以如下使用
        FutureTask<String> futureTask = new FutureTask<>(call);
        new Thread(futureTask).start();
        //如何获取结果呢?
        String s = futureTask.get();//使用get参数 获取获取结果

        //如果多条线程执行呢?
        new Thread(futureTask).start();
        new Thread(futureTask).start();
        //会发现方法打印只执行了一次
        //原因是Callable有缓存
    }
}

class MyCall implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("执行方法");
        //调用结果时可能会产生阻塞,因为方法内部逻辑的执行是一个耗时操作
        return "ok";
    }
}

常用辅助类

CountDownLatch减法计数器

测试:

public class Test1 {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count = new CountDownLatch(6);

        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(">>>"+Thread.currentThread().getName());
                count.countDown();//数量-1
            },String.valueOf(i)).start();
        }
        count.await();//等待计数器归零后才往下执行
        System.out.println("这段代码会等待所有线程执行完毕以后,再执行");
    }
}

CyclicBarrier加法计数器

测试:

public class Test2 {
    public static void main(String[] args) {
        //计数器加到7时 执行线程
        CyclicBarrier barrier = new CyclicBarrier(7,() -> {
            System.out.println("计数完执行的线程");
        });

        //创建7个线程
        for (int i = 0; i < 7; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName());

                try {
                    barrier.await();//线程等待 计数器+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}


注意点:如果无法计数到指定数量,那么线程会卡死,一直等待下去

Semaphore信号量

测试:

public class Test3 {
    public static void main(String[] args) {
        //Semaphore 主要应用于限流
        Semaphore semaphore = new Semaphore(3);
        
        //Semaphore的位置只有3个 现在有6个线程 先拿到通行证的线程先执行 其他线程必须等待释放以后执行
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();//获取通行证
                    System.out.println(Thread.currentThread().getName());
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("释放通行证");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

阻塞队列

BlockingQueue

BlockingQueue不是一个新东西,它是跟List,Set同级的集合框架


    //抛出异常
    public static void a(){
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        b.add("a");
        b.add("b");
        b.add("c");
        //队列已满 如果继续放会报错 java.lang.IllegalStateException: Queue full 队列已满异常
        //b.add("d");
        b.element();//查看队首的对象是谁
        b.remove();//弹出
        b.remove();
        b.remove();

        //队列已空 如果继续取出 会报错 java.util.NoSuchElementException
       // b.remove();
    }


public static void b(){
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        b.offer("a");
        b.offer("a");
        b.offer("a");
        //队列已满 继续放入 返回值为false 没有异常
        b.offer("a");
       b.peek();//查看队首
        b.poll();
        b.poll();
        b.poll();
        //队列已空 如果继续取出 为null  没有异常
        b.poll();

    }


//等待 阻塞(一直阻塞)
    public void c() throws InterruptedException {
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        b.put("a");
        b.put("a");
        b.put("a");
        //b.put("a"); 队列已满 一直阻塞

        b.take();
        b.take();
        b.take();
        // b.take(); 队列已空 一直阻塞
    }


  //等待 超时等待
    public void d() throws InterruptedException {
        //构造方法 需要传入队列大小
        ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);

        //往队列放入
        b.offer("a");
        b.offer("a");
        b.offer("a");
        b.offer("a",2, TimeUnit.SECONDS);//超时等待两秒 两秒之后退出

        //取出
        b.poll(2,TimeUnit.SECONDS);
        b.poll(2,TimeUnit.SECONDS);
        b.poll(2,TimeUnit.SECONDS);
        b.poll(2,TimeUnit.SECONDS);//超时等待两秒 取不到值就退出
    }

SynchronousQueue

没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素!

public static void main(String[] args) {
        //同步队列
        SynchronousQueue<String> queue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                queue.put("");
                System.out.println("存");
                queue.put("");
                System.out.println("存");
                queue.put("");
                System.out.println("存");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                queue.take();
                System.out.println("取");
                queue.take();
                System.out.println("取");
                queue.take();
                System.out.println("取");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

JUC并发编程(2)

线程池有哪些好处?

1.降低资源的消耗
2.提供响应速度
3.统一的管理(方便管理)

总结:控制最大并发数,可以控制最大并发数,管理线程

创建线程池

//Executors 工具类 3大方法
public class test {
    public static void main(String[] args) {
       // Executors.newSingleThreadExecutor();//单个线程

        ExecutorService executor = Executors.newFixedThreadPool(5);//创建一个固定的线程

  //      Executors.newCachedThreadPool();//可伸缩的 遇强则强


        //使用线程池 不再用原来的方式创建线程 而是用executor.execute()
        try {
            for (int i = 0; i < 10; i++) {
                executor.execute(() -> {
                    System.out.println("执行");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            executor.shutdown();//使用完一定要关闭线程池
        }

    }
}

源码分析-七大参数


//单个线程
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

//创建一个固定的线程
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

//可伸缩的 遇强则强
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

查看源码可以发现,创建线程都是调用了一个ThreadPoolExecutor的方法

这是个什么东西,我们点进行继续查看

public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                              int maximumPoolSize,//最大线程池大小
                              long keepAliveTime,//存活时间,超时了没人调用就会释放
                              TimeUnit unit,//存活的单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工程 创建线程 一般不用动
                              //拒绝策略
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

此时我们回过头再去看

//单个线程
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
        //最大线程数 和 核心线程数都是1  所以它是单个线程
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));


//可伸缩的 遇强则强
    public static ExecutorService newCachedThreadPool() {
    //核心线程数0  最大是21亿  如果出现OOM 内存溢出
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

在阿里云开发手册中有这样一句话:

自定义线程池

/**
 * 自定义线程池
 */
public class ThreadPool {

    public ThreadPoolExecutor getThreadPoolExecutor(){

        //核心线程数
        int corePoolSize = 3;

        //最大线程数
        int maximumPoolSize = 6;

        //超过核心线程数的最大空闲时间
        long keepAliveTime = 2;

        //以秒为时间单位
        TimeUnit time = TimeUnit.SECONDS;
        
        //创建线程池
        return new ThreadPoolExecutor(corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                time,
                //创建阻塞队列 超过最大线程数后 启用队列 存放等待执行任务
                new ArrayBlockingQueue<>(2),
                //线程工厂 一般不用改变
                Executors.defaultThreadFactory(),
                //拒绝策略 当最大线程数满了 并且阻塞队列也满了时 启用拒绝策略
                new ThreadPoolExecutor.AbortPolicy());;
    }

线程池的最大承载数:阻塞队列数+最大线程数

四种拒绝策略

超过最大承载数 启动拒绝策略:
1.AbortPolicy(): 不处理新的任务,直接抛出异常
2.CallerRunsPolicy():哪来的回哪里去,如果是main线程传递过来的,让它回main线程处理去
3.DiscardPolicy():队列满了,不会抛出异常,丢掉任务
4.DiscardOldestPolicy():不会抛出异常,尝试跟最早的竞争,竞争失败也会丢掉任务

最大线程数到底应该如何定义?
有两种解决方案:

CPU密集型:有几核CPU就定义几,可以保证CPU效率最高

       //获取CPU核心数 确保再不同的电脑上运行
        Runtime.getRuntime().availableProcessors();

IO密集型:判断程序中,非常耗费IO的线程数 大于这个IO数

四大函数式接口

Function函数型接口

函数式接口用于简化编程模型

传入参数T 返回参数R

       //工具类
        Function function =new Function<String,String>() {
            @Override
            public String apply(String o) {
                return o;
            }
        };

        //Lambda表达式
        Function functio = (str) -> {return str;};

Predicate断定型接口

只有一个输入参数 返回值只能是布尔值

 Predicate predicate = (s) -> {return true;};

Consumer消费性接口

只有输入参数 没有返回值

 //有输入参数 无返回值
 Consumer consumer = (s) -> {
            System.out.println(s);
        };

Supplier供给型接口

      //无输入参数 有返回值
        Supplier supplier = () -> {
          return 1;  
        };

ForkJoin

分支合并,并行处理任务,将比较大的任务拆成小任务

适用场景:适合处理大型数据

ForkJoin特点:工作窃取

此时有两个线程,A线程执行了一半,B线程已经执行完了,此时B线程会去窃取A线程的任务,来帮助它完成,这就叫工作窃取

这里面维护的是双端队列

ForkJoin使用

  • ForkJoin 使用步骤
  • 1.集成RecursiveTask 递归任务 重写compute方法
  • 2.创建ForkJoinPool类 submit() 提交任务–有返回值 /execute()执行任务–无返回值
public class ForkJoinDemo extends RecursiveTask<Long> {

    private Long start;
    private Long end;

    //临界值
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    //计算方法
    @Override
    protected Long compute() {
        long middle = (start+end)/2;//中间值
        ForkJoinDemo fork1 = new ForkJoinDemo(start, middle);
        fork1.fork();//拆分任务
        ForkJoinDemo fork2 = new ForkJoinDemo(middle, end);
        fork2.fork();
        return fork1.join()+fork2.join();//结果
    }
}

class TestFork{
    public static void main(String[] args) {
        test2();
    }

    //ForkJoin 计算long
    public static void test1() throws ExecutionException, InterruptedException {
        ForkJoinPool joinPool = new ForkJoinPool();
        ForkJoinTask<Long> submit = joinPool.submit(new ForkJoinDemo(0L, 10_0000_0000L));//提交任务
        submit.get();//获取结果
    }

    //使用stream流式计算
    public static void test2(){
        long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0, Long::sum);
    }
}

异步回调

CompletableFuture类似与ajax,用于异步执行任务

public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("没有返回值的异步回调");
        });

        //有返回值的supplyAsync异步回调
        //与ajax一样 成功和失败都可以获取响应信息
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("有返回值的异步回调");
            return 1024;
        });

        future2.whenComplete((t,u) -> {
            //成功时 的回调方法
            System.out.println(t);//1024 t正常的返回结果
            System.out.println(u); // 错误的信息
        }).exceptionally(e -> {
            //失败时的回调
            e.printStackTrace();
            return 244;
        });

        future2.get();//阻塞等待执行结果

    }
}

Volatile

Volatile是Java虚拟机提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

JMM:Java内存模型,不存在的东西,概念!约定

JMM的八种交互操作(每个操作都为原子操作)

1.lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

2.unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

3.read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用

4.load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

5.use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令

6.assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中

7.store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用

8.write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

对八种操作的规则

1.不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write

2.不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存

3.不允许一个线程将没有assign的数据从工作内存同步回主内存

4.一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作

5.一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

6.如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

7.如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量

8.对一个变量进行unlock操作之前,必须把此变量同步回主内存

保证可见性

//多个线程使用同一个变量时 需要加入volatile保证可见性
//main线程修改了变量 要及时通知其他线程 否则会造成死循环
private volatile static int sum =0;

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

    new Thread(() -> {
        while (sum==0){ //sum等于0时 程序死循环

        }
    }).start();

    Thread.sleep(2);//确保线程启动

    sum=1;//主线程修改sum的值

    System.out.println(sum);
}

不保证原子性

 //volatile不保证原子性
    private volatile static int sum =0;

    public static void add(){
        sum++;
    }
    
    public static void main(String[] args) {

        //理论上结果为两万 实际上volatile并不保证原子性 结果肯定不为两万
        for (int i = 1; i <= 20; i++) {
            new Thread(()-> {
                add();
            }).start();
        }
        
        while (Thread.activeCount() > 2){//线程存活数
            //main gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+""+sum);
    }

在不使用synchronized和lock的情况下,如何保证原子性

首先我们要了解到sum++不是一个原子性操作

所以要解决这个问题使用原子类

    //AtomicInteger int类型的原子类
    private volatile static AtomicInteger sum =new AtomicInteger();

    public synchronized static void add(){
        sum.getAndIncrement();// +1 操作  底层使用CAS 非常高效
    }

    public static void main(String[] args) {

        //理论上结果为两万 实际上volatile并不保证原子性 结果肯定不为两万
        for (int i = 1; i <= 20; i++) {
            new Thread(()-> {
                add();
            }).start();
        }

        while (Thread.activeCount() > 2){//线程存活数
            //main gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+""+sum);
    }

这些类的底层都直接和操作系统挂钩,直接操作内存 unsafe

禁止指令重排

Volatile如何避免指令重排

内存屏障,作用:

1.保证特定的操作执行顺序

2.可以保证某些变量的内存可见性

单例模式

饿汉式

//一上来就创建对象
public class Demo02 {
    
    //饿汉式 构造器私有 避免别人创建对象
    private Demo02(){
        
    }
    // 饿汉式可能会造成浪费空间
    private final static Demo02 DEMO = new Demo02();
    
    public static Demo02 getInstance(){
        return DEMO;
    }
    
}

懒汉式

public class Demo02 {

    //构造器私有 避免别人创建对象
    private Demo02(){

    }
    // 懒汉式可能会造成浪费空间 volatile确保不会被指令重排
    private volatile  static Demo02 DEMO;

    public static Demo02 getInstance(){
        if (DEMO == null){
            //多线程获取 需要使用synchronized 双重检测锁模式
            synchronized (Demo02.class){
                if (DEMO == null){
                    DEMO = new Demo02();
                }
            }
        }
        return DEMO;
    }

}


DCL懒汉式双重检测机制,在极端情况下还是会出现问题:

这几行代码 在正常情况下的执行流程应该是:

1.分配内存空间

2.执行构造方法,初始化对象

3.把这个对象指向这个空间

如果出现了指令重排123 变成了132 就会出现问题

所以我们应该加上volatile确保不会发生指令重排

使用枚举确保单例模式不被反射破坏

使用反射创建对象

public class Demo02 {

    //构造器私有 避免别人创建对象
    private Demo02(){

    }
    // 懒汉式可能会造成浪费空间 volatile确保不会被指令重排
    private volatile  static Demo02 DEMO;

    public static Demo02 getInstance(){
        if (DEMO == null){
            //多线程获取 需要使用synchronized 双重检测锁模式
            synchronized (Demo02.class){
                if (DEMO == null){
                    DEMO = new Demo02();
                }
            }
        }
        return DEMO;
    }

 //使用反射创建对象 破坏单例模式
    public static void main(String[] args) throws Exception {
        Constructor<Demo02> declaredConstructor = Demo02.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//关闭安全检测
        Demo02 demo02 = declaredConstructor.newInstance();//通过反射创建对象
    }

}

使用反射就可以绕过私有变量创建新的对象

我们查看反射的源码可以发现

反射机制不能操作枚举类

么是枚举类?

枚举是JDK1.5的时候出现的,本质上是一个class,它默认就是单例模式

public enum  EnumSingTest {
    
    INSTANCE;
    
    public EnumSingTest getInstance(){
        return INSTANCE;
    }
}

CAS

synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。

CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

compareAndSet :比较并交换

如果期望的值达到了,那么就会更新,否则就会不更新

public static void main(String[] args) {
        //CAS 如果期望的值达到了 就更新
        //CAS 是CPU的并发原语
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        atomicInteger.compareAndSet(2020,2021);
    }

查看原子类AtomicInteger的加1操作,再次理解CAS

CAS:比较当前工作内存中的值,和主内存中的值,如果这个值是期望的,则进行交换,否则会一直循环(自旋锁)

缺点:

1.循环会耗时

2.一次性只能保证一个共享变量的原子性

3.ABA问题

CAS:什么是ABA问题?(狸猫换太子)

比如是有两个线程A,B ,一个变量:苹果

A线程期望拿到一个苹果

B线程一进来把苹果改成了梨子,但是在最后结束的时候又把梨子换成了苹果

A线程在此期间是不知情的,以为自己拿到的苹果还是原来的那一个,其实已经被换过了

如何解决ABA问题?

原子引用

原子引用-解决ABA问题

AtomicStampedReference

类似于乐观锁,比较时会去对比版本号,确认变量是否被换过了

 public static void main(String[] args) {
       //AtomicStampedReference 构造方法需要传入变量 和 版本号
        AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 1);

        new Thread(() -> {
            int stamp = reference.getStamp();//获取版本号
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //期望2020 换成2021 期望的版本号是reference.getStamp() 结束后版本号+1
            reference.compareAndSet(1,2,reference.getStamp(),reference.getStamp()+1);

            reference.compareAndSet(2,1,reference.getStamp(),reference.getStamp()+1);
        }).start();

        new Thread(() -> {
            int stamp = reference.getStamp();//获取版本号
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //如果版本号不是原来的那一个 那么修改会不成功
            reference.compareAndSet(1,6,stamp,stamp+1);
        }).start();
    }

各种锁的理解

可重入锁

又叫递归锁

class Phone{
    
    //可重入锁 拿到外面的锁 也就获得了里面的锁
    public synchronized void sms(){
        System.out.println("1111");
        call();
    }
    
    public synchronized void call(){
        System.out.println(2222);
    }
}

class Phone{
    
    Lock lock =new ReentrantLock();
    
    public  void sms(){
        lock.lock();
        lock.lock();//lock锁细节 一把锁 对应一把钥匙 如果多把锁 只解了一次 那么一定会产生死锁
        try {
            System.out.println("1111");
            call();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
            lock.unlock();
        }
       
    }

    public  void call(){
        lock.lock();
        try {
            System.out.println(2222);
        }catch (Exception e){
        e.printStackTrace(); 
        }finally { 
            lock.unlock(); 
        }
    }
}

自旋锁

自旋锁就是不断尝试,直到成功为止

死锁

多个线程同时争夺对方的资源

参考链接

https://juejin.cn/post/6844904030263574535#heading-5

https://blog.csdn.net/weixin_46684099/article/details/114088718

posted @ 2021-08-11 18:58  Ricardo_ML  阅读(333)  评论(1编辑  收藏  举报