IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 关于网络编程和多线程的总结 -> 正文阅读

[网络协议]关于网络编程和多线程的总结

0、其他

0.1、函数式接口

Functional Interface:函数式接口

定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口

?//这就是一个函数式接口
?//只有一个抽象方法
?public interface Marry {
?    
?    public abstract void HappyMarry();
?    
?}

  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象

0.2、Lambda表达式

概述:

  • λ希腊字母表中排序第十一位的字母,英文名称为Lambda

  • 避免弥明内部类定义过多

  • 其实质属于函数式编程的概念

格式:

  • ?(params) -> expression[表达式]
  • ?(params) -> statement[语句]
  • ?(params) -> { statements }

例子:

  • ?a -> System.out.println(" I like lambda-->" + a);
  • ?new Thread(() -> System.out.println(" I like 学习!!!")).start();

优点:

  • 避免匿名内部类定义过多

  • 可以让你的代码看起来很简洁

  • 去掉了一堆没有意义的代码,只留下核心的逻辑

0.2.1、lambda代码例子

包括推导的流程:

  1. 函数式接口(只有Lambda表达式需要这样的函数式接口)

  2. 正常实现类

  3. 静态内部类

  4. 局部内部类

  5. 匿名内部类

  6. Lambda表达式(必须是函数式接口)

?//推导lambda表达式
?public class LambdaDemo01 {
?    
?    //3.静态内部类
?    static class Like2 implements Ilike{
?        
?        @Override
?        public void lambda() {
?            // TODO Auto-generated method stub
?            System.out.println("我喜欢Lambda表达式2!!!(静态内部类)");
?        }
?        
?    }
?    
?    public static void main(String[] args) {
?        
?        //调用实现类
?        Ilike like = new Like();
?        like.lambda();
?        
?        //调用静态内部类
?        like = new Like2();
?        like.lambda();
?        
?        //4.局部内部类
?        class Like3 implements Ilike{
?            
?            @Override
?            public void lambda() {
?                // TODO Auto-generated method stub
?                System.out.println("我喜欢Lambda表达式3!!!(局部内部类)");
?            }
?            
?        }
?        
?        //调用局部内部类
?        like = new Like3();
?        like.lambda();
?        
?        //5.匿名内部类,没有类的名称,必须借助接口或者父类
?        //调用匿名内部类
?        like = new Ilike() {
?            
?            @Override
?            public void lambda() {
?                // TODO Auto-generated method stub
?                System.out.println("我喜欢Lambda表达式4!!!(匿名内部类)");
?            }
?        };
?        like.lambda();
?        
?        //6.Lambda表达式
?        //调用Lambda表达式
?        like = () -> {
?            // TODO Auto-generated method stub
?            System.out.println("我喜欢Lambda表达式5!!!(Lambda表达式)");
?        };
?        like.lambda();
?    }
?    
?}
??
?//1.定义一个函数式接口
?interface Ilike{
?    
?    void lambda();
?    
?}
??
?//2.实现类
?class Like implements Ilike{
?    
?    @Override
?    public void lambda() {
?        // TODO Auto-generated method stub
?        System.out.println("我喜欢Lambda表达式1!!!(实现类)");
?    }
?    
?}

0.2.2、带参Lambda代码例子

?//推导lambda表达式
?public class LambdaDemo02 {
?    
?    //3.静态内部类
?    static class Love2 implements Ilove{
?        
?        @Override
?        public void lambda(String name) {
?            System.out.println("我喜欢Lambda表达式2!!!-->" + name);
?        }
?        
?    } 
?    
?    public static void main(String[] args) {
?        
?        //调用实现类
?        Ilove love = new Love();
?        love.lambda("实现类");
?        
?        //调用静态内部类
?        love = new Love2();
?        love.lambda("静态内部类");
?        
?        //4.局部内部类
?        class Love3 implements Ilove{
?            
?            @Override
?            public void lambda(String name) {
?                System.out.println("我喜欢Lambda表达式3!!!-->" + name );
?            }
?            
?        } 
?        
?        //调用局部内部类
?        love = new Love3();
?        love.lambda("局部内部类");
?        
?        //5.匿名内部类,没有类的名称,必须借助接口或者父类
?        //调用匿名内部类
?        love = new Ilove() {
?            
?            @Override
?            public void lambda(String name) {
?                System.out.println("我喜欢Lambda表达式4!!!-->" + name );
?            }
?        };
?        love.lambda("匿名内部类");
?        
?        //6.Lambda表达式
?        //调用Lambda表达式
?        love = (String name) -> {
?            System.out.println("我喜欢Lambda表达式5!!! -->" + name );
?        };
?        love.lambda("Lambda表达式");
? ? ? ? ?
? ? ? ? ?//7.Lambda表达式去参数类型
? ? ? ? ?//当具有多个参数,只有参数类型都一样才可以这么简化。
? ? ? ? ?//并且要去掉就都去掉,要不然都不去掉,且括号不能被简化了
?        //调用Lambda表达式
?        love = (name) -> {
?            System.out.println("我喜欢Lambda表达式6!!! -->" + name );
?        };
?        love.lambda("Lambda表达式去参数类型");
?        
? ? ? ? ?//8.Lambda表达式去参数类型和括号
? ? ? ? ?//当参数只有一个的时候可以这么使用
?        //调用Lambda表达式
?        love = name -> {
?            System.out.println("我喜欢Lambda表达式7!!! -->" + name );
?        };
?        love.lambda("Lambda表达式去参数类型和括号");
?        
?        //9.Lambda表达式去参数类型、括号和花括号
? ? ? ? ?//只有方法体只有的一行的情况才能这么简化
?        //调用Lambda表达式
?        love = name -> System.out.println("我喜欢Lambda表达式8!!! -->" + name );
?        love.lambda("Lambda表达式去参数类型、括号和花括号");
?    }
?    
?}
??
?//1.定义一个函数式接口
?interface Ilove{
?    
?    void lambda(String name);
?    
?}
??
?//2.实现类
?class Love implements Ilove{
?    
?    @Override
?    public void lambda(String name) {
?        System.out.println("我喜欢Lambda表达式1!!!-->" + name );
?    }
?    
?}

1、网络编程

1.1、概述

地球村:整个地球实现网络的连接,体现科技的发展,缩小了世界的时空距离。


计算机网络:

计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

网络编程的目的:

传播交流信息、数据交换、通信

想要达到这个效果需要什么

  1. 如何准确的定位网络上的一台主机。127.0.0.1: 端口号, 定位到这个计算机上的某个资源。

  2. 找到这个主机,如何传输数据呢?

Java Web:网页编程 B/S

网络编程:网络编程 C/S

1.2、网络通信的要素

三要素

  • IP:电子设备(计算机,服务器)在网络中的唯一标识

  • 端口:应用程序在计算机中的唯一标识。0~65536之间,1024以内的端口有可能被操作系统占用

  • 传输协议:规定数据传输的规则

1.3、IP

IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议。设计IP的目的是提高网络的可扩展性:一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端的设计原则,IP只为主机提供一种无连接、不可靠的、尽力而为的数据包传输服务。

1.4、端口

就是电脑与外界通信的端口。

什么是端口

