多线程
线程的创建方式
继承Thread
继承Thread后重写run方法,之后只需要创建子类对象,使用子类对象调用start()方法来开启线程
这种方式可以直接使用Thread的方法,但可扩展性差
实现runable接口
通过实现runable接口并重写run方法,之后需要创建实现类对象并将其作为参数传递给Thread(实现类)构造函数来创建一个线程,之后使用Thread对象调用Start()方法来开启线程
这种方式实现类无法直接使用Thread方法,必须依靠Thread对象,实现类作为任务对象存在
实现类内部依靠Thread.currentThread()方法来获取当前线程的对象之后才可以在实现类内使用Thread方法了
实现callable接口和Future接口
上述两种方法都是没有返回值的,当需要线程的返回值时需要使用这种方法
实现Callable<返回值类型>接口并创建实现类对象,然后将实现类对象作为参数传递给构造函数来创建FutureTask<返回值类型>对象来管理返回值,之后将FutureTask<返回值类型>对象作为参数传递给Thread(实现类)构造函数来创建一个线程,之后使用Thread对象调用Start()方法来开启线程
可以使用FutureTask<返回值类型>对象的方法get()来获取返回值
缺点也是无法直接使用Thread方法
Thread常用方法
void setName(String name),给线程设置名字,也可以在创建线程时直接设置名字
String getName(),获取线程名字,如果没设置名字,则默认thread-序号
Thread currentThread(),获取当前线程
static void sleep(long millis),线程休眠,单位毫秒
final int getPriority(),获取线程优先级
final void setPriority(int newPriority),设置线程优先级,默认为5,最小1,最大10
void setDaemon(boolean on),设置为守护线程,当非守护线程结束时,守护线程会跟着陆续结束(不是立即结束)
static void yield(),礼让线程
static void join(),插入线程,将线程插入到当前线程之前,当前线程就是join执行的线程
生命周期
- 创建
- 就绪,此时线程开始抢夺CPU执行权但未执行
- 运行,抢到CPU执行权后开始执行代码,如果此时被抢走CPU执行权会回到就绪状态
- 阻塞,遇到sleep等阻塞方法,阻塞结束后会回到就绪状态
- 死亡,线程代码执行完毕
同步代码块
一个线程在执行同步代码块的时候,其他线程无法访问同一个锁对象看管的同步代码块,确保静态资源不会出现重复
格式synchronized(锁对象){代码},其中锁对象可以是任意对象,只需要确保需要锁住的同步代码块之间的锁对象是唯一的即可,也就是静态共享的,确保锁是一样的否则无法阻止其他线程进入
一般可以使用当前类的字节码文件对象,因为一个文件夹的字节码文件是确定唯一不可能有重复的,xxxx.class
同步方法
格式修饰符 synchronized 返回值 方法名(){},锁对象无法自己指定,如果方法不是静态的则默认this,如果方法是静态的则为字节码文件对象
lock锁
比同步代码块灵活,可以自己控制上锁开锁的时机
lock无法实例化,因此创建其实现类ReentrantLock,同时需要注意创建的锁对象要唯一,最好静态
方法有lock.lock(),lock.unlock(),需要注意程序完成一定要释放锁,否则会导致其他线程无法访问导致程序无法结束,可以使用finally作为扫尾
等待唤醒机制
程序分为生产者和消费者,生产者判断队列是否有内容,有则等待,没有则发布,发布完叫醒消费者
消费者先判断队列有没有内容,没有则等待,有则取出,并叫醒生产者
常用方法
wait(),线程等待,直到被唤醒,这个方法会释放锁
notify(),随机唤醒单个线程,这个方法会将锁给包含wait方法的线程,让其继续进行
notifyAll(),唤醒所有线程,这个方法会将锁给包含wait方法的线程,让其继续进行
书写时必须使用锁对象去调用方法,让线程和锁对象绑定,因为唤醒的时候不能唤醒java中所有线程,因此使用锁对象调用表示只唤醒锁对象绑定的线程
为什么三个方法必须要在同步代码块中执行
因为以上三个方法都包含释放锁或者转交锁这个步骤,而释放锁的前提是自己有锁
阻塞队列实现等待唤醒机制
实现了生产者和消费者之间的队列,队列满则生产者阻塞,队列空则消费者阻塞,不需要自己书写同步代码块,提供了队列方法
阻塞队列实现
是个接口,只能实现其实现类,有两个
ArrayBlockingQueue(队列长度),底层是数组,有界
LinkedBlockingQueue(),底层是链表,无界,上限是int边界
同时注意,生产者和消费者的队列必须是同一个队列对象,因此最好单独创建队列对象,在创建生产者和消费者时将其作为参数传入,保证其唯一性
常用方法
put(),生产者将内容放到队列,满则自动阻塞
take(),消费者从队列中取内容,队列空则自动阻塞
以上两种方法不需要自己加锁,因为方法已经调用了锁
线程的完整六状态
- 创建
- 就绪,此时线程开始抢夺CPU执行权但未执行
- 计时状态,遇到sleep,时间到会自动醒来
- 等待状态,遇到wait,等待notify唤醒
- 阻塞状态,无法获得锁对象,得到锁醒来
- 死亡,线程代码执行完毕
java实际上没有运行状态,因为线程获得cpu执行权后虚拟机会将其交给操作系统,虚拟机不会管理
线程池
在每次执行线程时我们都创建一个新的线程并在执行完销毁,导致资源浪费,因此线程池可以将空闲线程存放并在下次需要使用时重复利用
线程池会在有任务时看看池内有没有空闲线程,没有则创建一个新的线程,线程池可以设置上限,如果同时执行的任务超过上限,线程池会让超过上限的任务进行排队等待其他任务归还线程
常用方法
Executors.newCachedThreadPool(),创建一个无上限的线程池
Executors.newFixedThreadPool(int),创建一个有上限的线程池
submit(),提交任务,获取线程
shutdown(),销毁线程池
自定义线程池
java提供的线程池不够灵活,因此我们可以使用自定义线程池
我们可以使用ThreadPoolExecutor类来创建一个自定义线程池,它的构造方法最多要七个参数,分别是
- 核心线程的数量,不小于0
- 线程池中的最大数量,据此可以计算出临时线程数,不能小于核心线程数
- 临时线程空闲多长时间被销毁的值
- 临时线程空闲多长时间被销毁的单位,使用
TimeUnit类中的常量表示 - 阻塞队列,存放等待核心线程的任务的,
new ArrayBlockingQueue(队列长度)就可以 - 创建线程的方式,使用
Executors.defaultThreadFactory()创建 - 当任务过多时采取的策略,是静态内部类,因此创建格式为
new ThreadPoolExecutor.AbortPolicy(),还有其他三种策略,了解即可
示例new ThreadPoolExecutor(3, 6, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
自定义线程池执行顺序为,先是创建核心线程,当任务超过核心线程让其排队等待,当队列满了之后开始创建临时线程为超出队列的任务服务,如果连临时线程都满了则执行拒绝策略
最大并行数
就是电脑cpu的线程数,或者java可以使用的cpu线程数
线程池多大合适
cpu密集型运算
计算较多,文件读取较少,线程池大小为最大并行数+1
I/O密集型运算
读取文件较多,则线程池大小为:最大并行数*期望CPU利用率*((CPU计算时间+等待时间)/CPU计算时间)
网络编程
网络三要素
ip
ip是确定一台电脑的地址
使用InetAddress类来创建一个ip对象,InetAddress不能直接new出来,只能使用静态方法getByName(主机名)来获取
可以看做一个电脑的对象,可以获取到主机的名字getHostName(),主机的ipgetHostAddress()
端口号
端口可以唯一确定一个应用
取值范围0~65535,0~1023之间的端口号用于知名网络服务不能使用
一个端口号只能一个应用程序使用
协议
UDP协议:
- 用户数据报协议
- 面向无连接通信
- 速度快,有大小限制64k,数据不安全,易丢失
TCP协议
- 传输控制协议
- 面向连接
- 速度慢,数据安全,没有大小限制
UDP协议
发送数据
使用DatagramSocket类创建一个数据传输对象,
使用DatagramPacket类打包数据,他接受一个字符数组、数组长度、ip地址InetAddress、端口号
使用send(DatagramPacket对象)方法发送数据
使用close()方法释放资源
接收数据
使用DatagramSocket类创建一个数据传输对象,此时创建时需要绑定端口号,且必须是发送端指定的端口号
使用DatagramPacket类作为接收数据的载体,接受一个字符数组和长度
使用DatagramSocket对象.receive(DatagramPacket对象)方法接收数据
使用DatagramPacket对象.getData()、getLength()、getAddress()、getPost()来解析数据
三种方式
- 单播,一对一
- 组播,一对多,组播地址:
224.0.0.0~239.255.255.255,如果是我们自己使用则只能使用预留地址224.0.0.0~224.0.0.255,创建的数据传输对象变成MulticastSocket,发送IP设为预留地址,在接收端需要将本机加入组内才能收到数据 - 广播,一对多,广播地址:
255.255.255.255,只需要发送地址改成255.255.255.255即可
TCP协议
发送数据
使用Socket类配合I/O输出流来输出数据
接收数据
使用ServerSocket类配合I/O输入流来接收数据
端口一定要对上
两者I/O流都可以使用高级流包装