Java教程

面试准备——Java回顾:高级编程(多线程、常用类、集合、泛型、IO流、反射、动态代理、新特性)

本文主要是介绍面试准备——Java回顾:高级编程(多线程、常用类、集合、泛型、IO流、反射、动态代理、新特性),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

多线程

程序、进程、线程

程序:完成特定任务、用某种语言编写的一组指令的结合,一段静态的代码,静态对象;

进程:程序的一次执行过程,即正在执行的程序;

线程:进程可细化为线程,是一个程序内部的一条执行路径;

进程作为资源分配的单位,线程作为调度和执行的单位,有独立的运行站和程序计数器PC,线程开销小;

方法区和堆是一个进程一份,线程共享;

单核CPU和多核CPU:
在这里插入图片描述

并发:一个CPU在一段时间内执行多个任务;
并行:多个CPU同时执行多个任务;

多线程:
①提高应用程序响应,增强用户体验;
②提高CPU利用率
③改善程序结构,将长、复杂的进程分为多个线程;

java.lang.Thread创建多线程
每个线程都是通过某个特定Thread对象的 run() 方法来完成操作的;
该Thread对象的start()方法来启动这个线程,而非直接调用run();

多线程的创建(方式一):
①创建一个继承于Thread类的子类
②重写Thread类的run()
③创建Thread类的子类的对象
④通过对象调用start() => 启动线程并执行run

Thread.currentThread().getName()获取当前运行线程名字

Thread线程测试常用方法
在这里插入图片描述

创建线程的方式二:实现runnable接口
在这里插入图片描述
两种方式比较:
在这里插入图片描述

实现runnable接口从而创建线程比较好,像购票系统,只需要new一个实现了接口的对象,就可以将此参数传递到Thread类的构造器中从而创建多个Thread类的对象;

在这里插入图片描述
线程的几种状态:
在这里插入图片描述
在这里插入图片描述

线程同步

why?
解决像买票这种线程安全问题;

线程同步、线程安全问题
两种同步机制:
①同步代码块 synchronized (锁){ 操作共享数据(多个线程共同操作的数据)的代码 }
任何一个类的对象(类也可以,真正的面向对象,反射会将)都可以充当锁,但多个线程必须共用同一个锁;
②同步方法:在方法修饰前加上synchronized,将同步代码放方法体内;同步方法仍然涉及到同步监视器,只是不需要显示声明,非静态同步方法,同步监视器是this,静态同步方法,同步监视器是当前类本身;
③lock锁;

实现runnable接口,同步监视器可以用this来充当;
如果用同步方法,且是继承Thread类的形式,那么同步类就要声明为

同步的优点及局限性:

  • 解决了线程安全问题
  • 操作同步代码时,只有一个线程操作,其它线程等待,相当于一个单线程问题,效率低;

使用同步机制将单例模式中的懒汉式改写为线程安全的

class Bank
{
	private Bank(){}
	private static Bank instance=null;
	/* 方法一
	public static synchronized Bnak getInstance()
	{
		if(instance == null)
		{
			instance=new Bank();
		}
		return instance;
	}
	*/
	// 方法2.1
	public static Bnak getInstance()
	{
		// 效率稍差
		synchronized(Bank.class)
		{
			if(instance == null)
			{
				instance=new Bank();
			}
			return instance;
		}
	}
	// 方法2.2
	public static Bnak getInstance()
	{
		if(instance == null)
		{
			synchronized(Bank.class)
			{
				if(instance == null)
				{
					instance=new Bank();
				}
			}
		}
		return instance;
	}
	
}

死锁问题

死锁:不同的线程相互占用同步资源,都在等待对方放弃自己需要的同步资源,形成死锁;

死锁不会有异常提示,所有线程处于阻塞状态,无法继续,要避免死锁;

lock锁
解决线程安全方式三:lock锁
private ReentrantLock lock = new ReentrantLock(fair=false) 若 fair = true,则先来先得,公平方式;若为false,则多人排队时下一个进入者随机;

private ReentrantLock lock = new ReentrantLock(fair=false);
lock.lock();
同步代码块
lock.unlock();

面试题:synchronized 与 lock 的异同

  • 都可以解决线程安全的问题;
  • synchronized在执行完相应的同步代码后,自动释放同步监视器;
  • lock需要手动开启同步和手动结束同步;

使用的优先顺序:lock -> synchronized同步代码块 -> 同步方法;

面试题:同步与异步
同步涉及到线程通信,多个线程访问临界区,只允许一个线程进去使用,其它的等待;
异步就是大家该走走,都并行地执行;

线程通信

wait、notify
wait进入阻塞状态时会释放锁;
notifyAll 会唤醒所有 wait 的线程

注意:

  • 这三个方法必须在同步方法or同步代码块中使用;
  • 三个方法的调用者必须是同步代码块or同步方法中的同步监视器;否则出现非法监视器状态异常;

面试题:sleep 和 wait 异同

  • 都能让线程进入阻塞;
  • 二者声明位置不同:Thread类中声明sleep(),object类中声明wait();
  • 调用范围不一样:wait要在同步中用,sleep可以在任何需要的场景下调用;
  • wait释放同步监视器,而sleep不释放;

练习:生产者消费者模型

import java.util.Arrays;
import java.util.Scanner;

public class test {
    public static void main(String[] args) {
        Goods goods = new Goods();
        Producer producer = new Producer(goods);
        Comsumer comsumer = new Comsumer(goods);
        producer.setName("生产者1");
        comsumer.setName("消费者1");
        producer.start();
        comsumer.start();
    }
}

class Goods {
    private int num = 0;