我们可以这样理解,知道了你家的地址门牌号,如果要进入你家里,一定要走门的,那么门就相当于我们所说的端口,对方的电脑要和你的电脑进行通信,你的电脑会开一个端口的,因为软件不同,所以所开的端口也不同的,也就是不同的软件有不同的端口,一般来说是不变的,比如说“冰河”的端口就是 7626 而“黑洞”的是2000 我可以在msdos下打一个命令 netstat -s 就可以看到本机所开的端口了,如果发现了上面的两种多端口,多数是中了木马了。 ? QQ之间通信也要有端口的,61.159.183.45:4000这是刚查到的tty的IP和QQ的端口,后面的4000就是端口。 TCP/IP协议中的端口,是逻辑意义上的端口。我们可以把IP地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536个之多。端口是通过端口号来标记的,端口号只有整数,范围从0 到65535。   端口号有两种基本分配方式:第一种叫全局分配,这是一种集中分配方式,由一个公认权威的中央机构根据用户的需要进行统一分配,并将结果公布于众;第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回本地唯一的端口号,进程再通过合适的系统调用,将自己和该端口连接起来绑定。   TCP/IP端口号的分配综合了以上两种方式,将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程。TCP和UDP规定,小于256的端口才能作为保留端口。剩余的为自由端口,以本地方式进行分配。于是一些端口常常会被黑客利用,对计算机系统进行攻击。

1.5、通信协议

(引用的网上的图片)?

协议:约定,就好比我们现在说的是普通话

网络通信协议:速率、传输码率、代码结构、传输控制。。。

问题:非常的复杂

大事化小:分层

TCP/IP协议簇(实际上是一组协议,不是一个写或者两个协议)

重要的协议:

  • TCP :用户传输协议

  • UDP:用户数据报协议

出名的协议:

  • TCP :用户传输协议

  • IP :网络互联协议

TCP和UDP对比

TCP:相当于打电话

  • 连接,稳定

  • 三次握手四次挥手

    ?最少需要三次,保证稳定连接!为了确保两端都在,已完成连接
    ?A:打招呼!
    ?B:回应!
    ?A:我开始了!
    ??
    ??
    ?需要四次进行连接的断开!为了确保一方想要断开,但是另一方还在接受或者正在发送,进行延迟断开,只有两方都要断开了才能真正的断开。
    ?A:我要断开了!
    ?B:我知道了!
    ?B:我也要断开了!
    ?A:我也知道了!

  • 客户端、服务端进行连接

  • 传输完成,释放连接,效率低

UDP:相当于发短信

  • 不连接、不稳定

  • 客户端,服务端进行连接,但是没有明确的界限

  • 不管有没有准备好,都可以发给你。能否接收到看自己的造化。

  • 导弹

  • DDOS:洪水攻击!饱和攻击

1.6、TCP

1.6.1、消息发送

客户端

  1. 连接服务器通过Socket

  2. 发送消息

?import java.io.OutputStream;
?import java.net.InetAddress;
?import java.net.Socket;
??
?public class ClientDemo02 {
??
?    public static void main(String[] args) throws Exception {
?    
?        //1.创建一个Socket连接
?        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000);
?                
?        //2.创建一个输出流
?        OutputStream os = socket.getOutputStream();
?        
?        //3.发送消息
?        os.write("你好,欢迎学习Java多线程!!!".getBytes());
?        
?        os.close();
?        socket.close();
?    
?    }
?}

服务器

  1. 建立服务的端口ServerSocket

  2. 等待用户的连接通过accept

  3. 接受用户的消息

?import java.io.ByteArrayOutputStream;
?import java.io.InputStream;
?import java.net.ServerSocket;
?import java.net.Socket;
??
?public class ServerDemo02 {
?    
?    public static void main(String[] args) throws Exception {
?        
?        ServerSocket serverSocket = new ServerSocket(9000);
?        
?        Socket socket = serverSocket.accept();
?        
?        InputStream is = socket.getInputStream();
?        
?        //管道流
?        ByteArrayOutputStream 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());
?        
?        bos.close();
?        is.close();
?        socket.close();
?        serverSocket.close();
?        
?    }
?    
?}
??

1.6.2、文件上传

客户端

  1. 连接服务器通过Socket

  2. 发送消息

?import java.io.ByteArrayOutputStream;
?import java.io.FileInputStream;
?import java.io.InputStream;
?import java.io.OutputStream;
?import java.net.InetAddress;
?import java.net.Socket;
??
?public class ClientDemo01 {
??
?    public static void main(String[] args) throws Exception {
?        
?        //1.创建一个Socket连接
?        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000);
?        
?        //2.创建一个输出流
?        OutputStream os = socket.getOutputStream();
?        
?        //3.读取文件
?        FileInputStream fis = new FileInputStream("4.png");
?        
?        //4.写出文件
?        byte[] buffer = new byte[1024];
?        int len;
?        while((len = fis.read(buffer))!=-1) {
?            
?            os.write(buffer,0,len);
?            
?        }
?        
?        socket.shutdownOutput();
?        
?        
?        //确定服务器接收完毕,才能够断开连接
?        InputStream input = socket.getInputStream();
?        
?        ByteArrayOutputStream baos = new ByteArrayOutputStream();
?        
?        byte[] buffer2 = new byte[1024];
?        
?        int len2;
?        
?        while((len2 = input.read(buffer2))!=-1) {
?            baos.write(buffer2,0,len2);
?        }
?        
?        System.out.println(baos.toString());
?        
?        //5.关闭资源
?        baos.close();
?        input.close();
?        fis.close();
?        os.close();
?        socket.close();
?    }
?    
?}

服务器

  1. 建立服务的端口ServerSocket

  2. 等待用户的连接通过accept

  3. 接受用户的消息

?import java.io.File;
?import java.io.FileOutputStream;
?import java.io.InputStream;
?import java.io.OutputStream;
?import java.net.ServerSocket;
?import java.net.Socket;
??
?public class ServerDemo01 {
?    
?    public static void main(String[] args) throws Exception {
?        
?        //1.创建服务
?        ServerSocket serverSocket = new ServerSocket(9000);
?        
?        //2.监听客户端的连接
?        Socket socket = serverSocket.accept();
?        
?        //3.文件输出
?        InputStream is = socket.getInputStream();
?        
?        //4.文件输出
?        FileOutputStream fos = new FileOutputStream(new File("44444.png"));
?        byte[] buffer = new byte[1024];
?        
?        int len;
?        
?        while((len = is.read(buffer))!=-1) {
?            
?            fos.write(buffer,0,len);
?        }
?        
?        //通知客户端我接受完毕了
?        OutputStream os = socket.getOutputStream();
?        os.write("我接受完毕了,你可以断开了!!!".getBytes());
?        
?        
?        //5.关闭服务
?        os.close();
?        fos.close();
?        is.close();
?        socket.close();
?        serverSocket.close();
?    }
??
?}
??

1.6.3、Tomcat服务器

服务端(两种方式)

  • 自定义 S

  • Tomcat服务器或者其他服务器 S :Java后台开发主要使用这些服务器!

客户端(两种方式)

  • 自定义 C

  • 浏览器 B

1.7、UDP

注意

  • 没有具体的客户端和服务端,客户端也可以是服务端,服务端也可以是客户端。

  • 可以了解接收端和发送端具体怎么写,但是每一端都可以发送和接收


1.7.1、发送消息

