Java基本核心语法

139 阅读22分钟

Java 基础语法

一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。

public class HelloWorld {
    /* 第一个Java程序
     * 它将打印字符串 Hello World
     */
    public static void main(String []args) {
        System.out.println("Hello World"); // 打印 Hello World
    }
}

image.png

Java 对象和类

类可以看成是创建 Java 对象的模板。

image.png 一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。

一个类可以拥有多个方法。

构造方法

每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。

public class Puppy{
    public Puppy(){ 
    } 
    public Puppy(String name){
        // 这个构造器仅有一个参数:name
    } 
}

创建对象

对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字 new 来创建一个对象。
  • 初始化:使用 new 创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子:

public class Puppy{
    public Puppy(String name){
        //这个构造器仅有一个参数:name 
        System.out.println("小狗的名字是 : " + name );
    }
    public static void main(String[] args){
        // 下面的语句将创建一个Puppy对象
        Puppy myPuppy = new Puppy( "tommy" ); 
    }
}

Java 基本数据类型

变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。

内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。

image.png

Java 的两大数据类型:

  • 内置数据类型
  • 引用数据类型

内置数据类型

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

byte:

  • byte 数据类型是8位、有符号的,以二进制补码表示的整数;
  • 最小值是 -128(-2^7);
  • 最大值是 127(2^7-1);
  • 默认值是 0;
  • byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
  • 例子:byte a = 100,byte b = -50。

short:

  • short 数据类型是 16 位、有符号的以二进制补码表示的整数
  • 最小值是 -32768(-2^15);
  • 最大值是 32767(2^15 - 1);
  • Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是 0;
  • 例子:short s = 1000,short r = -20000。

int:

  • int 数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是 -2,147,483,648(-2^31);
  • 最大值是 2,147,483,647(2^31 - 1);
  • 一般地整型变量默认为 int 类型;
  • 默认值是 0 ;
  • 例子:int a = 100000, int b = -200000。

long:

  • long 数据类型是 64 位、有符号的以二进制补码表示的整数;
  • 最小值是 -9,223,372,036,854,775,808(-2^63);
  • 最大值是 9,223,372,036,854,775,807(2^63 -1);
  • 这种类型主要使用在需要比较大整数的系统上;
  • 默认值是 0L;
  • 例子: long a = 100000L,long b = -200000L。
    "L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。

float:

  • float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float 在储存大型浮点数组的时候可节省内存空间;
  • 默认值是 0.0f;
  • 浮点数不能用来表示精确的值,如货币;
  • 例子:float f1 = 234.5f。

double:

  • double 数据类型是双精度、64 位、符合 IEEE 754 标准的浮点数;
  • 浮点数的默认类型为 double 类型;
  • double类型同样不能表示精确的值,如货币;
  • 默认值是 0.0d;

char:

  • char 类型是一个单一的 16 位 Unicode 字符;
  • 最小值是 \u0000(十进制等效值为 0);
  • 最大值是 \uffff(即为 65535);
  • char 数据类型可以储存任何字符;
  • 例子:char letter = 'A';。

引用类型

  • 在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。
  • 对象、数组都是引用数据类型。
  • 所有引用类型的默认值都是null。
  • 一个引用变量可以用来引用任何与之兼容的类型。
  • 例子:Site site = new Site("Runoob")。

Java 常用类

String 类

字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。

创建字符串最简单的方式如下:

String str = "Runoob";

String 创建的字符串存储在公共池中,而 new 创建的字符串对象在堆上

String s1 = "Runoob"; // String 直接创建
String s2 = "Runoob"; // String 直接创建
String s3 = s1; // 相同引用
String s4 = new String("Runoob"); // String 对象创建
String s5 = new String("Runoob"); // String 对象创建

image.png

String类型不可变

image.png

实例中的 s 只是一个 String 对象的引用,并不是对象本身,当执行 s = "Runoob";  创建了一个新的对象 "Runoob",而原来的 "Google" 还存在于内存中。

Java StringBuffer 和 StringBuilder 类

当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。

Java 数组

Java 语言中提供的数组是用来存储固定大小的同类型元素。

double[] myList; // 首选的方法

dataType[] arrayRefVar = new dataType[arraySize];

日期时间

Date

java.util 包提供了 Date 类来封装当前的日期和时间。

