java查漏补缺(2)

多线程

进程&线程

启动一个程序,是一个进程
一个进程内并行执行的是多个线程

创建多线程三种方式

这里以把同一个数循环50次加1为例。结果和预想有出入,请看线程同步部分。
想操作同一个数请把该数设为static!!!

extends Thread

设计一个类,继承Thread,并且重写run方法
启动线程办法:调用其start方法

public class AddThread1 extends Thread {
    private static Integer num = 0;

    public AddThread1(Integer num) {
        this.num = num;
    }

    public void add() throws InterruptedException {
        Thread.sleep(50);
        num = num + 1;
        System.out.println(currentThread().getName() + ":" + num);
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行线程:

		AddThread1 addThread1 = new AddThread1(num);
        AddThread1 addThread11 = new AddThread1(num);
        addThread1.start();
        addThread11.start();

implements Runnable

创建类,实现Runnable接口
启动的时候,首先创建一个对象,然后再根据该对象创建一个线程对象,并启动

public class AddThread2 implements Runnable {
    private static Integer num;
    public AddThread2(Integer num) {
        this.num = num;
    }
    public void add() throws InterruptedException {
        Thread.sleep(50);
        num = num + 1;
        System.out.println(Thread.currentThread().getName()+ ":" + num);
    }
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行线程:

		AddThread2 addThread2 = new AddThread2(num);
        new Thread(addThread2).start();
        new Thread(addThread2).start();

匿名类

使用匿名类,继承Thread,重写run方法,直接在run方法中写业务代码
匿名类的一个好处是可以很方便的访问外部的局部变量。
运行线程:

public class ThreadTest {
    private static Integer num = 0;
    public static void main(String[] args) {
        Thread t1=new Thread(){
            @Override
            public void run() {
                add50();
            }
        };
        t1.start();
        Thread t2=new Thread(){
            @Override
            public void run() {
                add50();
            }
        };
        t2.start();
    }
    public static void add50(){
        for (int i=0;i<50;i++){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num = num + 1;
            System.out.println(Thread.currentThread().getName()+ ":" + num);
        }
    }
}

常见线程方法

在这里插入图片描述
sleep:表示当前线程暂停 ,其他线程不受影响

//休眠1000ms
Thread.sleep(1000);

join:即表明在主线程中加入该线程,主线程会等待该线程结束完毕, 才会往下运行。

public class TestThread {
    public static void main(String[] args) {
        Thread t1= new Thread(){
            public void run(){
                 //todo     
            }
        };
        t1.start();
        //代码执行到这里,一直是main线程在运行
        try {
            //t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
            t1.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }     
    } 
}

setPriority:优先级,当线程处于竞争关系的时候,优先级高的线程会有更大的几率获得CPU资源

		Thread t2= new Thread(){
            public void run(){
              //todo            
            }
        };
        t2.setPriority(Thread.MAX_PRIORITY);

yield:当前线程,临时暂停,使得其他线程可以有更多的机会占用CPU资源

		Thread t2= new Thread(){
            public void run(){
                //临时暂停,使得t1可以占用CPU资源
                Thread.yield();
         		//todo
            }
        };

setDaemon:当一个进程里,所有的线程都是守护线程的时候,结束当前进程。

		Thread t1= new Thread(){
            public void run(){
              	//todo             
            }
        };
        t1.setDaemon(true);
        t1.start();

线程安全的解决方案

上面三种创建线程的方式都是把同一个数0用两个线程循环50次加1,但最后结果都达不到100。
是因为其中一个线程加1的时候,另一个线程不是用的第一个线程加1以后的数据再加1,而是当前的数据,这个数据很可能还没加1。
解决办法有:使用synchronized、使用线程安全的类

synchronized

所有需要修改数据的地方,有要建立在占有someObject的基础上。
而对象 someObject在同一时间,只能被一个线程占有。 间接地,导致同一时间,数据只能被一个线程修改。
这个 someObject就是个锁,要多个线程共用一个锁线程同步才会生效。

  1. extends Thread 形式的线程同步:
    在这里插入图片描述
  2. 实现Runnable接口的由于是同一个AddThread2类的对象生成了两个线程,所以可以用本对象当锁。
    synchronized (this) {
    }
    表示当前对象为同步对象,即是addThread2为同步对象。
    在这里插入图片描述
    在这里插入图片描述
  3. 匿名内部类用个obj就行
    在这里插入图片描述

注:为什么不用static的Integer对象num做同步的对象呢?

java中这种包装类的自动拆箱装箱特性导致的,在拆装箱的过程中实际上是new了一个新对象代替了原num对象,导致锁变了,如果没有装箱拆箱过程就可以用。
可使用线程安全的AtomicInteger。

线程安全的类

在集合中,线程安全的有:HashTable,Vector
在包装类中,线程安全的有:StringBuffer,AtomicInteger等
非线程安全的类转换为线程安全的类:

ArrayList<Object> arrayList = new ArrayList<>();
List<Object> objects = Collections.synchronizedList(arrayList);

在这里插入图片描述

线程交互:

wait()的意思是, 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。

线程交互案例

需求:设计一个类,里面一个加1和一个减1方法,创建三个线程,对同一个数进行两个减1和一个加1,当数据到0,不能减1,等待数据大于0才能减1。
分析:减1方法用while循环的时候最好不要一直判断数据是否=0,可以wait()临时释放当前的占用,让加1加完再执行线程,而不是不停的循环判断,占用cpu资源。

public class AddAndSubstractThread {
    private static int num;

    public AddAndSubstractThread (int num) {
        this.num = num;
    }

    //同步锁是this
    public synchronized void add() {
        num++;
        System.out.println(Thread.currentThread().getName() + ":" + num);
        //唤醒其他所有等待在this对象上的线程
        this.notifyAll();
    }

    public synchronized void substract() {
        //这里不能用if
        //当用if时,num=0只会判断一次,在进入判断条件内部就不会再进行判断了,wait之后只要拿到锁就算num还是0也会减1.
        while (num == 0) {
            //暂时释放对this的占有,并等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName() + ":" + num);
    }
}
	public static void main(String[] args) {
        AddAndSubstractThread addAndSubstractThread = new AddAndSubstractThread(5);
        Thread add = new Thread() {
            @Override
            public void run() {
                while (true) {
                    addAndSubstractThread.add();
                    try {
                        sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread substract1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    addAndSubstractThread.substract();
                    try {
                        sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread substract2 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    addAndSubstractThread.substract();
                    try {
                        sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        add.start();
        substract1.start();
        substract2.start();
    }

线程池

每一个线程的启动和结束都是比较消耗时间和占用资源的。
如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢。
为了解决这个问题,引入线程池这种设计思想。
线程池类ThreadPoolExecutor在包java.util.concurrent下

Lock锁

lock与synchronized区别

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
  2. Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
  3. synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。

原子访问

所谓的原子性操作即不可中断的操作,比如赋值操作。原子性操作本身是线程安全的
在这里插入图片描述
可以使用原子类操作。
JDK6 以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger。

网络编程

网络连接介绍

网络连接要素:

  • 地址:ip和端口号
    在这里插入图片描述
    自己用端口最好避开这些默认的端口号:
    在这里插入图片描述

  • 协议:tcp,udp等
    在这里插入图片描述

连接分可靠的和不可靠的,可靠的比如TCP连接,不可靠的UDP连接。
在这里插入图片描述

单向通信

先启动server,再启动client

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) {
        try {
            Socket s = new Socket("127.0.0.1", 8888);
            OutputStream os = s.getOutputStream();
            
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeUTF("中英文混合alkjds");
            dos.close();
            
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            Socket s = ss.accept();
            InputStream is = s.getInputStream();

            DataInputStream dis = new DataInputStream(is);
            String msg = dis.readUTF();
            System.out.println(msg);
            dis.close();

            s.close();
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

双向通信

双向通信要用到多线程,这里可以把发送消息和接收消息抽取出来做成单独的两个类。

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

public class ReceiveThread extends Thread {
    private Socket s;

    public ReceiveThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        try (DataInputStream dis = new DataInputStream(s.getInputStream())) {
            while (true) {
                String msg = dis.readUTF();
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class SendThread extends Thread {
    private Socket s;

    public SendThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        try (DataOutputStream dos = new DataOutputStream(s.getOutputStream())) {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String next = scanner.next();
                dos.writeUTF(next);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) {
        try {
            Socket s = new Socket("127.0.0.1", 8888);
            new SendThread(s).start();
            new ReceiveThread(s).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            Socket s = ss.accept();
            new SendThread(s).start();
            new ReceiveThread(s).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

反射

获取类对象的方式

package com.coderhao.reflect;

public class Hero {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Hero(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Hero(){

    }

    public void showInfo(){
        System.out.println("id:"+id+",name:"+name);
    }
}

package com.coderhao.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTest {
    public static void main(String[] args) {
        //创建类对象方法
        //1
        Class heroClass1 = Hero.class;
        try {
            //2
            Class heroClass2 = Class.forName("com.coderhao.reflect.Hero");
            System.out.println(heroClass2);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //3
        Class heroClass3 = new Hero().getClass();
        //可以发现都是同一个地址
        System.out.println(heroClass1);
        System.out.println(heroClass3);

        System.out.println();
        //创建对象
        try {
            //类对象得到构造器,通过构造器实例化对象
            Constructor heroConstructor = heroClass1.getConstructor();
            Hero hero = (Hero)heroConstructor.newInstance();

            //得到对象的属性并赋值,由于属性为private修饰,需要field.setAccessible(true)
            Field field1 = hero.getClass().getDeclaredField("name");
            field1.setAccessible(true);
            field1.set(hero,"name1");
            System.out.println(hero.getName());
            
            //得到对象的方法,并使用
            Method method1 = hero.getClass().getMethod("showInfo");
            method1.invoke(hero);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注解

自定义注解

package com.coderhao.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//表示这个注解可以用用在类/接口上,还可以用在方法上
@Target({ElementType.METHOD,ElementType.TYPE})
//表示这是一个运行时注解,即运行起来之后,才获取注解中的相关信息
//而不像基本注解如@Override 那种不用运行,在编译时eclipse就可以进行相关工作的编译时注解。
@Retention(RetentionPolicy.RUNTIME)
public @interface HeroConfig {
    String name();
    int id() default 5;
}
package com.coderhao.annotation;
import com.coderhao.reflect.Hero;

//自定义的注解
@HeroConfig(name = "name1")
public class AnnotationTest {

    public static void main(String[] args) throws Exception {
        //反射得到对象实例
        Class hClass = Class.forName("com.coderhao.reflect.Hero");
        Hero hero = (Hero)hClass.newInstance();
        //得到主类上的注解信息
        HeroConfig heroConfig = AnnotationTest.class.getAnnotation(HeroConfig.class);

        hero.setId(heroConfig.id());
        hero.setName(heroConfig.name());

        hero.showInfo();
    }
}

元注解

注解自定义注解的注解

@Target

@Target 表示这个注解能放在什么位置上,是只能放在类上?还是即可以放在方法上,又可以放在属性上。
可以选择的位置列表如下:

  • ElementType.TYPE:能修饰类、接口或枚举类型
  • ElementType.FIELD:能修饰成员变量
  • ElementType.METHOD:能修饰方法
  • ElementType.PARAMETER:能修饰参数
  • ElementType.CONSTRUCTOR:能修饰构造器
  • ElementType.LOCAL_VARIABLE:能修饰局部变量
  • ElementType.ANNOTATION_TYPE:能修饰注解
  • ElementType.PACKAGE:能修饰包

@Retention

@Retention 表示生命周期

  • RetentionPolicy.SOURCE: 注解只在源代码中存在,编译成class之后,就没了。@Override 就是这种注解。
  • RetentionPolicy.CLASS: 注解在java文件编程成.class文件后,依然存在,但是运行起来后就没了。@Retention的默认值,即当没有显式指定@Retention的时候,就会是这种类型。
  • RetentionPolicy.RUNTIME: 注解在运行起来之后依然存在,程序可以通过反射获取这些信息,自定义注解@JDBCConfig 就是这样。

@Inherited

@Inherited 表示该注解具有继承性。

@Documented

@Documented如图所示, 在用javadoc命令生成API文档后,使用该注解的类的文档说明会出现该注解说明。

@Repeatable

当没有@Repeatable修饰的时候,注解在同一个位置,只能出现一次,有这个注解的注解可以多次重复出现在同一个地方,注解的属性值一般是个集合类。

	public @interface FileTypes {
        FileType[] value();
    }
    
    @FileType( ".java" )
    @FileType( ".html" )
    @FileType( ".css" )
    @FileType( ".js" )
    public void work(){
	//todo
	}

jdk8新特性的使用

lambda表达式

在这里插入图片描述
语法:
在这里插入图片描述

其实就是为了简化写法.
建个maven项目,引入依赖:

	<dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
      <scope>provided</scope>
    </dependency>

test测试:

package com.coderhao;

import org.junit.Test;
import java.util.Comparator;
import java.util.TreeSet;

public class LambdaTest {
	//test1和test2方法效果一样但lambda写法更简洁
    @Test
    public void test1() {
        Comparator comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        };

        TreeSet<Integer> integers = new TreeSet<Integer>(comparator);
        integers.add(3);
        integers.add(6);
        integers.add(2);

        integers.stream().forEach(System.out::println);//2,3,6
    }

    @Test
    public void test2() {
        Comparator<Integer> comparator = (x, y) -> (x - y);

        TreeSet<Integer> integers = new TreeSet<Integer>(comparator);
        integers.add(3);
        integers.add(6);
        integers.add(2);

        integers.stream().forEach(System.out::println);//2,3,6
    }

}

搞一个User类,下面要用:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
}

场景模拟:

	@Test
    public void test3() {
        ArrayList<User> users = new ArrayList<>();
        users.add(new User(1, "name1"));
        users.add(new User(2, "name2"));

        //需求:筛出id>1的
        List<User> users1 = filter1(users);

        //需求:晒出id>3的
        List<User> users2 = filter2(users);

        //以上的过滤方法太重复了,找解决办法:设计模式或lambda
        //匿名内部类。太麻烦
        users.stream()
                .filter(new Predicate<User>() {
                    @Override
                    public boolean test(User user) {
                        return user.getId() > 1;
                    }
                })
                .forEach(System.out::println);
        //stream+lambda,极端简洁,就是不好debug
        users.stream()
                .filter(x -> x.getId() > 1)
                .forEach(System.out::println);
    }

    public List<User> filter1(List<User> users) {
        List<User> result = new ArrayList<>();
        for (User user : users) {
            if (user.getId() > 1) {
                result.add(user);
            }
        }
        return result;
    }

    public List<User> filter2(List<User> users) {
        List<User> result = new ArrayList<>();
        for (User user : users) {
            if (user.getId() > 3) {
                result.add(user);
            }
        }
        return result;
    }

函数式接口

什么是函数式接口

  • 只包含一个抽象方法的接口,称为函数式接口。
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda
    表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方
    法上进行声明)。
  • 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,
    这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包
    含一条声明,说明这个接口是一个函数式接口。
    在这里插入图片描述

stream API

简介

stream流操作,可以处理集合。有以下特点:

  • 不会改变源对象,而是返回了一个新流。
  • 延迟操作,使用新流的对象时才会执行流操作。
    操作三步骤:
  1. 创建stream,通过一个集合或数组。
  2. 中间操作,对流的操作
  3. 终止操作,收集处理后的流

创建流

新建一个maven项目,添加个junit4的jar.
pom中部分代码:

	<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>
    </dependencies>

测试类:

package com.coderhao;

import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;

public class JDK8Test {


    @Test
    public void createStream() {
        //1. 集合变流
        Stream<Object> stream1 = new ArrayList<>().stream();
        //2. 数组变流
        Stream<Integer> stream2 = Arrays.stream(new Integer[3]);
        //3. Stream.of()
        Stream<String> stream3 = Stream.of("aa", "bb", "cc");
        //4. 无限流
        //4.1 iterate
        Stream<Integer> stream41 = Stream.iterate(0, (x) -> x + 2);
        stream41.limit(10).forEach(System.out::println);
        //4.2 generate
        Stream<Double> stream42 = Stream.generate(() -> Math.random());
        stream42.limit(10).forEach(System.out::println);
    }
}

操作流

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

筛选与切片

在这里插入图片描述
这个distinct筛选是通过hashCode和equals来去重,去去简单的类型可以,给实体类去重得重写hashcode和equals方法。可以用idea右键generate-equals() and hashCode();也可以使用lombok依赖的@data注解,自带重写hashCode和equals方法的,还是很方便的。

Stream<Double> stream = Stream.generate(() -> Math.random()*10);
stream.filter(x->x>5).limit(5).forEach(System.out::println);
映射

在这里插入图片描述

排序

在这里插入图片描述

终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

查找与匹配

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值