发送端步骤

  1. 建立一个Socket

    ?DatagramSocket socket = new DatagramSocket();
  2. 建一个包

    ?String msg = "你好啊,服务器!";
    ??
    ?//发送给谁
    ?InetAddress localhost = InetAddress.getByName("lockhost");
    ?int port = 9090;
    ??
    ?//参数分别为数据、数据的长度起始、要发送给谁
    ?DatagramPacket packet = new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,localhost,port);

  3. 发送包

    ?socket.send(packet);
  4. 关闭流

    ?socket.close();

接收端步骤

  1. 创建一个Socket

    ?//端口号与客户端连接的端口号一致,不然连接不上
    ?DatagramSocket socket = new DatagramSocket(9090);
  2. 接受数据包

    ?byte[] buffer = new byte[1024];
    ?DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
    ?//堵塞接收
    ?socket.receive(packet);
  3. 打印数据包

    ?//打印数据包是从哪里来的
    ?System.out.println(packet.getAddress().getHostAddress());
    ?//打印数据包的内容
    ?System.out.println(new String(packet.getData(),0,packet.getLength());
  4. 关闭流

    ?socket.close();

1.7.2、咨询

双放互发消息:比如客户与客服之间进行聊天

第一版客户端步骤:

?import java.io.BufferedReader;
?import java.io.InputStreamReader;
?import java.net.DatagramPacket;
?import java.net.DatagramSocket;
?import java.net.InetSocketAddress;
??
?public class UdpSendDemo01 {
?    public static void main(String[] args) throws Exception {
?        // TODO Auto-generated method stub
?        //创建socket
?        DatagramSocket socket = new DatagramSocket(8888);
??
??
?        while(true){
?         ? ?
?         ? ?//准备数据:控制台读取System.in
?         ? ?BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
??
?         ? ?String data = reader.readLine();
?         ? ?byte[] dataByte = data.getBytes();
?         ? ?
?         ? ?DatagramPacket packet = new DatagramPacket(dataByte,0,dataByte.length,new InetSocketAddress("localhost",6666));
?         ? ?
?         ? ?//发送包
?            socket.send(packet);
?         ? ?
?         ? ? if("quit".equals(dataByte)){
?         ? ? ? ?break;
?         ?  }
?        }
??
?        //关闭流
?        socket.close();
?    }
?}

第一版客服端步骤:

?import java.net.DatagramPacket;
?import java.net.DatagramSocket;
??
?public class UdpReceiveDemo01 {
?    public static void main(String[] args) throws Exception {
?        //创建socket
?        DatagramSocket socket = new DatagramSocket(6666);
??
?        while(true){
?         ? ?//接收数据包
?            byte[] buffer = new byte[1024];
?            DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
?         ? ?//堵塞接收数据包
?            
?            socket.receive(packet);
?         ? ?
?         ? ?byte[] data = packet.getData();
?         ? ?String receiveData = new String(data,0,data.length);
?         ? ?
?         ? ?//打印数据包的内容
?            System.out.println(receiveData);
?            
?         ? ?if("quit".equals(receiveData.substring(0, 4))){
?         ? ? ? ?break;
?         ?  }
?        }
??
?        //关闭流
?        socket.close();
?    }
??
?}

第二版发送端:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

public class UdpSendDemo02 implements Runnable{
	
	private DatagramSocket socket = null;
	private int port;
	private String toIP;
	private int toPort;

	public UdpSendDemo02(int port, String toIP, int toPort) {
		this.port = port;
		this.toIP = toIP;
		this.toPort = toPort;
		
		try {
            //创建socket
			socket = new DatagramSocket(port);
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
	
		while(true){
			//准备数据:控制台读取System.in
			BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
			
			String data;
			try {
				data = reader.readLine();
				byte[] dataByte = data.getBytes();
				DatagramPacket packet = new DatagramPacket(dataByte,0,dataByte.length,new InetSocketAddress(toIP,toPort));
				//发送包
				socket.send(packet);
						 
				if("quit".equals(data)){
					break;
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		//关闭流
		socket.close();
	}

}

第二版接收端:

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UdpReceiveDemo02 implements Runnable{
	
	private DatagramSocket socket = null;
	private int port;
	private String fromName;
	
	
	
	public UdpReceiveDemo02(int port,String fromName) {
		this.port = port;
		this.fromName = fromName;
		
		try {
            //创建socket
			socket = new DatagramSocket(port);
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	@Override
	public void run() {
		
		while(true){
			//接收数据包
			byte[] buffer = new byte[1024];
			DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
			
			try {
                //堵塞接收数据包
				socket.receive(packet);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
				    
			byte[] data = packet.getData();
			String receiveData = new String(data,0,data.length);
				    
			//打印数据包的内容
			System.out.println(fromName+"说 : "+receiveData);
					
			if("quit".equals(receiveData.substring(0, 4))){
				break;
			}
		}
		//关闭流
		socket.close();
		
	}

}

学生端主方法:

public class UdpStudent {
	public static void main(String[] args) {
		
		new Thread(new UdpSendDemo02(9999, "localhost", 8888)).start();
		new Thread(new UdpReceiveDemo02(7777,"老师")).start();
		
	}
}

老师端主方法:

public class UdpTeacher {
	public static void main(String[] args) {
        
		new Thread(new UdpSendDemo02(6666, "localhost", 7777)).start();
		new Thread(new UdpReceiveDemo02(8888,"学生")).start();	
	
    }
}

1.8、URL

统一资源定位符:定位资源的,定位互联网上的某一个资源

DNS域名解析 www.baidu.com = xxx.xxx.xxx.xxx

协议://ip地址:端口号/项目名/资源

1.8.1、下载资源

在指定URL地址上下载资源:

import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class UrlDown {
	public static void main(String[] args) throws Exception {
		
		//1.下载地址
        //"http://localhost:8080/liuxuefeng/SecurityFile.txt"为tomcat的文件资源地址
		URL url = new URL("http://localhost:8080/liuxuefeng/SecurityFile.txt");
		
		//2.连接到这个资源 HTTP
		HttpURLConnection con =  (HttpURLConnection) url.openConnection();
		
		InputStream is =  con.getInputStream();
		
        //"C:\\Users\\Administrator\\Desktop\\SecurityFile.txt"为文件下载的地址
		FileOutputStream fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\SecurityFile.txt");
		
		//3.下载文件资源
		byte[] buffer = new byte[1024];
		int len;
		while((len = is.read(buffer)) != -1) {
			fos.write(buffer,0,len);
		}
		
		fos.close();
		is.close();
		con.disconnect();
	}

}

2、多线程

2.1、线程简介

普通方法调用:只有主线程一条执行路径。

多线程方法调用:多条执行路径,主线程和子线程并行交替执行。

程序、进程、线程的关系:在操作系统中运行的程序就是进程,一个进程可以有多个线程。比如视频中的声音,图像,弹幕,等等。

程序:是指令和数据的有序集合,本身没有任何运行的含义,是一个静态的概念。

进程:进程就是执行程序的一次执行过程,它是一个动态的开年。是系统资源分配的单位。

线程:在进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是cpu调度和执行的单位。

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

2.2、线程实现(重点)

2.2.1、Thread(类)

继承Thread类(重点)

不建议使用:避免OOP单继承局限性

步骤:

  • 创建一个类继承Thread类

  • 重写run()方法

  • 调用start开启线程

2.2.1.1、Thread使用例子

//创建线程方式一:继承Thread类,从写run()方法,调用start开启线程
//总结:注意线程开启不一定立即执行,由cpu调度执行。

public class ThreadDemo01 extends Thread{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 2000; i < 4000; i++) {
			System.out.println("我在看代码第"+i+"行!!!");
			
		}
	}
	
	public static void main(String[] args) {
		
		//创建一个线程对象
		ThreadDemo01 test = new ThreadDemo01();
		
		//调用start()方法开启线程
		test.start();
		
		
		for(int i = 0; i < 2000; i++) {
			System.out.println("我在学习多线程第"+i+"行!!!");
			
		}
		
	}
}

执行结果:两个循环一起执行。

2.2.1.2、下载图片

import java.io.File;
import java.net.URL;

import org.apache.commons.io.FileUtils;

public class ThreadDemo02 extends Thread{
	
	private String url;
	
	private String name;

	public ThreadDemo02(String url, String name) {
		this.url = url;
		this.name = name;
	}
	
	@Override
	public void run() {
		
		WebDownloader down = new WebDownloader();
		down.download(url, name);
		System.out.println("下载了文件"+name+"!!!");
		
	}
	
	public static void main(String[] args) {
		
		ThreadDemo02 d1 = new ThreadDemo02("http://localhost:8080/liuxuefeng/result.png", "result.png");
		ThreadDemo02 d2 = new ThreadDemo02("http://localhost:8080/liuxuefeng/zhidao@2x-e9b427ecc4.png", "4.png");
		ThreadDemo02 d3 = new ThreadDemo02("http://localhost:8080/liuxuefeng/tupian@2x-482fc011fc.png", "c.png");
		
		d1.start();
		d2.start();
		d3.start();
		
	}
	
}

class WebDownloader{
	
	public WebDownloader() {
	}
	//下载方法
	public void download(String url,String name) {
		// TODO Auto-generated method stub
		try {
			FileUtils.copyURLToFile(new URL(url), new File(name));
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("===============IO异常,download方法出现问题==============");
		}
	}
}

2.2.2、Runnable(接口)

实现Runnable接口(重点)

推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

步骤:

  • 创建一个类实现Runnable接口

  • 实现run()方法,编写线程执行体

  • 创建线程对象,调用start开启线程

2.2.2.1、runnable使用例子

//创建线程方法2:实现Runnable接口,重写run()方法,执行线程需要丢入runable接口实现类,调用start方法。
public class RunnableDemo01 implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		for(int i = 2000; i < 4000; i++) {
			System.out.println("我在看代码第"+i+"行!!!");
			
		}
	}
	
	public static void main(String[] args) {
				
		 //创建runnable接口的实现类对象
//		 RunnableDemo01 runnableDemo01 = new RunnableDemo01();
		 
		 //创建线程对象,通过线程对象来开启我们的线程,代理
//		 Thread thread = new Thread(runnableDemo01);
		 
		 //开启线程
//		 thread.start();
        
		//缩写
		new Thread(new RunnableDemo01()).start();
		
		for(int i = 0; i < 2000; i++) {
			System.out.println("我在学习多线程第"+i+"行!!!");
					
		}
		
	}
}

执行结果:两个循环一起执行。

2.2.2.2、下载图片

import java.io.File;
import java.net.URL;

import org.apache.commons.io.FileUtils;

public class RunnableDemo02 implements Runnable{

	private String url;
	
	private String name;

	public RunnableDemo02(String url, String name) {
		this.url = url;
		this.name = name;
	}
	
	@Override
	public void run() {
		
		WebDownloader1 down = new WebDownloader1();
		down.download(url, name);
		System.out.println("下载了文件"+name+"!!!");
		
	}
	
	public static void main(String[] args) {
		
		RunnableDemo02 d1 = new RunnableDemo02("http://localhost:8080/liuxuefeng/result.png", "result1.png");
		RunnableDemo02 d2 = new RunnableDemo02("http://localhost:8080/liuxuefeng/zhidao@2x-e9b427ecc4.png", "41.png");
		RunnableDemo02 d3 = new RunnableDemo02("http://localhost:8080/liuxuefeng/tupian@2x-482fc011fc.png", "c1.png");
		
		new Thread(d1).start();
		new Thread(d2).start();
		new Thread(d3).start();
		
	}
	
}

class WebDownloader1{
	
	//下载方法
	public void download(String url,String name) {
		// TODO Auto-generated method stub
		try {
			FileUtils.copyURLToFile(new URL(url), new File(name));
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("===============IO异常,WebDownloader1方法出现问题==============");
		}
	}
}

2.2.2.3、买火车票

runnable的优点,多个线程可以同时操作同一个对象

发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱

//多个线程同时操作同一个对象
//买火车票的例子

//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱

public class RunnableDemo03 implements Runnable{
	
	private int ticketNum = 10;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		while(true) {
			
			if(ticketNum <= 0) {
				break;
			}
			
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + "拿到第"+ ticketNum-- + "票!");
		}
	}
	
	public static void main(String[] args) {
		
		RunnableDemo03 thread = new RunnableDemo03();
		
		new Thread(thread,"小明").start();
		new Thread(thread,"老师").start();
		new Thread(thread,"黄牛党").start();
		
	}
	
}

2.2.2.4、龟兔赛跑

public class RunnableDemo04 implements Runnable{

	private static String winner;
	
	@Override
	public void run() {
		
	
		for (int i = 0;i <= 100;i++) {
			
			if("兔子".equals(Thread.currentThread().getName()) && i%10==0) {
				
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
                
			}
		
			boolean b = gameOver(i);
			
			if(b) {
				break;
			}
			
			System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");

		}
	}
	
	private boolean gameOver(int steps) {
		
		if(winner != null && !"".equals(winner)) {
			return true;
		}{
			if(100 <= steps) {
				
				winner = Thread.currentThread().getName();
				System.out.println("winner is" + winner);
				return true;
				
			}
			
		}
		
		return false;
	}
	
	public static void main(String[] args) {
		
		RunnableDemo04 demo4 = new RunnableDemo04();
		
		new Thread(demo4,"兔子").start();
		new Thread(demo4,"乌龟").start();
		
	}
}

2.2.3、Callable(接口)

实现Callable接口(了解)

优点:

  1. 具有返回值

  2. 可以抛出异常

步骤:

  1. 实现Callable接口,需要返回值类型

  2. 重写call方法,需要抛出异常

  3. 创建目标对象

  4. 创建执行服务

ExecutorService ser = Executors.newFixedThreadPool(1);
  1. 提交执行

Future<Boolean> result1 = ser.submit(t1);
  1. 获取结果

boolean r1 = result1.get();
  1. 关闭服务

ser.shutdownNow();

2.2.3.1、下载图片

import java.io.File;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.io.FileUtils;

public class CallableDemo01 implements Callable<Boolean>{

	private String url;
	
	private String name;

	public CallableDemo01(String url, String name) {
		this.url = url;
		this.name = name;
	}
	
	@Override
	public Boolean call() {
		
		WebDownloader2 down = new WebDownloader2();
		down.download(url, name);
		System.out.println("下载了文件"+name+"!!!");
		
		return true;
		
	}
	
    //不显示警告
	@SuppressWarnings("unused")
	public static void main(String[] args) throws Exception {
		
		//创建目标对象
		CallableDemo01 d1 = new CallableDemo01("http://localhost:8080/liuxuefeng/result.png", "result2.png");
		CallableDemo01 d2 = new CallableDemo01("http://localhost:8080/liuxuefeng/zhidao@2x-e9b427ecc4.png", "42.png");
		CallableDemo01 d3 = new CallableDemo01("http://localhost:8080/liuxuefeng/tupian@2x-482fc011fc.png", "c2.png");
		
		//创建执行服务
		ExecutorService ser = Executors.newFixedThreadPool(1);
		
		//提交执行
		Future<Boolean> result1 = ser.submit(d1);
		Future<Boolean> result2 = ser.submit(d2);
		Future<Boolean> result3 = ser.submit(d3);
		
		//获取结果
		boolean r1 = result1.get();
		boolean r2 = result2.get();
		boolean r3 = result3.get();
		
		//关闭服务
		ser.shutdownNow();
		
	}
}
class WebDownloader2{
	
	//下载方法
	public void download(String url,String name) {
		// TODO Auto-generated method stub
		try {
			FileUtils.copyURLToFile(new URL(url), new File(name));
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("===============IO异常,download方法出现问题==============");
		}
	}
}

2.2.4、静态代理

代理:就是让其他对象帮忙完成调用

总结:

  • 真实对象和代理对象都要实现同一个接口

  • 代理对象要代理真实角色

好处:

  • 代理对象可以做很多真实对象做不了的问题

  • 真实对象专注做自己的事情

2.2.4.1、结婚例子

//静态代理模式总结:
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色

//好处:
//代理对象可以做很多真实对象做不了的问题
//真实对象专注做自己的事情

public class ProxyDemo01 {
	
	public static void main(String[] args) {
		
		new WeddingCompany(new You()).HappyMarry();
		
	}

}

//结婚接口
interface Marry {
	
	void HappyMarry();
	
}

//自己类,自己结婚
class You implements Marry{
	
	@Override
	public void HappyMarry() {
		// TODO Auto-generated method stub
		System.out.println("要结婚了,超级开心!!!");
	}
	
}

//代理公司,婚庆公司类,帮助自己完成结婚这个事情
class WeddingCompany implements Marry{
	
	private Marry marry;//真实角色
	
	public WeddingCompany(Marry marry) {
		this.marry = marry;
	}

	@Override
	public void HappyMarry() {
		before();
		this.marry.HappyMarry();
		after();
	}
	
	private void before() {
		System.out.println("结婚之前,布置现场!!!");
	}
	
	private void after() {
		System.out.println("结婚之后,收取尾款!!!");
	}
	
}

2.3、线程状态

2.3.1、五大状态

  • 创建状态:

Thread t = new Thread();

线程对象一旦创建就进入到了新生状态。

  • 就绪状态:

    当调用start()方法,线程立即进入到就绪状态,但不意味着立即调度执行

  • 运行状态:

    进入运行状态,线程才真正执行线程体的代码块。

  • 阻塞状态:

    当调用sleep,wait或同步锁定时,线程进入到阻塞状态。

    就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。

  • 死亡状态:

    线程中断或者结束,一旦进入死亡状态,就不能再次启动。

2.3.2、线程方法

方法说明
setPriority(int newPriority)更改线程的优先级
static void sleep(Long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他线程
void interrupt()中断线程(别用这个方式)
boolean isAlive()测试线程是否处于活动状态

2.3.3、停止线程

  • 不推荐使用JDK提供的stop()、destory()方法。【已废弃】

  • 推荐线程自己停止下来

  • 建议使用一个标志位进行终止变量。当flag = false,则终止线程运行。

2.3.3.1、标识位停止线程例子

public class StopDemo01 implements Runnable{
	
	//1.线程中定义线程体使用的标识
    private boolean flag = true;
    
	@Override
	public void run() {
        
		int i = 0;
		
		//2.线程体使用该标识	
        while(flag){	
            
        	System.out.println("谢谢!!!--》"+i++);
        }  
	}
	
    //3.对外提供方法改变标识
    public void stop(){
        
        this.flag = false;
        
    }
    
    public static void main(String[] args) throws Exception {
		
    	StopDemo01 d1 = new StopDemo01();
    	
    	new Thread(d1).start();
    	
    	//方法一:使用sleep控制结束时间
    	Thread.sleep(1);
    	d1.stop();
    	System.out.println("线程停止了!!!");
    	
    	//方法二:使用循环控制结束时间
//    	for (int i = 0; i < 100000; i++) {
//			if(i == 99999) {
//				//调用stop方法切换标志位,让线程停止
//				d1.stop();
//				System.out.println("线程停止了!!!");
//			}
//		}
    	
	}

}

执行结果:

2.3.4、线程休眠

概述:

  • sleep(时间)指定当前线程阻塞的毫秒值

  • sleep存在异常IneterruptedExcption

  • sleep时间达到后线程进入到就绪状态

  • sleep可以模拟网络延时,倒计时等。

  • 每一个对象都有一个锁,sleep不会释放锁

2.3.4.1、模拟网络延时

作用:

  • 放大问题的发生性。

//模拟网络延时
public class SleepDemo01 implements Runnable{

	private int ticketNum = 10;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		while(true) {
			
			if(ticketNum <= 0) {
				break;
			}
			
			//模拟延时
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + "拿到第"+ ticketNum-- + "票!");
		}
	}
	
	public static void main(String[] args) {
		
		SleepDemo01 thread = new SleepDemo01();
		
		new Thread(thread,"小明").start();
		new Thread(thread,"老师").start();
		new Thread(thread,"黄牛党").start();
		
	}
	
}

2.3.4.2、模拟倒计时

import java.text.SimpleDateFormat;
import java.util.Date;

//模拟倒计时。。。
public class SleepDemo02 {

	public static void main(String[] args) throws Exception {
		
		int num = 10;
		
		tenDown();
		
		while(num > 0) {
			Thread.sleep(1000);
			
			//获取系统当前时间
			Date startTime = new Date(System.currentTimeMillis());
			
			System.out.println(new SimpleDateFormat("yyyy:MM:dd:HH:mm:ss").format(startTime));
			
			num--;
			
		}
		
	}

	//模拟倒计时
	private static void tenDown() throws InterruptedException {
		
		int num = 10;
		
		while(true) {
			
			Thread.sleep(1000);
			
			System.out.println(num--);
			
			if(num <= 0) {
				
				break;
				
			}
			
		}
	}
}

2.3.5、线程礼让

概述:

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞

  • 将线程从运行状态转为就绪状态

  • 让cpu重新调度,礼让不一定成功!看cpu心情

//测试礼让线程
//礼让不一定成功,看cpu心情
public class YieldDemo01 implements Runnable{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		System.out.println(Thread.currentThread().getName()+"线程开始执行!!!");
		Thread.yield(); //礼让 
		System.out.println(Thread.currentThread().getName()+"线程结束执行!!!");
		
	}

	public static void main(String[] args) {
		
//		MyYield my = new MyYield();
		YieldDemo01 my1 = new YieldDemo01();
		
//		new Thread(my,"a").start();
//		new Thread(my,"b").start();
		new Thread(my1,"a").start();
		new Thread(my1,"b").start();
		
	}
	
}

