总结: 1.控制线程: join():等待线程死亡后,其他线程加入执行队列 sleep():当前线程以毫秒数暂停后继续执行 2.线程的分类:守护线程、用户线程
3.线程的生命周期 线程的状态:新建、就绪、运行、阻塞、死亡 4.线程同步 数据安全问题的解决方法: 同步代码块或同步方法:synchronized 关键字 相当于给代码加锁
5.线程的死锁:出现死锁 不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态 无法继续 解决办法: 1 减少同步代码块或同步方法的嵌套 ? 2 尽量减少同步资源的定义 ? 3 使用专门的算法 6.线程通信 1 等待 wait();唤醒notify / notify All() 2 互斥锁:依次最多只能有一个线程持有锁 lock()获得锁 / unlock()释放锁
7.网络编程 三要素:IP地址、端口、协议 UDP协议:发送数据,接收数据
1 线程控制
void | join() 等待这个线程死亡 |
---|
void | setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程。 | static void | sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 |
t1.join();
Thread.sleep(1000);
2 线程的分类
分为:守护线程 和 用户线程
- 守护线程和用户线程基本上是相同的。唯一的区别就是判断 jvm 何时离开
- 守护线程是用来服务用户线程的。通过在 start 方法之前调用 thread.setDeamon(true) 可以将一个用户多线程变成守护线程
- Java的垃圾回收,是一个典型的守护线程
- 如果 jvm 中都是守护线程,jvm 将退出(用户线程执行结束 守护线程无论是否结束 都将终止执行)
3 线程的生命周期
jdk中使用Thread State定义了线程的状态:
线程的状态通常分为5种状态:
-
新建:当一个Thread类及其子类声明并创建时,此时的线程对象就处于新建状态 -
就绪:处于新建状态的线程被start后,线程进入CPU的执行队列等待获得CPU的执行权,此时的线程已经具备了运行的条件,只是还未获得CPU的执行权 -
运行:当就绪的线程获得CPU事物执行权时。就处于运行状态 -
阻塞:在某种特殊的情况下,被认为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行。此时线程就进入了阻塞状态 -
死亡:完成了全部工作或线程被提前强制终止,或出现异常导致线程异常结束。
线程状态之间的相互转换 一个线程一旦死亡,是不可以重新启动的
4 线程同步
案例:卖票
需求:某电影院目前正在上映国产大片,共有100张票。而他有三个售票窗口,请设计一个程序模拟该影院售票
思路:
? 1 定义一个类SellTicket 实习Runnbale接口,定义一个成员变量 private int tickets = 100;
? 2 在SellTicket类中 重写run()。实现卖票:
? A 判断票数是否大于0
? B 售出票之后 票数减一
? C票没有了,也可能还有人来问,所以这个地方使用死循环 让卖票行为一直持续
3 定义一个测试类,实现售票动作
? A 创建SellTicket 类的对象
? B 创建三个Thread类 对象,将SellTicket 类的对象传递给线程 并给线程命名
? C 启动线程
public class SellTicket implements Runnable{
private int ticktes = 100;
@Override
public void run() {
while(true){
if(ticktes > 0 ){
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,"1号窗口");
Thread t2 = new Thread(st,"2号窗口");
Thread t3 = new Thread(st,"3号窗口");
t1.start();
t2.start();
t3.start();
}
}
出现每个窗口销售各自的100张票,实际生活中 售票是需要时间的 ,因此我们售卖一张票的时间为100毫秒
public class SellTicket implements Runnable{
private int ticktes = 100;
@Override
public void run() {
while(true){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
}
出现负票 和相同号的票 原因线程:执行的顺序是随机的
4.1 解决数据安全问题——同步代码块
出现问题的条件:
- 多线程环境
- 有共享数据
- 有多条语句操作共享数据
如何解决线程安全问题: 基本的思想:让程序没有安全问题的环境
怎么实现呢?
? 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行。
java提供了解决的方式是使用同步代码块或同步方法:synchronized 相当于给代码加锁
可以用在代码块和方法上 分别称为同步代码块和同步方法:
public class SellTicket implements Runnable{
private int ticktes = 100;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
}
}
在同步代码块中 ,任意对象都可以充当所对象 一般情况下使用this
4.2 解决数据安全问题——实现方法
在方法的声明上添加synchronized关键字
public class SellTicket implements Runnable{
private int ticktes = 100;
@Override
public void run() {
while(true){
sellTicket();
}
}
public synchronized void sellTicket(){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
静态同步方法
public class SellTicket implements Runnable{
private static int ticktes = 100;
@Override
public void run() {
while(true){
sellTicket();
}
}
public static synchronized void sellTicket(){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
静态方法的同步代码块
public class SellTicket implements Runnable{
private static int ticktes = 100;
@Override
public void run() {
while(true){
sellTicket();
}
}
public static void sellTicket(){
synchronized(SellTicket.class){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
静态的同步方法或者静态方法中的同步代码块的所对象是类名.class对象
单例设计模式:懒汉式设计存在线程安全问题
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if( instance == null){
synchronized (Singleton.class){
if(instance ==null){
instance = new Singleton();
}
}
}
return instance;
}
}
public class SingletonTest {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
4.6 线程安全的类
在实际使用时,如果不需要线程安全的实现,推荐使用与之功能相同的 但是线程不同步的实现
5 线程的死锁的演示
死锁是我们需要规避的问题
不同的线程分别占用对方所需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就行成了线程死锁
出现死锁 不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态 无法继续
public class DeadLockDemo {
public static void main(String[] args) {
final StringBuilder s1 = new StringBuilder();
final StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s2.append("A");
synchronized (s2){
s2.append("B");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (s2){
s2.append("C");
synchronized (s1){
s1.append("D");
System.out.println(s2);
System.out.println(s1);
}
}
}
}.start();
}
}
死锁问题的出现是一个概率时间。
死锁问题的解决:
? 1 减少同步代码块或同步方法的嵌套
? 2 尽量减少同步资源的定义
? 3 使用专门的算法
练习:
银行有一个账户,有两个储户分别向同一个账户存3000,每次存1000元,存3次,每次存完打印账户余额。
该程序是否存在安全问题,如果存在 如何解决?
提示:
? 1 明确那些代码是多线程运行的代码,就是需要写入run方法
? 2 明确那些数据是共享数据
? 3 明确多下称运行代码中的那些语句操作了共享数据
6 线程通信
6.1 什么时候需要线程通信
多个线程并发执行,在默认情况下CPU随机切换线程,如果我们希望他们有规律的执行,就需要使用线程通信。
6.2 线程间如何通信
如果线程需要等待 就要调用wait() 如果要唤醒一个等待的线程 那么就使用notify() / notifyAll()
void | wait() 导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法。 |
---|
void | notify() 唤醒正在等待对象监视器的单个线程。 | void | notifyAll() 唤醒正在等待对象监视器的所有线程。 | void | wait(long timeout) 导致当前线程等待,直到另一个线程调用 notify() 方法或该对象的 notifyAll() 方法,或者指定的时间已过。 |
public class ThreadWait {
public static void main(String[] args) {
ThreadWait tw = new ThreadWait();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
tw.print1();
}
}
}).start();
new Thread(){
@Override
public void run() {
while(true){
tw.print2() ;
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
tw.print3() ;
}
}
}.start();
}
public synchronized void print1(){
notifyAll();
System.out.print("中");
System.out.print("北");
System.out.print("大");
System.out.print("学");
System.out.println();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void print2(){
notifyAll();
System.out.print("塔");
System.out.print("里");
System.out.print("木");
System.out.print("大");
System.out.print("学");
System.out.println();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void print3(){
notifyAll();
System.out.print("青");
System.out.print("岛");
System.out.print("大");
System.out.print("学");
System.out.println();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三个线程间的通信:想要按照顺序依次执行,notify和wait做不到
6.3 互斥锁
互斥锁:最多只能有1个线程持有锁
锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁
void | lock() 获得锁。 |
---|
void | unlock() 释放锁。 |
Interface Lock 可以使用的实现类 ReentrantLock
- 一个可重入互斥
Lock 具有与使用synchronized 方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
Condition
void | await() 导致当前线程等到发信号或 interrupted 。 |
---|
boolean | await(long time, TimeUnit unit) 使当前线程等待直到发出信号或中断,或指定的等待时间过去。 | void | signal() 唤醒一个等待线程。 | void | signalAll() 唤醒所有等待线程。 |
不同的线程需要使用不同的 Condition 这样就能区分唤醒额时候唤醒的是那个线程
public class ThreadLock {
public static void main(String[] args) {
ThreadLock tw = new ThreadLock();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
tw.print1();
}
}
}).start();
new Thread(){
@Override
public void run() {
while(true){
tw.print2() ;
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
tw.print3() ;
}
}
}.start();
}
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
int flag = 1;
public void print1(){
lock.lock();
if(flag != 1){
try {
c1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("中");
System.out.print("北");
System.out.print("大");
System.out.print("学");
System.out.println();
flag = 2;
c2.signal();
lock.unlock();
}
public void print2(){
lock.lock();
if(flag != 2){
try {
c2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("塔");
System.out.print("里");
System.out.print("木");
System.out.print("大");
System.out.print("学");
System.out.println();
flag =3;
c3.signal();
lock.unlock();
}
public void print3(){
lock.lock();
if(flag != 3){
try {
c3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("青");
System.out.print("岛");
System.out.print("大");
System.out.print("学");
System.out.println();
flag =1;
c1.signal();
lock.unlock();
}
}
6.4 生产者消费者案例
案例需求:
? 生产者和消费者案例中包含几个对象:
? 1 奶箱(box) 定义一个变量 表示奶箱中奶的数量 提供存储牛奶和获取牛奶的方法
? 2 生产者(product) 线程 重写run 调用存储牛奶的方法
? 3消费者(Customer) 线程 重写run 调用获取牛奶的方法
测试:
? 1 创建奶箱对象 这是共享数据区
? 2生产者对象,把奶箱作为参数传递给生产者。 调用存储的方法
? 3创建消费者 把奶箱作为参数传递给消费者。 调用获取的方法
? 4 创建线程 启动线程
public class Box {
private int milk;
private boolean state = false;
public synchronized void put(int milk){
if(state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.milk = milk;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产者将第" + milk + "瓶牛奶放入奶箱");
this.state = true;
notifyAll();
}
public synchronized void get(){
if(!state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户消费了第" + milk +"瓶牛奶");
state = false;
notifyAll();
}
}
package cn.lanqiao;
public class Product implements Runnable{
private Box box;
public Product(Box box){
this.box = box;
}
@Override
public void run() {
for(int i = 0; i <=30 ; i++){
box.put(i);
}
}
}
package cn.lanqiao;
public class Customer implements Runnable{
private Box box;
public Customer(Box box){
this.box = box;
}
@Override
public void run() {
while(true){
box.get();
}
}
}
public class BoxTest {
public static void main(String[] args) {
Box box = new Box();
Product p = new Product(box);
Customer c = new Customer(box);
Thread t1= new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
生产者消费者模型的作用是什么? 1通过平衡生产者的生产能力和消费者消费能力来提升整个系统的运行效率 2 解耦
多线程的学习重点:
- 线程的创建方式
- 线程的生命周期(线程的五种状态的转换)
- 线程同步
- 线程通信
7 网络编程
7.1 计算机的网络
网络: 是指将地理位置不同的具有地理功能的计算机及其外部设备 通过通信线路连接起来,在网络操作系统 网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
网络编程 :在网络协议下,实现网络互联的不同计算机上运行的程序见可以进行数据交换
7.2 网络编程的三要素:
IP地址:网络中每一台计算机的唯一的标识
端口:就是通过唯一标识锁定的设备上的应用程序的标识
协议:对数据的传输格式 传输速率 传输的步骤等做的统一规定,将这种规则称为通信协议,双方都必须遵守,才能完成数据交换。 常见的通信协议:UDP和TCP
7.3 InetAddress
static InetAddress | getByName(String host) 确定主机名称的IP地址。 |
---|
String | getHostName() 获取此IP地址的主机名。 | String | getHostAddress() 返回文本显示中的IP地址字符串 | static InetAddress | getLocalHost() 返回本地主机的地址。 |
public static void main(String[] args) throws UnknownHostException {
InetAddress address = InetAddress.getLocalHost();
System.out.println(address.getHostName());
InetAddress address1 = InetAddress.getByName("10.96.106.111");
System.out.println(address1.getHostAddress());
}
7.4 端口
端口:是程序在设备上的标识号 端口号:使用两个字节表示的整数,取值范围0—65535 ,其中0–1023之间的端口主要用于一些网络服务和应用。在自己写网络协议时尽量使用1024以上的端口
7.5 协议
UDP协议: 用户数据报协议 是无连接的通信协议,在数据传输时,数据的发送端和接收端不建立逻辑链接
优点:UDP协议消耗资源少,通信的效率高。
TCP:传输控制协议 是面向链接的通信协议,在传输数据之前。必须在发送端和接收端之间建立逻辑链接,然后才能传输数据。可以提供两台设备之间的可靠无差错通信
TCP协议区分发送端和接收端
TCP协议的链接的建立:
- TCP的链接需要经过三次”握手“
- TCP协议断开需要经过四次”挥手“
7.6 UDP协议通信程序
UDP是属于一种不可靠的协议,在通信的两端需要建一个Socket(网络套接字)对象。都是这两个Socket,只是发送和接收数据的对象,因此对于基于UDP协议的通信而言。没有所谓的客户端和服务器的概念
DataGramSocket 基于UDP 协议的Socket
- 此类表示用于发送和接收数据报数据包的套接字。
- 数据报套接字是分组传送服务的发送或接收点。
构造方法:
| DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口。 |
---|
protected | DatagramSocket(DatagramSocketImpl impl) 使用指定的DatagramSocketImpl创建一个未绑定的数据报套接字。 |
| DatagramSocket(int port) 构造数据报套接字并将其绑定到本地主机上的指定端口。 |
| DatagramSocket(int port, InetAddress laddr) 创建一个数据报套接字,绑定到指定的本地地址。 |
| DatagramSocket(SocketAddress bindaddr) 创建一个数据报套接字,绑定到指定的本地套接字地址。 | void | receive(DatagramPacket p) 从此套接字接收数据报包。 | ------ | ----------------------------------------------------- | void | send(DatagramPacket p) 从此套接字发送数据报包。 |
DataGramPacket 表示数据报包,用于实现无连接分组传送服务
DatagramPacket(byte[] buf, int length) 构造一个 DatagramPacket 用于接收长度的数据包 length 。 |
---|
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造用于发送长度的分组的数据报包 length 指定主机上到指定的端口号。 | DatagramPacket(byte[] buf, int offset, int length) 构造一个 DatagramPacket 用于接收长度的分组 length ,指定偏移到缓冲器中。 | DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 构造用于发送长度的分组数据报包 length 具有偏移 ioffset 指定主机上到指定的端口号。 | DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 构造用于发送长度的分组数据报包 length 具有偏移 ioffset 指定主机上到指定的端口号。 | DatagramPacket(byte[] buf, int length, SocketAddress address) 构造用于发送长度的分组的数据报包 length 指定主机上到指定的端口号。 |
发送数据的步骤:
- 创建发送端的Socket对象 DataGramSocket
- 创建数据,并将数据打包
- 调用DataGramSocket 的 send 方法
- 关闭发送端 Close
public class SendSocket {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
byte[] packet= "hello UDP".getBytes(StandardCharsets.UTF_8);
DatagramPacket dp = new DatagramPacket(packet,packet.length, InetAddress.getByName("127.0.0.1"),10086);
ds.send(dp);
ds.close();
}
}
接收数据的步骤:
- 创建发送端的Socket对象 DataGramSocket
- 创建一个数据报包 用于接收数据DataGramPacket
- 调用DATa Gram Socket的reveive接收
- 解析数据报包,把信息输出
- 关闭接收端口
public class ReceiveSocket {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(12503);
while (true) {
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data, data.length);
ds.receive(dp);
System.out.println("接收发送端的数据:" + new String(dp.getData(), 0, dp.getLength()));
}
}
}
7.6.1 UDP协议通信的练习
需求:
UDP发送数据,接收来自键盘录入,直到输入的数据是666/bye 发送结束
UDP接收数据,因为接收端你不知道发送端何时发送数据 所以采用死循环 持续等待
public class SendSokect2 {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while((line = br.readLine())!=null){
if("bye".equals(line)){
break;
}
byte[] data = line.getBytes(StandardCharsets.UTF_8);
DatagramPacket dp = new DatagramPacket(data,data.length, InetAddress.getByName("127.0.0.1"),10086);
ds.send(dp);
}
ds.close();
}
}
public class ReciveSocket2 {
public static void main(String[] args) throws IOException {
System.out.println("接收端启动成功:");
DatagramSocket ds = new DatagramSocket(10086);
while(true){
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data,data.length);
ds.receive(dp);
System.out.println("接收到发送端的数据:" +new String(dp.getData(),0,dp.getLength()));
}
}
}
|