多线程
进程&线程
启动一个程序,是一个进程
一个进程内并行执行的是多个线程
创建多线程三种方式
这里以把同一个数循环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就是个锁,要多个线程共用一个锁线程同步才会生效。
- extends Thread 形式的线程同步:
- 实现Runnable接口的由于是同一个AddThread2类的对象生成了两个线程,所以可以用本对象当锁。
synchronized (this) {
}
表示当前对象为同步对象,即是addThread2为同步对象。
- 匿名内部类用个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区别
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
- Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
- 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流操作,可以处理集合。有以下特点:
- 不会改变源对象,而是返回了一个新流。
- 延迟操作,使用新流的对象时才会执行流操作。
操作三步骤:
- 创建stream,通过一个集合或数组。
- 中间操作,对流的操作
- 终止操作,收集处理后的流
创建流
新建一个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 。