class MyYield implements Runnable{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		System.out.println(Thread.currentThread().getName()+"线程开始执行!!!");
		Thread.yield(); //礼让 
		System.out.println(Thread.currentThread().getName()+"线程结束执行!!!");
		
	}
	
}

2.3.6、Join

概述:

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

  • 可以想象成插队

  • 等待该线程执行完毕

public class JoinDemo01 implements Runnable{
	
	public static void main(String[] args) throws InterruptedException {
		
		Thread thread = new Thread(new JoinDemo01());
		thread.start();
		
		for (int i = 1; i <= 10; i++) {
			
			System.out.println("main in -->" + i);
			
			if(i == 5) {
				
				thread.join(); //等待该线程执行完毕
			
			}
		}
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 1; i <= 100; i++) {
			System.out.println("join in -->" + i);
		}
		
	}

}

2.3.7、观测线程状态

Thread.state

线程状态。线程可以处于以下状态之一:

  • New

    尚未启动的线程处于此状态

  • Runnable

    在java虚拟机中执行的线程处于此状态

  • Blocked

    被阻塞等待监视器锁定的线程处于此状态

  • Waiting

    正在等待另一个线程执行特定动作的线程处于此状态

  • Timed_Waiting

    正在等待另一个线程执行动作到达指定等待时间的线程处于此状态

  • Terminated

    已退出的线程处于此状态

