admin管理员组文章数量:1794759
Java进阶必看100条(一)
Java进阶
1、线程使用的三个概念
①程序(programm):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
②进程(process):程序的一次执行过程,或是正在运行的一个程序。说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
③线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
2、每个线程,拥有自己独立的:栈、程序计数器;多个线程,共享同一个进程中的结构:方法区、堆。
3、单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务,之所以能感觉到CPU好像是在同时工作,是因为CPU的执行速度非常的快。
4、一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程,如果发生异常,会影响主线程。
5、并行与并发的理解
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事6、创建多线程的两种方式
方式一:继承Thread类的方式:
两个问题: 问题一:启动一个线程,必须调用start(),不能调用run()的方式启动线程。 问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()
代码:
//1. 创建一个继承于Thread类的子类 class MyThread extends Thread { //2. 重写Thread类的run() @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3. 创建Thread类的子类的对象 MyThread t1 = new MyThread(); //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run() t1.start(); } }方式二:实现Runnable接口的方式:
开发中:优先选择:实现Runnable接口的方式。原因:
7、Thread类中的常用的方法:
- start():启动当前线程;调用当前线程的run()
- run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- stop():已过时。当执行此方法时,强制结束当前线程。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
- isAlive():判断当前线程是否存活
8、线程的优先级:
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5 -->默认优先级
getPriority():获取线程的优先级 setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
9、线程通信:wait() 、notify() 、 notifyAll() :此三个方法定义在Object类中的,原因:涉及线程安全问题时用到同步代码块synchronized,任何一个对象都能作为同步监视器(俗称:同步锁),确保每个对象都能使用这项线程通信的方法,所以定义到object类中。
10、线程生命周期 阻塞:临时状态,不可以作为最终状态,只有死亡才可以作为线程的最终状态。
11、线程同步
①问题来源:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
- 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
- 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
- 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
②Java解决方案:同步机制
*方式一:同步代码块 * synchronized(同步监视器){ * //需要被同步的代码 * } * 说明: 1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。 * 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。 * 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 * 要求:多个线程必须要共用同一把锁(同一个对象)。 * * 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。当前类也可以是一个对象。 /****************实现Runnable接口**************************************/ class Window1 implements Runnable{ private int ticket = 100; @Override public void run() { // Object obj = new Object(); while(true){ synchronized (this){//此时的this:需要是唯一的对象 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; } else { break; } } } } public class WindowTest1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.start(); t2.start(); t3.start(); } } /********************继承Thread类实现******************************/ class Window2 extends Thread{ private static int ticket = 100; private static Object obj = new Object(); @Override public void run() { while(true){ synchronized (Window2.class){//Class clazz = Window2.class,Window2.class只会加载一次 * 错误的方式:this代表着t1,t2,t3三个对象 *// synchronized (this){ if(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + ":卖票,票号为:" + ticket); ticket--; }else{ break; } } } } } public class WindowTest2 { public static void main(String[] args) { Window2 t1 = new Window2(); Window2 t2 = new Window2(); Window2 t3 = new Window2(); t1.start(); t2.start(); t3.start(); } } * 方式二:同步方法 * 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。 class Window4 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { show(); } } private static synchronized void show(){//同步监视器:Window4.class //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; } } } public class WindowTest4 { public static void main(String[] args) { Window4 t1 = new Window4(); Window4 t2 = new Window4(); Window4 t3 = new Window4(); t1.start(); t2.start(); t3.start(); } } * 关于同步方法的总结: * 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。 * 2. 非静态的同步方法,同步监视器是:this * 静态的同步方法,同步监视器是:当前类本身 * 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增 * * 1. 面试题:synchronized 与 Lock的异同? * 相同:二者都可以解决线程安全问题 * 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器 * Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()) * class Window implements Runnable{ private int ticket = 100; //1.实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try{ //2.调用锁定方法lock() lock.lock(); if(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; }else{ break; } }finally { //3.调用解锁方法:unlock() lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window w = new Window(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.start(); t2.start(); t3.start(); } }12、线程同步的方式,解决了线程的安全问题,弊端是操作同步代码时,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
13、面试题:写一个线程安全的单例模式。
使用同步机制将单例模式中的懒汉式改写为线程安全的。 class Bank{ private Bank(){}//提供一个私有的构造器 private static Bank instance = null;//创建一个该类的对象 public static Bank getInstance(){//提供一个获取该对象的方法 //方式一:效率稍差 // synchronized (Bank.class) {每个进来的线程都要进到同步锁,效率低 // if(instance == null){ // instance = new Bank(); // } // return instance; // } //方式二:效率更高 if(instance == null){//第一个进去的线程创建好对象,后面的对象直接用 synchronized (Bank.class) { if(instance == null){ instance = new Bank(); } } } return instance; } }14、死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所的线程都处于阻塞状态,无法继续。
public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread(){ @Override public void run() { synchronized (s1){ s1.append("a"); s2.append("1"); try { Thread.sleep(100);//线程阻塞,cpu会去执行其他线程 } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run() { synchronized (s2){ s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1){ s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } *假设cpu先执行第一个线程,s1的锁已经锁上,此时当前线程恰好阻塞 cpu去执行第二个线程,s2的锁被锁上了,第二个线程也被阻塞了 当这两个线程阻塞完后回到就绪状态,等待cpu分配资源 下一步是第一个线程需要第二个线程的锁,第二个线程需要第一个线程的锁 这样就形成了死锁,程序不会崩溃,但是什么也干不了。15、线程通信涉及到的三个方法:
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
- 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
- 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
- 否则,会出现IllegalMonitorStateException异常
- 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
16、面试题:sleep() 和 wait()的异同?
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
17、Thread.yield()方法:让步方法,释放当前线程获得的cpu资源,进入就绪状态,与其他线程共同等待cpu再次分配资源,而不是自己释放cpu资源后,不参与再次分配。好比去坐公交车,大家在公交车门口前排队,到你上车了,你让后面的人先上,自己再上车;也有可能你让后面的人先上,但是后面的人因为某种原因没有上,最终还是你自己先上了。
18、锁的释放
19、新增创建线程的方式 新增方式一:实现Callable接口。 — JDK 5.0新增
//1.创建一个实现Callable的实现类 class NumThread implements Callable{ //2.实现call方法,将此线程需要执行的操作声明在call()中 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { if(i % 2 == 0){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { //3.创建Callable接口实现类的对象 NumThread numThread = new NumThread(); //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象 FutureTask futureTask = new FutureTask(numThread); //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start() new Thread(futureTask).start(); try { //6.获取Callable中call方法的返回值 //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。 Object sum = futureTask.get(); System.out.println("总和为:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
- call()可以返回值的。
- call()可以抛出异常,被外面的操作捕获,获取异常的信
- Callable是支持泛型的
新增方式二:使用线程池
class NumberThread implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } public class ThreadPool { public static void main(String[] args) { //1. 提供指定线程数量的线程池 ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //设置线程池的属性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime(); //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 service.execute(new NumberThread());//适合适用于Runnable service.execute(new NumberThread1());//适合适用于Runnable // service.submit(Callable callable);//适合使用于Callable //3.关闭连接池 service.shutdown(); } }- 好处:
- 1.提高响应速度(减少了创建新线程的时间)
- 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 3.便于线程管理
corePoolSize:核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没任务时最多保持多长时间后会终止
面试题:Java中多线程的创建有几种方式?四种。
20、String类 详解 1)String 的特点:
1、String声明为final的,不可被继承 。 2、String实现了Serializable接口:表示字符串是支持序列化的。 3、实现了Comparable接口:表示String可以比较大小。 4、String内部定义了final 。char[] value用于存储字符串数据 5、通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)。 6、字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。
2)String的不可变性
1、当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。 2、当对现的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。 3、当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
代码举例: String s1 = "abc";//字面量的定义方式 String s2 = "abc"; s1 = "hello"; System.out.println(s1 == s2);//比较s1和s2的地址值,false System.out.println(s1);//hello System.out.println(s2);//abc System.out.println("*****************"); String s3 = "abc"; s3 += "def"; System.out.println(s3);//abcdef System.out.println(s2);//abc System.out.println("*****************"); String s4 = "abc"; String s5 = s4.replace('a', 'm'); System.out.println(s4);//abc System.out.println(s5);//mbc3) String实例化的不同方式 方式一:通过字面量定义的方式 方式二:通过new + 构造器的方式
*通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。 String s1 = "javaEE"; String s2 = "javaEE"; *通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。 String s3 = new String("javaEE"); String s4 = new String("javaEE"); System.out.println(s1 == s2);//true System.out.println(s1 == s3);//false System.out.println(s1 == s4);//false System.out.println(s3 == s4);//false面试题: String s = new String(“abc”);方式创建对象,在内存中创建了几个对象? 两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。 2.只要其中一个是变量,结果就在堆中。 3.如果拼接的结果调用intern()方法,返回值就在常量池中
代码举例: String s1 = "javaEE"; String s2 = "hadoop"; String s3 = "javaEEhadoop"; String s4 = "javaEE" + "hadoop"; String s5 = s1 + "hadoop"; String s6 = "javaEE" + s2; String s7 = s1 + s2; System.out.println(s3 == s4);//true System.out.println(s3 == s5);//false System.out.println(s3 == s6);//false System.out.println(s3 == s7);//false System.out.println(s5 == s6);//false System.out.println(s5 == s7);//false System.out.println(s6 == s7);//false String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop” System.out.println(s3 == s8);//true String s1 = "javaEEhadoop"; String s2 = "javaEE"; String s3 = s2 + "hadoop"; System.out.println(s1 == s3);//false final String s4 = "javaEE";//s4:常量 String s5 = s4 + "hadoop"; System.out.println(s1 == s5);//true5)String的常用方法:
int length():返回字符串的长度: return value.length char charAt(int index): 返回某索引处的字符return value[index] boolean isEmpty():判断是否是空字符串:return value.length == 0 String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写 String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写 String trim():返回字符串的副本,忽略前导空白和尾部空白 boolean equals(Object obj):比较字符串的内容是否相同 boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写 String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+” int compareTo(String anotherString):比较两个字符串的大小 String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。 String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。 boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束 boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始 boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始 boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引 int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始 int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引 int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索 注:indexOf和lastIndexOf方法如果未找到都是返回-1 替换: String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。 String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。 String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。 String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 匹配: boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。 切片: String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。 String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。6)String与其它结构的转换
-
String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str) 基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)
-
String --> char[]:调用String的toCharArray() char[] --> String:调用String的构造器
-
String --> byte[]:调用String的getBytes() 解码:byte[] --> String:调用String的构造器
21、String、StringBuffer、StringBuilder三者的对比
String:不可变的字符序列;底层使用char[]存储 StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储 StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
StringBuffer与StringBuilder的内存解析
以StringBuffer为例: //char[] value = new char[0]; String str = new String(); //char[] value = new char[]{'a','b','c'}; String str1 = new String("abc"); //char[] value = new char[16];底层创建了一个长度是16的数组。 StringBuffer sb1 = new StringBuffer(); System.out.println(sb1.length()); sb1.append('a');//value[0] = 'a'; sb1.append('b');//value[1] = 'b'; //char[] value = new char["abc".length() + 16]; StringBuffer sb2 = new StringBuffer("abc"); System.out.println(sb2.length());//3,不是16扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。 默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中。开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity),在使用StringBuffer时,指定一个容量。
执行效率:StringBuilder > StringBuffer > String
StringBuffer、StringBuilder中的常用方法 增:append(xxx) 删:delete(int start,int end) 改:setCharAt(int n ,char ch) / replace(int start, int end, String str) 查:charAt(int n ) 插:insert(int offset, xxx) 长度:length();
篇幅太长,请看下一篇
版权声明:本文标题:Java进阶必看100条(一) 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686531603a78553.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论