    public synchronized void produceGoods() {
        if (num < 20) {
            num++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + num + "个产品");
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consumeGoods() {
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + num + "个产品");
            num--;
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


}

class Producer extends Thread {
    private Goods good = new Goods();

    public Producer(Goods good) {
        this.good = good;
    }

    @Override
    public void run() {
        System.out.println(getName() + "开始生成产品...");
        while (true) {
            try {
                sleep(10);// 如果要生产快些,那就让生产者睡得短些
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            good.produceGoods();
        }
    }
}

class Comsumer extends Thread {
    private Goods good = new Goods();

    public Comsumer(Goods good) {
        this.good = good;
    }

    @Override
    public void run() {
        System.out.println(getName() + "开始消费产品...");
        while (true) {
            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            good.consumeGoods();
        }
    }
}

JDK5.0 定增创建线程方式:Callable接口
在这里插入图片描述
在这里插入图片描述

步骤:
①创建一个实现Callable接口的类
②实现call方法,将此线程需要执行的操作声明在call()中;
③创建Callable接口实现类的对象
④将此对象作为参数传递到FutureTask构造器中,创建FutureTash对象;
⑤将FutureTash对象作为参数传递到 Thread 类的构造器,创建Thread类的对象并 start() ;
⑥获取Callable中call()的返回值;

比实现runnable接口创建线程要强大;

创建线程的方式四:线程池
开发中不会一个个去创建线程,都会有线程池;
加粗样式
比如手机看新闻下滑过程,网路不好时,你下滑可能出现了文字但没出现图像,这几乎是主线程加载文字,用多个分线程去加载图片,不影响用户观看;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开发有框架,不需要掌握具体实现,但要记住创建多线程的四种方式;

常用类

字符串相关类

String
在这里插入图片描述
①JDK9之后是 byte[] 字节数组,主要是为了节省空间,还增加了一个coder字节,表示是否包含utf-16编码;
②String实现了Serializable接口:表示字符串支持序列化,序列化就是在这边是一个对象,通过网络传输字节过去后,在对面仍能够还原成这个对象;
③String内部定义了 final char[] value用于存储字符串数据;
④不可变性;
⑤当对字符串进行连接操作时,需要重写指定区域赋值,不能使用原有value进行赋值;

String str1=“abc” 和 String str2=new String(“abc”)的区别
在这里插入图片描述
注意含有字符串属性的类,字面对字符串的初始化也放常量池;
在这里插入图片描述

面试题:String str2 = new String(“abc”);在内存中创建了几个对象?

String str1 = "abc";  // 在常量池中
String str2 = new String("abc"); // 在堆上

当直接赋值时,字符串“abc”会被存储在常量池中,只有1份,此时的赋值操作等于是创建0个或1个对象。如果常量池中已经存在了“abc”,那么不会再创建对象,直接将引用赋值给str1;如果常量池中没有“abc”,那么创建一个对象,并将引用赋值给str1。

那么,通过new String(“abc”);的形式又是如何呢?答案是1个或2个。

当JVM遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个一个字符串。然后再执行new操作,会在堆内存中创建一个存储“abc”的String对象,对象的引用赋值给str2。此过程创建了2个对象。

当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。

在这里插入图片描述
图中两个String对象的value值的引用均为{char[3]@1355},也就是说,虽然是两个对象,但它们的value值均指向常量池中的同一个地址。当然,大家还可以拿一个复杂对象(Person)的字符串属性(name)相同时的debug结果进行比对,结果是一样的;

面试题:String str = “abc” + “def”;会创建几个对象
String str = "abc" + "def";
上面的问题涉及到字符串常量重载“+”的问题,当一个字符串由多个字符串常量拼接成一个字符串时,它自己也肯定是字符串常量。字符串常量的“+”号连接Java虚拟机会在程序编译期将其优化为连接后的值。

就上面的示例而言,在编译时已经被合并成“abcdef”字符串,因此,只会创建1个对象。并没有创建临时字符串对象abc和def,这样减轻了垃圾收集器的压力。
在这里插入图片描述
更深层的内容,参考文章:面试题系列第2篇:new String()创建几个对象?有你不知道的
字符串拼接
在这里插入图片描述
①常量与常量的拼接结果还在常量池,即常量池不会存在相同内容的常量;
②只要其中有一个是变量,结果就在堆中;
③如果拼接结果调用intern()方法,返回值就在常量池中;
④如果价格final 那么字符串就在常量池,那么拼接后还在常量池,比如final String s8="javaEE",String s9=s8+"hadoop"那么s3==s9 为true

面试题:字符串的不可变性
在这里插入图片描述
为什么ex.str输出为good,因为String的不可变性,当String对象作为参数传递给函数时,的确传的地址,但函数内部试图修改该地址所指向的内容,由于String的不可变性,使得它会在常量区中再创建"test ok",使形参str等于它的地址,但并未改变实参str的地址;

还是记住那句话:基本数据类型,传递值;引用数据类型,传递地址

字符串常量区的演变
1、 java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变;
2、 java7中,static变量从永久代移到堆中;
3、 java8中,取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可以认为在堆中,但是实际上我们说的堆指的是用于存放java对象的那些空间;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
String 与 基本数据类型、包装类的转换:
在这里插入图片描述
String 与 char[] 数组的转换
String -> char[] String.toCharArray()
char[] -> String 构造器;
String 与 byte[] 之间的转换:调用String的getBytes(可指定编码类型);

StringBuffer

这篇关于面试准备——Java回顾:高级编程(多线程、常用类、集合、泛型、IO流、反射、动态代理、新特性)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!