** int compareTo(Date date)** 比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。

Date日期比较

Java使用以下三种方法来比较两个日期:

  • 使用 getTime() 方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。
  • 使用方法 before(),after() 和 equals()。例如,一个月的12号比18号早,则 new Date(99, 2, 12).before(new Date (99, 2, 18)) 返回true。
  • 使用 compareTo() 方法,它是由 Comparable 接口定义的,Date 类实现了这个接口。

SimpleDateFormat 格式化日期

import java.util.*;
import java.text.*;

public class DateDemo {
   public static void main(String args[]) {

      Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");

      System.out.println("Current Date: " + ft.format(dNow));
   }
}

Calendar类

创建一个代表系统当前日期的Calendar对象

Calendar c = Calendar.getInstance();//默认是当前日期

Java 流(Stream)、文件(File)和IO

一个流被定义为一个数据序列。输入流用于从源读取数据,输出流用于向目标写数据。

image.png

下面将要讨论的两个重要的流是 FileInputStream 和 FileOutputStream

FileInputStream

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。

有多种构造方法可用来创建对象。

可以使用字符串类型的文件名来创建一个输入流对象来读取文件:

InputStream f = new FileInputStream("C:/java/hello");

也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:

File f = new File("C:/java/hello");

InputStream in = new FileInputStream(f);

FileOutputStream

该类用来创建一个文件并向文件中写数据。

如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。

有两个构造方法可以用来创建 FileOutputStream 对象。

使用字符串类型的文件名来创建一个输出流对象:

OutputStream f = new FileOutputStream("C:/java/hello")

也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:

File f = new File("C:/java/hello");

OutputStream fOut = new FileOutputStream(f);

我以一个文件复制程序来说,顺便演示一下缓存区的使用。( Java I/O默认是不缓冲流的,所谓“缓冲”就是先把从流中得到的一块字节序列暂存在一个被称为buffer的内部字节数组里,然后你可以一下子取到这一整块的字节数据,没有缓冲的流只能一个字节一个字节读,效率孰高孰低一目了然。有两个特殊的输入流实现了缓冲功能,一个是我们常用的BufferedInputStream. )

package com.hxw.io;
import java.io.*;
 
public class FileCopy {
 
  public static void main(String[] args) {
     // TODO自动生成的方法存根
     byte[] buffer=new byte[512];   //一次取出的字节数大小,缓冲区大小
     int numberRead=0;
     FileInputStream input=null;
     FileOutputStream out =null;
     try {
        input=new FileInputStream("D:/David/Java/java 高级进阶/files/tiger.jpg");
        out=new FileOutputStream("D:/David/Java/java 高级进阶/files/tiger2.jpg"); //如果文件不存在会自动创建
       
        while ((numberRead=input.read(buffer))!=-1) {  //numberRead的目的在于防止最后一次读取的字节小于buffer长度,
           out.write(buffer, 0, numberRead);       //否则会自动被填充0
        }
     } catch (final IOException e) {
        // TODO自动生成的 catch 块
        e.printStackTrace();
     }finally{
        try {
           input.close();
           out.close();
        } catch (IOException e) {
           // TODO自动生成的 catch 块
           e.printStackTrace();
        }
       
     }
  }
 
}

FileReader

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
 
