一、继承方式实现多线程和实现方式实现多线程对比
1、继承方式(继承Thread类)
(1)实现步骤
A、将类声明为 Thread 的子类;
B、该子类应重写 Thread 类的 run 方法;
C、在主线程进行该自定义的线程类的对象的创建。
(2)实现特点
A、启动线程时,不使用run()方法原因:ran()方法不能作为启动线程的方法,该方法的调用相当于普通方法,并不能体现出线程执行的随机性。
B、启动线程用Start()方法,start()方法通过JVM调用run()方法。一个线程不能连续启动,会出现非法状态异常。
2、实现方式(实现Runnabl接口,优于继承方式)
(1)实现步骤
A、自定义一个类,实现Runnable接口;
B、实现接口中的run方法,对耗时的代码进行操作;
C、然后在主线程中创建该了对象,将该类对象做为一个资源类,创建Threadd类的对象,将刚才的资源类作为参数进行传递。
(2)实现特点(优于继承方式的优势)
A、避免了Java单继承的局限性。
B、更符合Java面向对象的一种设计原则(面向接口编程),将代码的实现和资源对象有效分离,即数据分离原则。
二、多线程的生命周期
多线程的生命周期:线程从开始创建到线程执行,最终到线程终止的过程。阶段如下:
1、创建线程:此时线程无执行资格;
2、线程就绪:有执行资格,无执行权;
3、线程阻塞状态(可能会出现):线程睡眠sleep()或线程等待wait();
4、唤醒状态(可能会出现):notify(),唤醒之后执行阶段5;
5、线程执行:start(),有执行资格,有执行权;
6、线程死亡:线程执行完毕,会被垃圾回收线程中的垃圾回收器及时从内存中释放掉。
三、多线程安全
1、校验一个多线程程序是否有安全问题的隐患的前提条件
(1)当前程序是否是多线程环境;
(2)是否有共享数据;
(3)是否有多条语句对共享数据进行操作。
2、解决方案
(1)方案:前两个条件无法解决,通过解决条件(3)解决多线程安全问题,即:将多条语句对共享数据操作的代码,用代码块包起来,即同步代码块。
(2)格式:
synchronized(锁对象){
针对多条语句对共享数据操作代码;
}
(3)锁对象:任意的Java类对象,一般是引用类型。每个线程最终使用的锁,只能是同一把锁。
四、同步代码块(隐式锁)
1、格式:
synchronized(锁对象){
针对多条语句对共享数据操作代码;
}
2、例如:
(1)资源类:
package 多线程.同步代码块;/* * 模拟卖票,加入延迟操作 */public class SellTicket implements Runnable{ //定义票数变量 private int tickets=100; //创建锁对象 private Object obj=new Object(); @Override public void run() { while(true) { //同步代码块 synchronized(obj) { if(tickets>0) { try { //延迟1秒 Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在售出第"+(tickets--)+"张票"); } } } } }
(2)测试类:
package 多线程.同步代码块;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"); //启动线程 t1.start(); t2.start(); }}
五、同步方法
1、同步方法:用synchronized声明的方法。
2、锁对象:
(1)静态同步方法:类名.class
(2)非静态同步方法:this
3、例如:
(1)资源类
public class SellTicket implements Runnable { //定义票数变量 private static int tickets=100; @Override public void run() { while(true) { sellTicket(); } } //以静态同步方法为例 public static synchronized void sellTicket() { if(tickets>0) { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在售出第"+(tickets--)+"张票"); } }}
(2)测试类
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"); //启动线程 t1.start(); t2.start(); }}
六、Lock接口(显示锁)
1、概述:Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构。 可以使用Lock锁进行具体的锁定操作类,提供了具体的实现类:ReentrantLock。加锁并释放锁。
2、方法:
(1)加锁:Lock(),在对共享数据操作前获取锁。
(2)释放锁:unLock(),使用完毕之后释放锁。
3、显示获取锁的前提:必须创建Lock对象。
4、存在问题:使用Lock锁解决多线程安全问题,会存在问题:(1)执行效率低;(2)产生死锁。
七、死锁
1、死锁:两个或两个以上的线程,在执行的过程中出现互相等待的情况,即为死锁。
2、解决:生产者消费模式。需保证生产者线程和消费者线程针对同一个对象进行操作,
在外部创建一个学生对象,将这个学生对象通过构造方法传入到各个线程中。
3、例如:
4、在消费者线程和生产者线程中加入循环(多线程)
(1)出现问题:a、同一个对象出现多次;b、信息不匹配。
(2)原因:a、CPU的一点点时间片,在某一个时间点可执行多次。b、线程具有随机性。
(3)解决:用synchronized(或Lock锁)同步代码块将多条语句对共享数据的操作包起来。
(4)解决之后依旧存在问题:数据无法依次打印。
(5)解决:等待唤醒机制
八、等待唤醒机制
1、核心思想
(1)生产者线程产生数据,若本身无数据,等待其产生数据,若有数据,通知(唤醒)对方(消费者进程)输出数据。
(2)消费者线程消费数据,有数据,等待消费者线程先输出数据,一旦无数据,及时通知(唤醒)对方(生产者线程)产生数据。
2、(面试题)wait(),notify(),notifyAll() 这些方法为什么会定义在Object类中?
解:这些方法是主要用于线程的方法,而Thread类中并没有这些方法,多线程中同步锁对象是任意的Java类这些方法都和锁对象有关系,所以定义在Object类中。
九、线程组
1、概述:线程组,ThreadGroup,表示多个线程所在的集合。此外,线程组也可以包含其他线程组。
2、构造方法
(1)public ThreadGroup(String name)构造一个新线程组。
(2)Thread(ThreadGroup group, Runnable target, String name)构造线程组中的某个线程。
3、获取方法
(1)public final ThreadGroup getThreadGroup()返回该线程所属的线程组。
(2)public final String getName()返回线程组的名称
十、线程池(多线程第三种实现方式)
1、特点
(1)节约成本。
(2)线程执行完毕后不会变成垃圾,重新回到线程池中,等待被利用。
2、Executors类
(1)概述:一种工厂类,与线程池创建有关。
(2)方法:public static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池。
3、ExecutorService接口
(1)概述:可以执行异步任务,创建一个线程池,执行接口中的方法。
(2)提交:Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
4、Future接口:Future 表示异步计算的结果。
5、关闭线程池方法:void shutdown()线程池调用完毕可以关闭的,关闭之前,会提交刚才的任务。
6、实现多线程前提:自定义类实现Callable接口。Callable接口的泛型是Call()方法的返回值类型。
7、实现步骤
(1)自定义一个类实现Callable<V>接口,并重写Call()方法,接口泛型与Call()方法返回值类型一样。
(2)创建线程池对象:Executors 里面的方法,返回的是ExecutorsService
(3)调用ExecutorsService里面的提交任务的方法:
<T> Future<T> submit(Callable<T> task)提交一个返回值的任务用于执行
(4)关闭线程池。
十一、面向对象设计原则/设计方法(23种)
1、重点
(1)设计模式:a、简单工厂模式;b、工厂设计模式;c、单例设计模式(多线程),分为饿汉式和懒汉式。
(2)设计原则:a、关闭原则;b、接口分离原则。
2、面向对象设计原则
(1)单一职责原则:低耦合,高内聚。耦合性,类与类之间产生的关系。低耦合,让类与类之间的关系不复杂。内聚:执行一个件事情(功能)的能力。高内聚,一个类能够完成的事情,不要使用多个类一起来执行。
(2)开闭原则(重点):(核心思想)一个对象对扩展开放,对修改关闭。开发好一个程序(项目),尽量不要修改原有代码。借助抽象和多态,把可能变化的内容抽象出来,抽象的部分是相对稳定的,而具体的改变则是可以改变和扩展的。
(3)里氏替换原则:(核心思想)在任何父类出现的地方都可以用它的子类来替代。即:同一个继承体系中的对象应该有共同的行为特征。
(4)依赖注入原则:Spring MVC(开源框架)—MVC设计模式。(核心思想)要依赖于抽象,不要依赖于具体实现。即在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类。为了实现这一原则,就要求在编程的时候针对抽象类或者接口编程,而不是针对具体实现编程。
(5)接口分离原则(重点):(核心思想)不应该强迫程序依赖它们不需要使用的方法。即一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中。
(6)迪米特原则:(核心思想)一个对象应当对其他对象尽可能少的了解。即降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用。
3、面向对象设计模式
(1)简单工厂模式(静态工厂方法模式)
A、核心思想:定义一个具体的工厂类负责创建一些类的实例。
B、优点:客户端不需要在负责对象的创建,从而明确了各个类的职责。
C、缺点:静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护。
(2)工厂方法模式
A、核心思想:工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
B、优点:客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性。
C、缺点:需要额外的编写代码,增加了工作量。
(3)单例设计模式(重点)
A、单例模式:要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。Servlet程序(优化服务器开发的) 单例。
B、优点:在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
C、缺点:没有抽象层,因此扩展很难。职责过重,在一定程序上违背了单一职责。
D、分类:
a、饿汉式(开发常用,不会出现问题);
b、懒汉式(类似于多线程环境,面试常用,可能会出现问题)。解决:同步机制或静态同步方法。
E、饿汉式
a、特点:在加载那个类的时候,对象的创建工作已经完成。
b、设计步骤:
(a)定义个类,将该类的无参构造方法私有化;
(b)在该类的成员位置创建该类对象,并且一定要私有化,防止外界更改这个对象;
(c)在该类中提供静态成员方法(返回值就是创建的对象),能被当前类直接调用,static修饰。
F、懒汉式(延迟加载→懒加载)
a、设计步骤:单例模式核心思想
(a)自定义一个类,将无参构造私有化;
(b)在成员位置声明变量;
(c)提供公共静态功能,在里面判断的创建该类对象,返回该类对象。
b、可能出现的问题:同多线程环境问题。
c、解决:使用同步机制解决。
(4)(面试题)你使用过单例模式吗?简单介绍一种单例模式,请用代码设计。
核心思想:使用设计单例的懒汉式,想到使用同步机制解决线程的安全问题。
十二、Runtime类
1、概述:每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。
2、方法:
(1)public static Runtime getRuntime()返回与当前 Java 应用程序相关的运行时对象。
(2)public Process exec(String command) throws IOException在单独的进程中执行指定的字符串命令。
十三、网络编程
1、计算机网络:多台计算机通过网络协议,实现网络资源共享和信息传递。
2、网络通信三要素:IP地址、端口号、传输协议/规则(UDP/TCP)。
3、IP地址:
(1)组成:网络号码+主机地址
(2)分类:
A、A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码
一般用于:国防部/大的国家部门。
B、B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码
一般用于:大学里面的多媒体教室。
C、C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码
一般用于:私人地址。(互联网不使用,被用在局域网中的地址)。
4、端口号:
(1)有效端口号:0~65535
(2)保留端口号:0~1024(暂时不用)
(3)360软件可查询端口号。
5、传输协议:
(1)UDP协议(UDP编程):不需要建立连接通道,数据大小有限制,不可靠连接,执行效率高。例如:发信息,无需建立连接通道,可看做UDP协议。
(2)TCP协议(TCP编程):需要建立连接通道,数据大小无限制,可靠连接,执行效率低。例如:打电话,建立连接通道,可看做TCP协议。
十四、InetAddress类
1、概述:表示互联网协议 (IP) 地址。无构造方法,无字段,无成员方法。
2、一个类中没有构造方法,没有字段,只有成员方法的特征:
(1)应该有一些静态功能;
(2)可能符合一种单例模式(饿汉/懒汉);
(3)该类中的某些静态成员方法的返回值是该类本身。
3、常用方法:
(1)public static InetAddress getByName(String host)在给定主机名的情况下确定主机的 IP 地址。
异常:throws UnknownHostException
参数:主机名可以是机器名(如 "java.sun.com"),也可以是其 IP 地址的文本表示形式。
(2)public String getHostAddress()返回 IP 地址字符串(以文本表现形式)。
(3)public String getHostName()获取此 IP 地址的主机名。
十五、UDP编程开发步骤
1、UDP编程发送端开发步骤:
(1)创建发送端的Socket对象;
(2)创建数据,并打包;
(3)调用当前发送端Socket对象中的发送的方法;
(4)关闭资源。
2、UDP编程发送端开发常用方法:
(1)public DatagramSocket(int port,InetAddress laddr)构造方法,创建发送端的Socket对象。(DatagramSocket-此类表示用来发送和接收数据报包的套接字。)
(2)DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 创建数据,并打包。( buf-包数据,offset - 包数据偏移量,length - 包数据长度,address - 目的地址,port - 目的端口号
)
(3)public void send(DatagramPacket p)throws IOException从此套接字发送数据报包。
3、Udp编程接收端开发步骤:
(1)创建Socket对象;
(2)创建一个数据报包(接收容器);
(3)调用Socket对象中的接收方法;
(4)解析实际传递的数据;
(5)将解析的数据(ip,数据)展示在控制台上;
(6)关闭资源。
4、Udp编程接收端开发常用方法:
(1)public DatagramSocket(int port)创建数据报包套接字对象并且将其绑定到本地主机上的指定端口。
(2)public DatagramPacket(byte[] buf, int length)创建一个数据报包(接收容器)。
(3)public void receive(DatagramPacket p)接收数据报包。
(4)public InetAddress getAddress()获取ip地址文本形式,返回ip地址对象,数据报包类:DataGramPacket。
(5)public byte[] getData()获取缓冲中实际数据(从接收容器中获取)。
(6)public int getLength()返回将要发送或接收到的数据的长度。
5、注意:接收端不要运行多次,会出现异常。java.net.BindException: Address already in use: Cannot bind。