计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大,功能强的网络系统,从而使得
众多的计算机可以方便地互相传递消息,共享硬件,软件和数据信息等资源
网络编程的目的
直接或间接的通过网络协议与其他计算机实现数据交换,进行通讯。
两个主要的问题
如何准确的定位网络上一台或多台主机,定位主机上的特定的应用
通信的双方地址 IP 端口号
一定的规则(网络通信协议)
找到主机后如何高效的进行数据传输
网络通信协议 TCP / IP
通信要素一 IP地址和端口号
IP地址: InetAddress
计算机唯一的标识,通信实体。
本地回环地址,hostAddress
主机名hostName,locallhost
IP地址的方式
分类方式一,IPV4和IPV6
IPV4:4个字节组成,4个0-255,大概42亿,30亿在北美,亚洲4亿,2011年初已经用尽,以点分十进制表示
IPV6:123位16个字节,写成8个无符号整数,每个整数用16进制表示,数之间用冒号分开,
分类方式二,公网地址即万维网和私有地址即局域网
域名:www.baidu.com
本地回路地址,对应着localhost
如何实例化InetAddress 两个方法,getByName getLocalHost
端口号
端口号标识的是计算机上正在运行的(进程)程序。
不同进程有不同的端口号——被规定为一个16位的整数。
端口分类——公认端口0-1023,注册端口1024-49151,动态私有端口49152-65535
如何获取本地的IP地址和localhost值
public static void main(String[] args) {
//
try {
InetAddress inet1 = InetAddress.getByName("192.168.43.113");
System.out.println(inet1);///192.168.43.113
//
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
System.out.println(inet2);//www.baidu.com/180.101.49.11解析当前域名对应的IP地址的值是多少
//
//获取本地IP
InetAddress inet3 = InetAddress.getLocalHost();//获取本机的localhost主机名
System.out.println(inet3);//BF-202006292035/192.168.43.113会将结果解析为对应的IP地址
//
System.out.println(inet3.getHostAddress());//本地IP地址值192.168.43.113
System.out.println(inet3.getHostName());//本地local host值BF-202006292035
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//
}
Socket
端口号和IP地址的组合得到的一个网络套接字。网络编程也叫Socket编程。
通信要素二 网络协议如何进行网络传输 网络规则
网络通信协议 计算机网络中实现通信必须有一些约定,即通信协议,对速率,传输代码,代码结构,传输
控制步骤,出错步骤等制定标准。
问题是网络协议太复杂,通信协议分层的思想来解决该问题。
TCP/IP协议
传输层协议中有两个非常重要的协议:
TCP 传输控制协议——三次握手,建立链接,保证可靠;进行大数据量的传输;大数据量的传输;四次挥手,释放建立的传输,效率低;;类似于打电话。客户端和服务器都可以发起挥手操作。
IP 网络互联协议
UDP 用户数据报协议——将数据、源、目的封装成数据包,不需要建立连接,不可靠;每个数据报的数据限制在64K;可以广播发送;无需释放资源,开销小,速度快。类似于发短信。
package com.hst.thread;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import org.junit.jupiter.api.Test;
import org.omg.CORBA_2_3.portable.OutputStream;
/**
* 网络编程---基于TCP
* 1例子 客户端 发送一句话给服务端
* @author Administrator
*先启动服务器再启动客户端
*/
public class TCPTest {
//
//客户端
@Test
public void client() {
//
InetAddress inet;
OutputStream os=null;
Socket socket=null;
try {
inet = InetAddress.getByName("192.168.43.113");
//
socket = new Socket(inet,8899);//创建一个流套接字并将其与指定的IP地址中的指定端口号连接起来。
//
//传输数据
os = (OutputStream) socket.getOutputStream();
os.write("您好,我是客户端".getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {//获取对方的服务器主机名
if(os!=null) {
try {
//关闭资源
//
os.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(socket!=null) {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//
//
}
//
//服务端
@Test
public void server(){
//接收端口的信号
ServerSocket ss=null;
ByteArrayOutputStream baos =null;
InputStream is = null;
Socket socket =null;
try {
ss = new ServerSocket(8899);
socket = ss.accept();
//接收数据
is = socket.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len ;
while((len=is.read(buffer))!=-1) {
//System.out.println(new String(buffer,0,len));
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(baos!=null) {
try {
baos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(is!=null) {
try {
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(socket!=null) {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(ss!=null) {
try {
ss.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//
//从后往前关闭资源
}
//
/*过程中出现的错误Connection refused: connect
* java.net.ConnectException: Connection refused: connec
可执行程序。在某些情况下,已知可执行程序承载
多个独立的组件,这些情况下,
显示创建连接或侦听端口时
涉及的组件序列。在此情况下,可执行程序的
名称位于底部 [] 中,它调用的组件位于顶部,
直至达到 TCP/IP。注意,此选项
可能很耗时,并且在你没有足够
权限时可能失败。
-e 显示以太网统计信息。此选项可以与 -s 选项
结合使用。
-f 显示外部地址的完全限定
域名(FQDN)。
-n 以数字形式显示地址和端口号。
-o 显示拥有的与每个连接关联的进程 ID。
-p proto 显示 proto 指定的协议的连接;proto
可以是下列任何一个: TCP、UDP、TCPv6 或 UDPv6。如果与 -s
选项一起用来显示每个协议的统计信息,proto 可以是下列任何一个:
IP、IPv6、ICMP、ICMPv6、TCP、TCPv6、UDP 或 UDPv6。
-q 显示所有连接、侦听端口和绑定的
非侦听 TCP 端口。绑定的非侦听端口
不一定与活动连接相关联。
-r 显示路由表。
-s 显示每个协议的统计信息。默认情况下,
显示 IP、IPv6、ICMP、ICMPv6、TCP、TCPv6、UDP 和 UDPv6 的统计信息;
-p 选项可用于指定默认的子网。
-t 显示当前连接卸载状态。
-x 显示 NetworkDirect 连接、侦听器和共享
终结点。
-y 显示所有连接的 TCP 连接模板。
无法与其他选项结合使用。
interval 重新显示选定的统计信息,各个显示间暂停的
间隔秒数。按 CTRL+C 停止重新显示
统计信息。如果省略,则 netstat 将打印当前的
配置信息一次。
*/
/*
* 2找到被侵占的端口关闭它
*/
}
线程
//线程 进程分成的多个小任务
//进程 CPU执行的任务分成的小任务
在某个时刻,CPU 的某个核中只能处理一个进程。一个进程只能处理一个线程。
也就是用时间分割空间获取最大的效率,保证每个进程及线程都在进行,而使得任务在处理。
底层是依赖于线程的轮换会使得多个任务进行切换。
线程的工作职责
计算机的所有数据都是根据底层电子元件的状态进行的。
开启线程
方法一 继承线程类
package com.hst.thread;
/**
* 本包开始进行线程的相关知识点的介绍
* 本类用继承的方法实现线程的开启
* 继承与thrad类有关
* 0线程步骤
* 1.定义一个任务类继承线程类 设置任务的描述方法 重写 任务描述方法
* 2.创建一个任务类的对象
* 3.调用开启线程的方法 开启线程---调用任务方法 开辟新的线程 执行定义的任务方法
* @author Administrator
*
*/
public class ThreadDemo {
//
//
//面临的问题---创建对象调用开启线程的方法 但是 没有任务线---原线程中的类中不能直接调用方法设置任务
//创建一个新的类 继承线程类 解决上述问题
public static void main(String[] args) {//主方法是主线程 自己的线程是分线程
//创建线程任务对象
TDemo dg = new TDemo();
//调用开启线程的方法
//开启线程以后 可以使得 线程内的任务 同时和其他的任务开始抢占资源 --- 即资源的执行---不按顺序---按资源优先
//线程的开启类似于理解为---在一个房间内---同一天的时间---我们的任务从只看书新增加练字---看书练字在同一天的时间交替进行
//---值得注意的是---因为时间不可回溯---任务有进度---因此交替进行的任务只能交接进行---这个模式下 不断增加新的任务---就是进行新的
//---线程---资源即时间有限---空间有限的前提下---线程尽可能的完成不同的任务 当然这个过程是根据不同的任务优先级进行的
dg.start();//该方法是开启线程的方法 务必需要start
//
//
//主方法执行的内容在底层是由一个主线程执行的
for (int i = 10; i > 0; i--) {//不是线程中的循环体 验证线程
System.out.println("main\t"+i);
}
}
}
//我们要表示线程---描述线程中的任务---和线程类相关才可以进行描述
//该类用于继承线程类---定义线程中的任务
class TDemo extends Thread{
//为什么要继承该方法---当开启线程时---线程的任务就是该方法的内容
@Override
public void run() {//线程任务的定义和描述
//super.run();//运行父类中的方法
for (int i = 0; i <10; i++) {
System.out.println("TDemo\t"+i);
}
//让线程休眠
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
方法二 实现线程接口
package com.hst.thread;
/**
* 本类用实现的方法开启线程
* 实现与runnable有关
* 0步骤
* 1.定义一个线程任务描述的类该类实现于一个抽象的接口
* 2.重写线程中定义任务描述的方法
* 3.使用线程含参构造方法 创建一个线程对象
* 4.用该对象调用启动线程的方法
* @author Administrator
*
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//创建一个代表线程类的对象
TDemo2 dg = new TDemo2();
//创建一个线程类的对象
Thread t = new Thread(dg);//同类对象给本来对象构造对象---装饰者设计模式
t.start();//开启线程 开启线程后 直接调用本类中的定义的任务
}
}
//代表线程描述任务的类
//因为在使用继承类的方法进行线程的开启时---灵活度不高---因此使用实现的方法开启线程---实现的接口属于线程类
class TDemo2 implements Runnable{
//
public void run() {//任务描述的方法
for (int j = 0; j <10; j++) {
System.out.println(j);
}
}
}
方法三 线程池
package com.hst.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 本类对线程进行进阶
* 0步骤
* 1.通过线程池返回一个执行服务器 线程池有很多的线程
* 2.将要执行的线程提交到执行服务器
* 3.返回执行器执行的线程类型
* @author Administrator
*
*/
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//通过线程池返回一个执行服务器
ExecutorService e = Executors.newCachedThreadPool();//线程池
//将执行的线程提交到执行服务器
Future<Integer> f = e.submit(new CDemo());
System.out.println(f.get());
}
//
}
//代表线程的类
class CDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception{//重写的任务描述的方法
return 25;
}
}
线程的实例
入门实例——票窗统计系统
package com.hst.thread;
/**
* 0售票系统
* 0一个机场有四个入口
* 1都可以卖票
* 2总共卖了100张
* 3要判断每个售票口卖了几张
*
* 4.对于线程类中的属性来说我们无法进行初始化 因为每个开启的线程的对象都有自己的一个独有属性
* 5.我们一般采用含参构造的方法 对属性进行初始化 将实参转为形参的方式进行传递 保证 每个线程操作的值都是同一个
* @author Administrator
*
*/
public class TicketDemo {
//
public static void main(String[] args) {
//设置票数
Ticket ps = new Ticket();
ps.setTicket(50);
//
Demo3 t = new Demo3(ps);
Thread s = new Thread(t,"A");//一个对象代表一个售票员
s.start();//开启一个售票窗口
//
Demo3 t1 = new Demo3(ps);
Thread s1 = new Thread(t1,"B");
s1.start();
//
Demo3 t2 = new Demo3(ps);
Thread s2 = new Thread(t2,"C");
s2.start();
//
Demo3 t3 = new Demo3(ps);
Thread s3 = new Thread(t3,"D");
s3.start();
//
}
//
}
//代表票的类
class Ticket{
//代表票数
private int ticket;
//
public int getTicket() {
return ticket;
}
//
public void setTicket(int ticket) {
this.ticket = ticket;
}
}
//代表线程的类---描述售票员卖票的过程
class Demo3 implements Runnable{
//int ticket = 50;//票数---不可以使用基本数据类型---作为属性是每个线程的一部分
//使用引用数据类型
Ticket ps;//只是本类中的属性 没有对象值 但是名称一样的声明
//利用构造方法对属性初始化
public Demo3(Ticket ps) {
super();
this.ps = ps;
}
@Override
public void run() {//任务
//卖票的过程
while(ps.getTicket()!=0) {//判断条件就票卖完了
//票数减一
ps.setTicket(ps.getTicket()-1);
//输出每个售票员卖票后的结果
//Thread.currentThread().getName()当前正在执行的线程的名字
System.out.println(Thread.currentThread().getName()+"卖了一张票"+"还剩"+ps.getTicket()+"张票");
}
}
}
我们还可以设置配置文件,将上述结果设置为动态可调整的例子,加入以下代码即可
//创建properties对象---该类进行配置文件的加载
Properties pro = new Properties();//动态获取每次改变的票数
pro.load(new FileReader("C:\\Users\\Administrator\\Desktop\\ticket.Propertise.txt"));//加载文件
//根据键获取值---键和值都是字符串
String value = pro.getProperty("count");
//把字符串转为int
int count =Integer.parseInt(value);
//设置票数
Ticket ps = new Ticket();
ps.setTicket(count);
线程的数据安全问题
0当加入休眠时会出现bug
* 0为什么出现这个问题---多线程之间存在相互抢占资源的问题---资源的抢占发生在资源的每一行---
* 0该线程正在执行的过程中被内核调整即CPU---
* 0执行权被其他线程拿走
* 0会使得该过程中断 出现异常 产生数据安全问题重复 跳过 负数等的可能性
* 0线程资源抢占问题解决
* 1加锁
* 0同步代码锁
* 0同步方法锁
同步代码锁
while(true) {//判断条件就票卖完了
//票数减一
synchronized (ps) {
//synchronized (Demo3.class) {
//synchronized (Math.class) {
//synchronized (this) {当线程对象都是同一个代表线程的类的对象时 可以使用this
//synchronized (String.class) {//class文件在类加载的时候就在方法区
//方法区的类 默认对所有的线程对象都适用
//
//同步代码锁---解决资源抢占的问题
//锁对象 表示共享的线程对象的范围---即线程对象有公用的资源时
//对资源进行配置的一种方式---代码锁的区域不会有资源抢占的问题
//代码锁---只能保证一个线程
if(ps.getTicket()<=0)
{break;}
ps.setTicket(ps.getTicket()-1);
//输出每个售票员卖票后的结果
//Thread.currentThread().getName()当前正在执行的线程的名字
System.out.println(Thread.currentThread().getName()+"卖了一张票,"+"还剩"+ps.getTicket()+"张票");
}
}
同步方法锁
不用给定锁对象,但是跟方法有关
非静态方法的锁对象---this
静态方法的锁对象---当前类.class
public synchronized void run() {//在方法前加synchronized
同步和异步
同步:在某个时刻只能有一个资源被使用
异步:在某个时刻可以被多个线程抢占同一个资源
同步的一定是安全的。
不安全的一定是异步。
线程的死锁
死锁---因为锁嵌套造成的问题
以下是死锁状态
public class DeadLockDemo {
//
private static Scann s = new Scann();
private static Print p = new Print();
public static void main(String[] args) {
//第一个员工 先打印 再扫描
Thread t1 = new Thread(new Runnable() {
//采用匿名内部类的方法---该部分是套框架---即在含参构造方法的基础上---采用匿名内部类的方法
public void run() {
System.out.println(Thread.currentThread().getName());
synchronized (p) {
p.print();//打印
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (s) {
s.scan();//扫描
}
}
}
},"员工A");
//
t1.start();
//第二个员工 先打印 再扫描
Thread t2 = new Thread(new Runnable() {
//采用匿名内部类的方法---该部分是套框架---即在含参构造方法的基础上---采用匿名内部类的方法
public void run() {
System.out.println(Thread.currentThread().getName());
synchronized (s) {
s.scan();//扫描
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (p) {
p.print();//打印
}
}
}
},"员工B");
//
t2.start();
}
}
//代表扫描的类
class Scann{
//
public void scan() {
System.out.println("咔嚓咔嚓的扫描");
}
}
//代表打印的类
class Print{
public void print() {
System.out.println("呼哧呼哧的打印");
}
}
死锁解决
1线程有优先级,如果产生死锁的话,可以采用优先级的方式,高优先级优先获得执行权。
优先即非等待状态
2避免锁的嵌套
生产与消费者模型——线程进阶 ——等待唤醒机制
利用wait()方法,notify(),notifyAll()方法,标志位和锁控制对象之间的执行轨迹。
如何自由的控制线程。
package com.hst.thread;
/**
* 1生产与消费者模型
* 1每次生产的商品都要求是一个随机的数
* 1保证每次剩余的商品数量不能超过一千
* 1每次消费的商品数量也是一个随机整数
* 1保证每次剩余的商品数量不能少于零个
* 1让生成和消费交替出现
* 1即生产的商品和原来剩余的商品加在一起不能超过一千
* 1消费一次后存货不能为负 即不能出现商品短缺的情况
* 1当线程抢到执行权后 执行完后 下一次不管是否抢到执行权都 不能执行
* 1让该线程休眠或等待
* 1该方法保证线程不能连续两次获得执行权
* 1等待wait();
* 1唤醒notify();
* 1利用布尔值进行标志 通过判断的迭代变化来实现是否唤醒
* @author Administrator
*
*/
public class ProdectModelDemo {
public static void main(String[] args) {
Product p = new Product();
p.setNums(10);
//创建匿名对象
new Thread(new Productor(p),"生产").start();
new Thread(new Consumer(p),"消费").start();
}
}
//代表线程生产的过程
class Productor implements Runnable{
//设置一个有参构造 将对象的值控制为同一个对象
Product pro;
public Productor(Product pro) {
this.pro=pro;
}
//
public void run() {
while(true) {//保证一直生产
//最大生产的数量
synchronized (pro) {
while(pro.flag==true) {
try {
pro.wait();//让线程等待不能连续两次同时执行权
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
int max = 1000-pro.getNums();
//随机生产的商品数量
int count = (int) (Math.random()*(max+1));
pro.setNums(pro.getNums()+count);
//输出生产的结果
System.out.println("生产了"+count+",个商品,还剩余"+pro.getNums());
pro.notifyAll();//唤醒线程 否则线程将一直等待
pro.flag=true;
}
}
}
}
//代表线程消费的过程
class Consumer implements Runnable{
//
Product pro;
public Consumer(Product pro) {
this.pro=pro;
}
//
public void run() {
while(true) {//保证一直消费
synchronized (pro) {
while(pro.flag==false) {
try {
pro.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//消费了多少商品
int count = (int) (Math.random()*(pro.getNums()+1));
pro.setNums(pro.getNums()-count);
//输出生产的结果
System.out.println("消费了"+count+",个商品,还剩余"+pro.getNums());
pro.notifyAll();//唤醒线程 否则线程将一直等待 唤醒的别的线程
pro.flag=false;
}
}
}
}
//代表商品信息
class Product{
//商品数量
private int nums;
boolean flag=true;//标志位 判断哪个线程对象等待
//
public int getNums() {
return nums;
}
//
public void setNums(int nums) {
this.nums = nums;
}
//
}
线程的优先级
每个线程创建出来以后 默认创建了优先级 创建优先级都一样
package com.hst.thread;
/**
* 1优先级
* 1理论上线程的优先级越高 抢占执行权的可能性越大
* 1线程的优先级分布 从 一 到 十
* 1开启线程优先级的方法setPriority()
* 1如果线程之间的优先级差大于五 理论上 可以抢占的资源的概率越高
* @author Administrator
*
*/
public class PriorityDemo {
//
public static void main(String[] args) {
//创建线程
Thread t1 = new Thread(new PdDemo(),"A");
Thread t2 = new Thread(new PdDemo(),"B");
//设置线程的优先级
t1.setPriority(3);
t2.setPriority(3);
//开启线程
t1.start();
t2.start();
}
}
//代表线程的类
class PdDemo implements Runnable{
public synchronized void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"抢到了"+i);
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
守护线程和被守护线程
package com.hst.thread;
/**
* 1守护线程和被守护线程
* 1当被守护线程结束时候 守护线程也结束
* 1线程要么是守护线程 要么是被守护线程
* 1守护线程和被守护线程都可以有多个
* 1被守护线程都结束 只有等到所有的被守护线程都结束 其他的守护线程才能结束
* @author Administrator
*
*/
public class DeamonDemo {
public static void main(String[] args) {
//创建线程对象----小兵----守护boss
Thread t1 = new Thread(new Soilder(),"小兵甲");
Thread t2 = new Thread(new Soilder(),"小兵乙");
Thread t3 = new Thread(new Soilder(),"小兵丙");
Thread t4 = new Thread(new Soilder(),"小兵丁");
//设置守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t4.setDaemon(true);
t3.setDaemon(true);
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
//主线程---main方法中的线程为主线程---Boss
for (int i = 20; i >=0; i--) {
System.out.println("BOSS"+"还剩"+i+"滴血");
}
}
}
//代表线程的类
class Soilder implements Runnable{
int sk = 10;
public synchronized void run() {
while(sk!=0) {
sk-=1;
System.out.println(Thread.currentThread().getName()+"还剩"+sk+"滴血");
if(sk==0) {
System.out.println(Thread.currentThread().getName()+"被消灭");
Thread.interrupted();//中断线程
}
}
//
}
}
线程的状态
线程的创建
线程的就绪---所有线程在执行前必须就绪
线程的执行
线程的销毁---所有的线程都在结束后销毁
线程的阻塞---线程人为的等待
单例设计模式
实例对象设计模式----只有一个对象的设计模式
package com.hst.thread;
/**
* 1单例设计者模式
* 1只能产生一个对象
* 1
* @author Administrator
*
*/
public class SingleDemo {
public static void main(String[] args) {
//获取唯一的一个对象
TaskManger m = TaskManger.getDg();//通过类来调用实例方法
//静态的方法可以通过类名直接调用
//静态信息和类一个级别 在类加载的时候同时加载到静态方法区
}
//
}
//代表任务管理器的类---只能产生一个对象
//懒汉式---有线程安全问题
class TaskManger{
//私有的构造方法---意味着外部不能调用该方法
//当无参构造方法是私有的时候 不能在类的外部调用到该类
private TaskManger() {
//
}
//创建实例对象
private static TaskManger dg = null;//非静态属性非静态方法 可以通过对象来调用
//静态对象在类加载的时候已经在方法区
//常量不可改变 只能有一个实例
//返回对象方法
public static TaskManger getDg() {
if(dg==null) {//如果该类已经存在则 直接返回 否则创建新的对象返回 保证该实例对象只有一个 且 唯一的一个
dg=new TaskManger();
}
return dg;
}
}
//饿汉式---没有线程安全问题
class TaskManger2{
//私有的构造方法---意味着外部不能调用该方法
//当无参构造方法是私有的时候 不能在类的外部调用到该类
private TaskManger2() {
//
}
//创建实例对象
private final static TaskManger2 dg = new TaskManger2();//非静态属性非静态方法 可以通过对象来调用
//静态对象在类加载的时候已经在方法区
//常量不可改变 只能有一个实例
//返回对象方法
public static TaskManger2 getDg() {
return dg;
}
}