public class Main {
    public static void main(String[] args) {
        try {
            Reader r = new FileReader("C:\\Users\\Administrator\\Desktop\\a.txt");
            int n;
            char[] chars = new char[3];
            while ((n = r.read(chars)) != -1) {
                String s = new String(chars,0,n);
                System.out.println(s);
            }
            r.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
}

FileWriter

import java.io.*;
 
public class Main {
    public static void main(String[] args) {
        try {
            Reader r = new FileReader("C:\\Users\\Administrator\\Desktop\\a.txt");
            Writer w = new FileWriter("C:\\Users\\Administrator\\Desktop\\b.txt");
            int n;
            char[] chars = new char[3];
            while ((n = r.read(chars)) != -1) {
                w.write(chars,0,n);
            }
            r.close();
            w.flush();
            w.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
}

字节字符转换流

OutputStreamWriter类 OutputStreamWriter:可以将输出的字节流转换为字符流的输出形式

InputStreamReader类 InputStreamReader:将输入的字节流转换为字符流输入形式。

缓存流

在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

字节缓存流 构造方法: public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流,注意参数类型为InputStream。 public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流,注意参数类型为OutputStream。

字符缓存流 构造方法: public BufferedReader(Reader in) :创建一个新的缓冲输入流,注意参数类型为Reader。 public BufferedWriter(Writer out): 创建一个新的缓冲输出

异常

异常是中断程序运行的问题

image.png

异常的处理

运行时异常(系统异常)不需要预处理,通过规范的代码可以避免产生这种异常

受检异常(编译时异常)必须预处理,否则编译报错,有两种预处理方式 : 捕获处理 抛出处理

异常捕获处理

try、catch和finally

try {
	
} catch (OneException e) {
	
} catch (TwoException e) {
	
} finally {
}

try中包含了可能产生异常的代码

try后面是catch,catch可以有一个或多个,catch中是需要捕获的异常

当try中的代码出现异常时,出现异常下面的代码不会执行,马上会跳转到相应的catch语句块中,如果没有异常不会跳转到catch中

finally表示,不管是出现异常,还是没有出现异常,finally里的代码都执行,

throws 与throw关键字

throw 和 throws 的区别? throw throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。

throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。

throw一般用于抛出自定义异常。

throws throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。

throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。

throws表示出现异常的一种可能性,并不一定会发生这种异常。

自定义异常

/**
 * 自定义受控异常(编译时、检查时异常(checkedException)),继承Exception
 */
class MyCheckedException extends Exception {

    public MyCheckedException() {
        //调用父类的默认构造函数
        super();
    }

    public MyCheckedException(String message) {
        //手动调用父类的构造方法
        super(message);
    }

}

继承于Exception,是编译异常。必须进行try catch。

RuntimeException是运行异常。

Java 封装

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问

实现Java封装的步骤

  1. 修改属性的可见性来限制对属性的访问(一般限制为private),例如:
public class Person {
private String name;
private int age; 
}

这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。

  1. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:
public class Person{
    private String name; 
    private int age;  
    public int getAge(){ 
        return age;
    }
    public String getName(){
        return name;
    }
   public void setAge(int age){
       this.age = age;
   }
   public void setName(String name){
       this.name = name;
   } 
}

采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。

Java 继承

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

class 父类 {
} 
class 子类 extends 父类 {
}
  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。

重写与重载之间的区别

区别点重载方法重写方法
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)

Java 多态

多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:

image.png

  • 继承
  • 重写
  • 父类引用指向子类对象:Parent p = new Child();

Java 抽象类

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

Java 接口

接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

Java 枚举(enum)

enum Color 
{ 
    RED, GREEN, BLUE; 
} 
public class Test
{
    // 执行输出结果
    public static void main(String[] args)\
    {
        Color c1 = Color.RED;
        System.out.println(c1);
    }
}
执行以上代码输出结果为:RED

Java 集合框架

image.png

Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,

image.png

Set和List的区别

    1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
    1. Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变  <实现类有HashSet,TreeSet>
    1. List 和数组类似,可以动态增长,根据实际存储的数据的长度自动增长 List 的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变  <实现类有ArrayList,LinkedList,Vector>  。

ArrayList

ArrayList<E> objectName =new ArrayList<>();  // 初始化
  • E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型
  • ArrayList 是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  ArrayList<String> sites = new ArrayList<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Weibo");
        System.out.println(sites.get(1));  // 访问第二个元素
        sites.set(2"Wiki"); // 第一个参数为索引位置,第二个为要修改的值
        sites.remove(3); // 删除第四个元素
        System.out.println(sites.size());
        for (int i = 0; i < sites.size(); i++) {
            System.out.println(sites.get(i));
        }
        for (String i : sites) {
            System.out.println(i);
        }

LinkedList

LinkedList<E> list = new LinkedList<E>();   // 普通创建方法

用法和ArrayList相似

HashSet

HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。

HashSet 允许有 null 值。

HashSet 是无序的,即不会记录插入的顺序。

HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对

HashSet<String> sites = new HashSet<String>();
HashSet<String> sites = new HashSet<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Zhihu");
        sites.add("Runoob");  // 重复的元素不会被添加
        sites.remove("Taobao");  // 删除元素,删除成功返回 true,否则为 false
        sites.clear();//删除所有的元素
       
        for (String i : sites) {
            System.out.println(i);
        }

HashMap

HashMap<Integer, String> Sites = new HashMap<Integer, String>();
// 创建 HashMap 对象 Sites
        HashMap<Integer, String> Sites = new HashMap<Integer, String>();
        // 添加键值对
        Sites.put(1"Google");
        Sites.put(2"Runoob");
        Sites.put(3"Taobao");
        Sites.put(4"Zhihu");
        System.out.println(Sites.get(3));//使用 get(key) 方法来获取 key 对应的 value:
        Sites.remove(4);//使用 remove(key) 方法来删除 key 对应的键值对(key-value):
        // 输出 key 和 value
        for (Integer i : Sites.keySet()) {
            System.out.println("key: " + i + " value: " + Sites.get(i));
        }
        
        // 返回所有 value 值
        for(String value: Sites.values()) {
          // 输出每一个value
          System.out.print(value + ", ");
        }

 Iterator(迭代器)

Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList和 HashSet 等集合。

// 创建集合
        ArrayList<String> sites = new ArrayList<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Zhihu");

        // 获取迭代器
        Iterator<String> it = sites.iterator();

        // 输出集合中的所有元素
        while(it.hasNext()) {
            System.out.println(it.next());
        }

多线程编程

一个线程的生命周期

image.png

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start()  这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run() ,此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

java语言中,实现线程有两种方式

第一种方式:

编写一个类,直接 继承 java.lang.Thread重写 run方法

public class ThreadTest02 {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        // 启动线程
        //t.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
        t.start();
        // 这里的代码还是运行在主线程中。
        for(int i = 0; i < 1000; i++){
            System.out.println("主线程--->" + i);
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        // 编写程序,这段程序运行在分支线程中(分支栈)。
        for(int i = 0; i < 1000; i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

注意:

t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)

t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。 run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

第二种方式:

编写一个类,实现 java.lang.Runnable 接口,实现 run方法

public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable()); 
        // 启动线程
        t.start();
        
        for(int i = 0; i < 100; i++){
            System.out.println("主线程--->" + i);
        }
    }
}

// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 100; i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。

获取当前线程对象、获取线程对象名字、修改线程对象名字

方法名作用
static Thread currentThread()获取当前线程对象
String getName()获取线程对象名字
void setName(String name)修改线程对象名字
class MyThread2 extends Thread {
    public void run(){
        for(int i = 0; i < 100; i++){
            // currentThread就是当前线程对象。当前线程是谁呢?
            // 当t1线程执行run方法,那么这个当前线程就是t1
            // 当t2线程执行run方法,那么这个当前线程就是t2
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName() + "-->" + i);

            //System.out.println(super.getName() + "-->" + i);
            //System.out.println(this.getName() + "-->" + i);
        }
    }
}

关于线程的sleep方法

静态方法:Thread.sleep(1000);

参数是毫秒

作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。

这行代码出现在A线程中,A线程就会进入休眠。

这行代码出现在B线程中,B线程就会进入休眠。

Thread.sleep()方法,可以做到这种效果:

间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

Java中合理结束一个进程的执行(常用)

public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunable4 r = new MyRunable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

        // 模拟5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终止线程
        // 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
        r.run = false;
    }
}

class MyRunable4 implements Runnable {

    // 打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            if(run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                // return就结束了,你在结束之前还有什么没保存的。
                // 在这里可以保存呀。
                //save....

                //终止当前线程
                return;
            }
        }
    }
}

为什么if()语句要在循环里面? 由于一个线程一直运行此程序,要是if判断在外面只会在启动线程时判断并不会结束,因此需要每次循环判断一下标记。

什么时候数据在多线程并发的环境下会存在安全问题呢

满足三个条件:

  1. 条件1:多线程并发
  2. 条件2:有共享数据
  3. 条件3:共享数据有修改的行为

怎么解决线程安全问题呢?

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?

线程排队执行。(不能并发)。用排队执行解决线程安全问题。

这种机制被称为:线程同步机制。

专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。

线程同步就是线程排队了,线程排队了就会 牺牲一部分效率 ,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

两个专业术语:

异步编程模型: 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。

其实就是:多线程并发(效率较高。)

异步就是并发。

同步编程模型: 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。

效率较低。线程排队执行。

同步就是排队。

更多多线程知识点