2.3.7.1、观测线程状态

public class StateDemo01 {

	public static void main(String[] args) throws InterruptedException {
		
		Thread thread = new Thread(()->{
			for (int i = 1; i <= 5; i++) {
				
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				System.out.println("main is -->"+i);
			}
		});
		
		//观察状态
		Thread.State state = thread.getState();
		System.out.println(state);
		
		//观察启动
		thread.start();//启动线程
		state = thread.getState();
		System.out.println(state);
		
		while(state != Thread.State.TERMINATED) {//只要线程不停止,就一直输出状态
			Thread.sleep(100);
			state = thread.getState();
			System.out.println(state);
		}
	}
	
}

2.3.8、线程优先级

概念:

  • java提供一个线程调度器来监视程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

  • 线程的优先级用数字标识,范围从1~10

    • Thread.MIN_PRIORITY = 1;
    • Thread.MAN_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用已下方式改变或获取优先级

    • Thread.currentThread().getPriority()//获取线程的优先级
    • Thread.setPriority(int xxx); //设置线程的优先级
  • 优先级的设定建议在statr()调度前

  • 优先级低只是意味着获得调度的概率低,并不是优先级低就不会先被调用了,这都是看CPU的调度

2.3.8.1、线程优先级例子

public class PriorityDemo01{
	
