首页 > 编程语言 > 详细

Java基础(二)

时间:2019-11-27 10:12:07      阅读:57      评论:0      收藏:0      [点我收藏+]

技术分享图片

进程与线程

  1. 进程:执行中的程序,一个进程至少包含一个线程;
  2. 线程:进程中负责程序执行的执行单元,线程本身依靠程序进行运行,线程是程序中的顺序控制流,只能使用分配给程序的资源和环境;
  3. 单线程:程序中只存在一个线程,实际上主方法就是一个主线程;
  4. 多线程:在一个程序中运行多个任务,目的是更好地使用CPU资源。

线程的状态

  1. 初始状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
  5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
    下面为线程中的 7 中非常重要的状态:(有的书上也只有认为前五种状态,而将“锁池”和“等待池”都看成是“阻塞”状态的特殊情况,这种认识也是正确的,但是将“锁池”和“等待池”单独分离出来有利于对程序的理解)。

技术分享图片

创建线程常用的方法

  1. 继承Thread类
    • 在这子类中重写run方法,在run方法内写线程任务代码
    • 创建该子类实例,即是创建了一个线程实例
    • 调用该实例的start方法来启动该线程
  2. 实现Runnable接口
    • 该类去实现接口的run方法,run方法内写线程任务代码
    • 创建该类实例,把该实例当作一个标记target传给Thread类,如:Thread t = new Thread(该类实例);即创建一个线程对象
    • 调用线程的star方法来启用该线程

继承Thread程序示例

/**
 * 继承Thread类
 * 1.在这子类中重写run方法,在run方法内写线程任务代码
 * 2.创建该子类实例,即是创建了一个线程实例
 * 3.调用该实例的start方法来启动该线程
 */
public class ThreadTest extends Thread {

    @Override
    public void run() {
        System.out.printf("当前线程ID: %s %n", Thread.currentThread().getId());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            ThreadTest threadTest = new ThreadTest();
            threadTest.start();
        }
    }

}

继承Thread示例输出结果

当前线程ID: 16 
当前线程ID: 13 
当前线程ID: 12 
当前线程ID: 14 
当前线程ID: 15 

实现Runnable程序示例

/**
 * 实现Runnable接口
 * 1.该类去实现接口的run方法,run方法内写线程任务代码
 * 2.创建该类实例,把该实例当作一个标记target传给Thread类,如:Thread t = new Thread(该类实例);即创建一个线程对象
 * 3.调用线程的start方法来启用该线程
 */
public class RunnableTest implements Runnable {

    @Override
    public void run() {
        System.out.printf("当前线程ID: %s %n", Thread.currentThread().getId());
    }

    public static void main(String[] args) {
        RunnableTest runnableTest = new RunnableTest();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(runnableTest);
            thread.start();
        }
    }

}

实现Runnable示例输出结果

当前线程ID: 12 
当前线程ID: 14 
当前线程ID: 13 
当前线程ID: 16 
当前线程ID: 15 

堆(Heap)&栈(Stack)&静态区/方法区

  • 堆区(Heap):代表了数据,专门用来保存对象的实例(new 创建的对象和数组),实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在Stack中)
    • 存储的全部是对象及包装类数据(如Integer,String等将基本数据类型包装起来的类),每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令);
    • jvm只有一个堆区,被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身;
  • 栈区(Stack):代表了处理逻辑,用来保存对象的地址,对象实例在堆中分配好以后,需要在栈中保存一个4字节的堆内存地址,用来定位该对象实例在堆中的位置,便于找到该对象实例。
    • 每个线程包含一个栈区,栈中只保存基础数据类型和堆中自定义对象的引用(不是对象),对象都存放在堆区中;
    • 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问;
    • 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
  • 静态区/方法区
    • 方法区又叫静态区,跟堆一样,被所有的线程共享。
    • 方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
    • 静态常量存放在方法区的常量区中,静态常量初始化后不可更改。
关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤: 
(1)先定义一个名为str的对String类的对象引用变量:String str; 
(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,
接着创建一个新的String类的对象o,并将o 的字符串值指向这个地址,而且在栈中这个地址旁
边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。 
(3)将str指向对象o的地址。 
值得注意的是,一般String类中字符串值都是直接存值的。
但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用! 
            
int a = 3; 
int b = 3; 
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,
没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的
引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

值传递和引用传递的区别和联系

  • 值传递(形式参数类型是基本数据类型)
    • 只要是基本类型传递,都是值传递;
    • 方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是用实际参数的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形式参数值的改变不影响实际参数的值。
  • 引用传递(形式参数类型是引用数据类型)
    • 也称为地址传递,针对于基本类型进行封装(比如对象),对封装(对象)进行传递,是引用传递。
    • 方法调用时,实际参数是对象(或数组),这时实际参数与形式参数指向同一个地址,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,这个结果在方法结束后被保留了下来,所以方法执行中形式参数的改变将会影响实际参数。

构造函数&常量的理解

  • 构造函数:是类在实例化成对象时用来做一些事情的,而这些事情是该对象被创建时必须做的事。例如初始化属性,但不限于此。另外我们可以对构造函数进行重载,是让我们在类的实例化时能够更多元化。
  • 常量:简单地说,用final修饰过的变量就叫常量,常量一旦定义了就不允许被修改。往大的说,定义常量,是不想让某些固定的属性或方法被调用后改变了值,或者被继承后重写。往底层说,常量存放在常量池里,在类加载之前就已经被加载,且不会改变

List、Map、Set存取元素时的特点

  • List
    • 以特定次序来持有元素,可有重复元素。即,有序可重复。
    • 访问时可以使用for循环,foreach循环,iterator迭代器迭代。
  • Set
    • Set 无法拥有重复元素,内部排序。即,无序不可重复。
    • 访问时可以使用foreach循环,iterator迭代器迭代。
  • Map
    • Map保存 key-value 值,一一映射。key值 是无序,不重复的,value值可重复。
    • 访问时可以map中key值转为set存储,然后迭代这个set,用map.get(key)获取value。

String,StringBuffer,StringBuilder的区别

  1. 可变性
    • String类中使用字符数组保存字符串,但因为有“final”修饰符,所以不可变;
    • StringBuilder与StringBuffer都继承自AbstractStringBuilder类,AbstractStringBuilder类也是使用字符数组char[]保存字符串,char[]长度可变。
  2. 线程安全性
    • String中的对象是不可变的,也就可以理解为常量,显然线程安全;
    • StringBuffer对方法加了Sychronized同步锁或者对调用的方法加了Sychronized同步锁,所以是线程安全的;
    • StringBuilder并没有对方法进行加Sychronized同步锁,所以是非线程安全的。
  3. 使用场景
    • 在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算;
    • 在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装;
    • 在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。

单例模式(Singleton)

// 饿汉
public class HungerSingleton {

    private static HungerSingleton hungerSingleton = new HungerSingleton();

    HungerSingleton () {};

    public static HungerSingleton getInstance() {
        return hungerSingleton;
    }

}

// 懒汉
public class LazySingleton {

    private static LazySingleton instance = null;

    LazySingleton() {};

    public static LazySingleton getInstance() {
        return instance == null ? new LazySingleton() : instance;
    }

}

Java基础(二)

原文:https://www.cnblogs.com/maxiaobing/p/11939310.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!