	public static void main(String[] args) {
		
		//默认优先级
		System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
		
		MyPriority myPriority = new MyPriority();
		
		Thread t1 = new Thread(myPriority);
		Thread t2 = new Thread(myPriority);
		Thread t3 = new Thread(myPriority);
		Thread t4 = new Thread(myPriority);
		Thread t5 = new Thread(myPriority);
		Thread t6 = new Thread(myPriority);
		Thread t7 = new Thread(myPriority);
		Thread t8 = new Thread(myPriority);
		
		//先设置优先级,在启动
		
		t1.setPriority(1);
		t1.start();
		
		t2.setPriority(Thread.MAX_PRIORITY);
		t2.start();
		
		t3.setPriority(8);
		t3.start();
		
		t4.setPriority(6);
		t4.start();
		
		t5.setPriority(3);
		t5.start();
		
		t6.setPriority(4);
		t6.start();

		//优先级太低		
//		t7.setPriority(-1);
//		t7.start();

		//优先级太高
//		t8.setPriority(11);
//		t8.start();
		
	}
	
}

class MyPriority implements Runnable {
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
		
	}
	
}

2.3.9、守护(daemon)线程

概述:

  • 线程分为用户线程和守护线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

  • 如,后台记录操作日志,监控内存,垃圾回收等待都是守护线程

Thread.setDaemon(true)//默认是false表示是用户线程,正常的线程都是用户线程。。。

2.3.9.1、守护线程的例子

//测试守护线程
//上帝守护你
public class DaemonDemo01 {

	public static void main(String[] args) {
		
		God god = new God();
		You you = new You();
		
		Thread threadGod = new Thread(god);
		threadGod.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程。。。
		
		threadGod.start();//上帝守护线程启动
		
		new Thread(new You()).start();
		
	}
	
}

class God implements Runnable{
	
	@Override
	public void run() {
		
		while(true) {
			System.out.println("上帝保佑着你!!!");
		}
	}
}

//自己
class You implements Runnable{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		for (int i = 1; i <= 36500; i++) {
			
			System.out.println("你开心的活着!!!-->" + i + "天");
			
		}
		
		System.out.println("=========goodbye! world!=========");
		
	}
	
}

2.4、线程同步(重点)

多个线程操作同一个资源,存在风险。也就是并发。

并发:同一个对象被多个线程同时操作

线程同步:处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列。等待前面线程使用完毕,下一个线程在使用。

队列和锁:线程同步形成条件 = 队列 + 锁,每一个对象都有一把锁。为了确保安全性。

线程同步概述

  • 由于同一个进行的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制(synchronized),当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起

    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题

    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

2.4.1、不安全案例

2.4.1.1、案例一(买票)

public class UnsafeBuyTicket {

	public static void main(String[] args) {
		
		BuyTicket b = new BuyTicket();
		
		Thread t1 = new Thread(b,"我");
		Thread t2 = new Thread(b,"你");
		Thread t3 = new Thread(b,"黄牛党");
		
		t1.start();
		t2.start();
		t3.start();
				
		
	}
	
}

class BuyTicket implements Runnable{
	
	//票
	private int ticketNums = 10;
	
	//停止标志位
	private boolean flag = true;
	
	@Override
	public void run() {
		
		//买票 
		while (flag) {
			
			buy();
			
		}
		
	}
	
	private void buy() {
		
		// 判断是否有票
		if(ticketNums <= 0) {
			flag = false;
			return;
		}
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//买票
		System.out.println(Thread.currentThread().getName() + "拿到第" + ticketNums-- + "票!!!");
		
	}
	
}

2.4.1.2、案例二(取款)

public class UnsafeBank {
	
	public static void main(String[] args) {
		
		Account account = new Account(100,"装修基金");
		
		Bank you = new Bank(account, 50);
		Bank girl = new Bank(account, 100);
		
		new Thread(you,"自己").start();
		new Thread(girl,"媳妇").start();
		
	}

}

class Account{
	
	int money; //余额
	
	String name; //卡名
	
	public Account(int money, String name) {
		this.money = money;
		this.name = name;
	}
	
}

//银行:模拟取款
class Bank implements Runnable{
	
	Account account; //账户
	
	int drawingMoney; //取了多少钱
	
	int nowMoney; //现在手里有多少钱
	
	public Bank(Account account, int drawingMoney) {
		this.account = account;
		this.drawingMoney = drawingMoney;
	}

	@Override
	public void run() {
		//判断有没有钱
		if(account.money < drawingMoney) {
			System.out.println(account.name + "账户余额不足!!!");
			return;
		}
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//取完之后的余额
		account.money = account.money - drawingMoney;
		
		//现在手里的钱
		nowMoney = nowMoney + drawingMoney;
		
		System.out.println("账户余额为:" + account.money + "------" + Thread.currentThread().getName() + "手里的金额为:" + nowMoney);
		
	}
	
}

2.4.1.3、案例三(集合)

可能出现多个线程往集合中添加数据时,几个数据放在了同一个位置

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class UnsafeList {

	public static void main(String[] args) {
		
		List<String> list = new ArrayList<String>();
		
		for (int i = 0; i < 100; i++) {
			
			
			
			new Thread(() -> {
				
				list.add(Thread.currentThread().getName());
				
			}).start();
			
		}
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(list.size()); 
	}
	
}

执行结果:

2.4.2、同步方法

synchronized关键字:

  • 由于我们可以通过private关键字来保证对象只能被本类中的方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种方法:synchronized方法和synchronized块

    //同步方法
    public synchronized void method(int args){}
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

    • 缺陷:若将一个大的方法申明为synchronized将会影响效率

同步方法的弊端

  • 方法里面需要修改的内容才需要锁,锁太多,浪费资源

同步块

  • 同步块:synchronized(obj){}

  • Obj称之为同步监视器

    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器

    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class【反射中讲解】

  • 同步监视器的执行过程:

    • 第一个线程访问,锁定同步监视器,执行其中代码。

    • 第二个线程访问,发现同步监视器被锁定,无法访问。

    • 第一个线程访问完毕,解锁同步监视器。

    • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

  • synchronized锁的就是变化的量,哪个对象内部的内容发生增删改了,就锁哪个对象。就是锁共享的资源。

  • synchronized方法其实就相当与synchronized (this){}

2.4.2.1、案例一(安全买票)

public class SafeBuyTicket {

	public static void main(String[] args) {
		
		BuyTicketSafe b = new BuyTicketSafe();
		
		Thread t1 = new Thread(b,"我");
		Thread t2 = new Thread(b,"你");
		Thread t3 = new Thread(b,"黄牛党");
		
		t1.start();
		t2.start();
		t3.start();
			
	}
	
}

class BuyTicketSafe implements Runnable{
	
	//票
	private int ticketNums = 10;
	
	//停止标志位
	private boolean flag = true;
	
	@Override
	public  void run() {
		
		//买票 
		while (flag) {
			
			buy();
			
		}
		
	}
	
	//synchronized 同步方法,锁的是this
    //相当于在run的内部,synchronized (this){}
	private synchronized void buy() {
		
		// 判断是否有票
		if(ticketNums <= 0) {
			flag = false;
			return;
		}
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//买票
		System.out.println(Thread.currentThread().getName() + "拿到第" + ticketNums-- + "票!!!");
		
	}
	
}

2.4.2.2、案例二(安全取款)

public class SafeBank {
	
	public static void main(String[] args) {
		
		AccountSafe account = new AccountSafe(10000,"装修基金");
		
		BankSafe you = new BankSafe(account, 50);
		BankSafe girl = new BankSafe(account, 100);
		
		new Thread(you,"自己").start();
		new Thread(girl,"媳妇").start();
		
	}

}

class AccountSafe{
	
	int money; //余额
	
	String name; //卡名
	
	public AccountSafe(int money, String name) {
		this.money = money;
		this.name = name;
	}
	
}

//银行:模拟取款
class BankSafe implements Runnable{
	
	AccountSafe account; //账户
	
	int drawingMoney; //取了多少钱
	
	int nowMoney; //现在手里有多少钱
	
	public BankSafe(AccountSafe account, int drawingMoney) {
		this.account = account;
		this.drawingMoney = drawingMoney;
	}

	@Override
	public void run() {
		
		//锁的对象就是变化的量,需要增删改的对象
		synchronized (account) {
			//判断有没有钱
			if(account.money < drawingMoney) {
				System.out.println(account.name + "账户余额不足!!!");
				return;
			}
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			//取完之后的余额
			account.money = account.money - drawingMoney;
			
			//现在手里的钱
			nowMoney = nowMoney + drawingMoney;
			
			System.out.println("账户余额为:" + account.money + "------" + Thread.currentThread().getName() + "手里的金额为:" + nowMoney);
			
		}
		
	}
	
}

2.4.2.3、案例三(安全集合)

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class SafeList {

	public static void main(String[] args) {
		
		List<String> list = new ArrayList<String>();
		
		for (int i = 0; i < 100000; i++) {
			
			new Thread(() -> {
				
				synchronized (list) {
					list.add(Thread.currentThread().getName());
				}
				
			}).start();
			
		}
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(list.size()); 
	}
	
}

2.4.3、JUC

JUC:是并发的包,是java.util.concurrent的缩写。

CopyOnWriteArrayList:是安全的集合,相当于加锁之后的集合。synchronized(list){};

2.4.3.1、安全集合例子

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class JucDemo01 {

	public static void main(String[] args) {
		
		CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
		
		for (int i = 0; i < 10000; i++) {
			
			new Thread(() -> {
				
				list.add(Thread.currentThread().getName());
				
			}) .start();
			
		}
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(list.size());
		
	}
	
}

2.4.4、死锁

概述:

  • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

死锁避免方法

产生死锁的四个必要条件:

  • 互斥条件:一个条件每次只能被一个进程使用。

  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

2.4.4.1、死锁例子

看看出现死锁是什么样子的,每个人获得一个对象的锁,程序僵持住了无法正常结束。

持有且等待是产生死锁的条件。想要不产生死锁,就需要去释放持有的。

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
//程序的意思是:拿着口红用镜子,拿着镜子用口红
public class LockDemo01 {

	public static void main(String[] args) {
		
		MakeUp g1 = new MakeUp(0, "沈月");
		MakeUp g2 = new MakeUp(1, "鞠婧祎");
		
		g1.start();
		g2.start();
		
	}
	
}

//口红
class Lipstick{
	
}

//镜子
class Mirror{
	
}

class MakeUp extends Thread{
	
	static Lipstick lipstick = new Lipstick();
	static Mirror mirror = new Mirror();
	
	int choice; //选择的顺序
	String name; //女孩的名字
	
	
	public MakeUp(int choice, String name) {
		this.choice = choice;
		this.name = name;
	}

	@Override
	public void run() {
		
		try {
			makeup();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	private void makeup() throws InterruptedException {
		
		if(choice == 0) {
			
			synchronized (lipstick) {
                
				System.out.println(this.name + "获得了口红的锁!!!");
				
                Thread.sleep(1000);
				
                synchronized (mirror) {
				
                    System.out.println(this.name + "获得了镜子的锁!!!");
				
                }
			}
		}else {
			
			synchronized (mirror) {
				System.out.println(this.name + "获得了镜子的锁!!!");
				Thread.sleep(2000);
				synchronized (lipstick) {
					System.out.println(this.name + "获得了口红的锁!!!");
				}
			}	
		}	
	}
}

2.4.4.2、死锁例子解决例子

//解决死锁问题
//现在程序的意思是:拿着口红,释放口红再去拿镜子。拿着镜子,释放镜子再去拿口红
public class LockDemo01 {

	public static void main(String[] args) {
		
		MakeUp g1 = new MakeUp(0, "沈月");
		MakeUp g2 = new MakeUp(1, "鞠婧祎");
		
		g1.start();
		g2.start();
		
	}
	
}

//口红
class Lipstick{
	
}

//镜子
class Mirror{
	
}

class MakeUp extends Thread{
	
	static Lipstick lipstick = new Lipstick();
	static Mirror mirror = new Mirror();
	
	int choice; //选择的顺序
	String name; //女孩的名字
	
	
	public MakeUp(int choice, String name) {
		this.choice = choice;
		this.name = name;
	}

	@Override
	public void run() {
		
		try {
			makeup();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	private void makeup() throws InterruptedException {
		
		if(choice == 0) {
			
			synchronized (lipstick) {
				System.out.println(this.name + "获得了口红的锁!!!");
				Thread.sleep(1000);
				
			}
			
			synchronized (mirror) {
				System.out.println(this.name + "获得了镜子的锁!!!");
			}
			
		}else {
			
			synchronized (mirror) {
				System.out.println(this.name + "获得了镜子的锁!!!");
				Thread.sleep(2000);
				
			}
			
			synchronized (lipstick) {
				System.out.println(this.name + "获得了口红的锁!!!");
			}
			
		}
		
	}
	
}

2.4.5、Lock(锁)

概述:

  • 从JDK5.0开始,java提供了更强大的线程同步机制----通过显示定义同步锁对想来实现同步。同步锁使用Lock对象充当

  • java.util.concurrent.locks.Lock接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象

  • ReenteantLock类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

  • 也可以称之为可重入锁

synchronized与Lock的对比

  • Lock是显示锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁也有方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

  • 优先使用顺序

    • Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

2.4.5.1、Lock锁使用例子

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class LockDemo01 {

	public static void main(String[] args) {
		
		TestLock testLock = new TestLock();
		
		new Thread(testLock,"我").start();
		new Thread(testLock,"你").start();
		new Thread(testLock,"黄牛党").start();
		
	}
	
}

class TestLock implements Runnable{
	
	int ticketNums = 10;
	
	//定义Lock锁
	
	private final ReentrantLock lock = new ReentrantLock();
	
	@Override
	public void run() {
		
		while(true) {
			
			lock.lock(); //加锁
			
			try {
				
				if(this.ticketNums <= 0) {
					
					break;
					
				}
				
				try {
					
					Thread.sleep(100);
				
				} catch (InterruptedException e) {
				
					e.printStackTrace();
				
				}
				
				System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票!!!");
				
			} catch (Exception e) {
				
				e.printStackTrace();
			
			}finally {
				
				lock.unlock(); //解锁
				
			}
			
		}
		
	}
	
}

2.5、线程通信问题

生产者消费者模式

  • 这个模式是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件

  • 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费

  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费

  • 在生产者消费者问题中,仅有synchronized是不够的

    • synchronized可阻止并发更新同一个共享资源,实现了同步

    • synchronized不能用来实现不同线程之间的消息传递(通信)

java提供了几个方法解决线程之间的通信问题

方法名作用
wait()表示线程一直等待,知道其他线程通知,与sleep不同,会释放锁
wait(Long timeout)指定等待的毫秒数
notify()唤醒一个处于等待状态的线程
notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

(注意:均是Object类的方法,都只能在同步方法或者同步代码块种使用,否则会抛出异常IIIegalMonitorStateException)

解决方法

一、并发协作模型“生产者/消费者模式” --> 管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程);

  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程);

  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”;

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

二、并发协作模型“生产者/消费者模式” --> 信号灯法

通过标志位来解决。

2.5.1、管程法

解决生产者-消费者问题

//测试生产者消费者模型---利用缓冲区方法解决---方法名字:管程法

//需要的对象:生产者、消费者、产品、缓冲区
public class TestPC {
	
	public static void main(String[] args) {
	
		SynContainer container = new SynContainer();
		
		new Productor(container).start();
		new Consumer(container).start();
		
	}
}

class Productor extends Thread{
	
	SynContainer container;

	public Productor(SynContainer container) {
		this.container = container;
	}
	
	@Override
	public void run() {
		
		for (int i = 1; i <= 100; i++) {
			
			container.push(new Chicken(i));
			
			
			
		}	
	}
}

class Consumer extends Thread{
	SynContainer container;

	public Consumer(SynContainer container) {
		this.container = container;
	}
	
	@Override
	public void run() {
		
		for (int i = 0; i < 100; i++) {
			
			container.pop();
			
		}
		
	}
	

}

class Chicken{
	
	int id; //产品编号

	public Chicken(int id) {
		this.id = id;
	}
	
}

class SynContainer{
	
	//需要一个容器大小
	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++;
		System.out.println("生产了第"+chicken.id+"只鸡!!!");
		
		//通知消费者可以消费了
		this.notifyAll();
		
	}
	
	//消费者消费产品
	public synchronized void pop() {
		
		//判断是否能消费
		if(count == 0) {
			
			//消费者等待,生产者生产
			try {
				this.wait();
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
			
		}
		
		//如果可以消费
		count--;
		Chicken chicken = chickens[count];
		System.out.println("消费了-->第" + chicken.id + "只鸡");
		
		//吃完了,通知生产者生产
		this.notifyAll();

	}
	
}

2.5.2、信号灯法

使用标志位来控制线程的等待和唤醒

//测试生产者消费者问题2:信号灯法,标志位解决
public class TestPC2 {

	public static void main(String[] args) {
		
		TV tv = new TV();
		new Player(tv).start();
		new Watcher(tv).start();
		
	}
	
}

//生产者--演员
class Player extends Thread{
	TV tv = new TV();
	
	public Player(TV tv) {
	
		this.tv = tv;
	}

	@Override
	public void run() {
	
		
		for (int i = 0; i < 20; i++) {
			
			if(i%2==0) {
				
//				System.out.println("王牌对王牌播放中!!!");
				tv.play("王牌对王牌!!!");
				
			}{
				tv.play("抖音记录美好生活!!!");
//				System.out.println("抖音记录美好生活播放中!!!");
			}
			
		}
		
	}
	
}

//消费者--观众
class Watcher extends Thread{
	
	TV tv = new TV();
	
	public Watcher(TV tv) {
	
		this.tv = tv;
	}
	
	@Override
	public void run() {
		
		for (int i = 0; i < 20; i++) {
				
				tv.Watch();
				
		}
		
	}
	
}

//产品--节目
class TV{
	
	//演员表演,观众等待   T
	//观众观看,演员等待   F
	String voice; //表演的节目
	boolean flag = true;
	
	//表演
	public synchronized void play(String voice) {
		// TODO Auto-generated method stub
		if(!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("演员表演了--》" + voice);
		
		this.voice = voice;
		this.flag = !this.flag;
		
		this.notifyAll();
		
	}
	
	//观看
	public synchronized void Watch() {
		// TODO Auto-generated method stub
		if(this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		
		System.out.println("观众观看了--》" + this.voice);
		
		this.flag = !this.flag;
		
		this.notifyAll();
		
		
	}
	
}

2.5.3、线程池

背景:

  • 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能能影响很大。

思路:

  • 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。

  • 可以避免平凡创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:

  • 提高响应速度(减少了创建新线程的时间)

  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

  • 便于线程管理(。。。)

    • corePoolSize:核心池的大小

    • maximumPoolSize:最大线程数

    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

使用线程池:

  • JDK5.0起提供了线程池想过的API:ExecutorService和Excutors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable

    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable

    • void shutdown():关闭连接池

  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 12:25:58  更:2021-08-07 12:26:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 12:55:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计