Java_2_(1、2、3_共3篇)_00001

contents

异常

1、异常概述与异常体系结构。
2、常见异常
3、异常处理机制一:try-catch-finally
4、异常处理机制二:throws
5、手动抛出异常:throw
6、用户自定义异常类

异常概述与异常体系结构

Java程序在执行过程中所发生的异常事件可分为两类:
1、Error
2、Exception

异常分类:
1、编译时异常
2、运行时异常

在这里插入图片描述

  1. 运行时异常
    指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。

    对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。

  2. 编译时异常
    指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。

    对于这类异常,如果程序不处理,可能会带来意想不到的结果。

常见异常

反射

目录

1、Java反射机制概述。
2、理解Class类并获取Class实例。
3、类的加载与ClassLoader的理解。
4、创建运行时类的对象。
5、获取运行时类的完整结构。
6、调用运行时类的指定结构。
7、反射的应用:动态代理。

Java反射机制概述

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为反射。
    在这里插入图片描述
  • 动态语言 VS 静态语言
    1 、动态语言
    是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
    主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
    2 、静态语言
    与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

    Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
    Java的动态性让编程的时候更加灵活。

Java反射机制提供的功能:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时获取泛型信息。
  • 在运行时调用任意一个对象的成员变量和方法。
  • 在运行时处理注解。
  • 生成动态代理。

反射相关的主要API:

  • java.lang.Class: 代表一 个类。
  • java.lang.reflect.Method: 代表类的方法。
  • java.lang.reflect.Field: 代表类的成员变量。
  • java.lang.reflect.Constructor: 代表类的构造。
package com.one.java;

import org.junit.Test;

import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {


    //反射之前,对于Person的操作。
    @Test
    public void test00000() {

        //1.创建Person类的对象。
        Person person = new Person("宋江", 50);

        //2.通过对象,调用其内部的属性、方法。
        person.age = 10;
        System.out.println(person.toString());

        person.show();

        /*
               在Person类外部,不可以通过Person类的对象调用其内部私有结构。
               比如:name属性、showNation()以及私有的构造器。
        */
        
    }

    //反射之后,对于Person的操作。
    @Test
    public void test00001() throws Exception{
    	
        Class<Person> clazz = Person.class;
        
        //1.通过反射,创建Person类的对象
        Constructor<Person> constructor = clazz.getConstructor(String.class, int.class);
        Object obj = constructor.newInstance("宋江", 50);
        Person person = (Person)obj;
        System.out.println(person.toString());
        
        //2.通过反射,调用对象指定的属性、方法。
        //调用属性
        Field age = clazz.getDeclaredField("age");
        age.set(person, 60);
        
        System.out.println(person.toString());

        //调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(person);

        System.out.println("*******************************");

        //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
        //调用私有的构造器
        Constructor<Person> constructor1 = clazz.getDeclaredConstructor(String.class);
        constructor1.setAccessible(true);
        Person person1 = constructor1.newInstance("卢俊义");
        System.out.println(person1);

        //调用私有的属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(person1, "吴用");
        System.out.println(person1);

        //调用私有的方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        String nation = (String)showNation.invoke(person1, "中国"); //相当于String nation = person1.showNation("中国")
        System.out.println(nation);

    }
    
    /*
	    疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
	    建议:直接new的方式。
	    什么时候会使用:反射的方式。 反射的特征:动态性。
	    
	    疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待这两个技术?
	    不矛盾。
	*/
    
    /*
	    关于java.lang.Class类的理解。
	  1、类的加载过程:
		    程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
		    接着使用java.exe命令对某个字节码文件进行解释运行。
		    相当于将某个字节码文件加载到内存中。此过程就称为类的加载。
		    加载到内存中的类,我们就称为运行时类。
		    此运行时类,就作为Class的一个实例。
	
	  2、Class的实例就对应着一个运行时类。
	  3、加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
     */
    
    //获取Class的实例的方式。
    @Test
    public void test00002() throws ClassNotFoundException {
    	
        //方式一:调用运行时类的属性:.class。
        Class<Person> clazz = Person.class;
        System.out.println(clazz);
        
        System.out.println("*************");
        
        //方式二:通过运行时类的对象调用getClass()
        Person person = new Person();
        Class<?> clazz1 = person.getClass();
        System.out.println(clazz1);

        System.out.println("*************");
        
        //方式三:调用Class的静态方法:forName(String classPath)
        Class<?> clazz2 = Class.forName("com.one.java.Person");
        //clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz2);

        System.out.println("*************");
        
        System.out.println(clazz == clazz1);
        System.out.println(clazz == clazz2);
        System.out.println(clazz1 == clazz2);

        System.out.println("*************");
        
        //方式四:使用类的加载器:ClassLoader
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class<?> clazz3 = classLoader.loadClass("com.one.java.Person");
        System.out.println(clazz3);

        System.out.println(clazz == clazz3);

    }

    //Class实例可以是哪些结构的说明。
    @Test
    public void test0003(){
    	
    	//类
        Class<Object> clazz = Object.class;
        System.out.println(clazz);
        
        //接口
        Class<?> clazz1 = Comparable.class;
        System.out.println(clazz1);
        
        Class<?> clazz2 = String[].class;
        System.out.println(clazz2);
        
        Class<?> clazz3 = int[][].class;
        System.out.println(clazz3);
        
        //枚举类
        Class<?> clazz4 = ElementType.class; 
        System.out.println(clazz4);
        
        //注解
        Class<?> clazz5 = Override.class; 
        System.out.println(clazz5);
        
        Class<?> clazz6 = int.class; 
        System.out.println(clazz6);
        
        Class<?> clazz7 = void.class;
        System.out.println(clazz7);
        
        Class<?> clazz8 = Class.class;
        System.out.println(clazz8);
        
        int[] array = new int[1];
        int[] array1 = new int[2];
        Class<? extends int[]> clazz9 = array.getClass();
        Class<? extends int[]> clazz10 = array1.getClass();
        System.out.println(clazz9);
        System.out.println(clazz10);
        
        //只要数组的元素类型与维度一样,就是同一个Class
        System.out.println(clazz9 == clazz10);

    }
    
}

Java基础

JDK(Java development kit,Java开发工具包)是整个java的核心,包括了Java运行环境、Java工具和Java基础的类库。

编译器:javac.exe
解释器:java.exe

JRE主要由Java虚拟机(JVM)和一些标准类库组成。

源文件中只能有一个类用public修饰,源程序文件的名字必须和这个public类的名字一致。

java语言的基本特点
1、面向对象性:两个基本概念:类、对象;三大特性:封装、继承、多态
2、健壮性:吸收了C/C++语言的优点,去掉了其影响程序健壮性的部分(如指针、内存的申请与释放等),提供了一个相对安全的内存管理和访问机制。
3、跨平台性:通过Java语言编写的应用程序在不同的系统平台上都可以运行。

为什么要设置path?
目的是为了在控制台的任何文件路径下,都可以调用jdk指定目录下的所有指令。
path环境变量:windows操作系统执行命令时所要搜寻的路径
为什么要配置path:希望java的开发工具(javac.exe,java.exe)在任何的文件路径下都可以执行成功

JDK、JRE、JVM的关系是什么?
在这里插入图片描述

自己使用java文档注释的方式编写程序,并用javadoc命令解析
答案后续补上

GC是什么? 为什么要有GC?
GC是垃圾收集的意思(Gabage Collection)。
没有或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的。Java语言没有提供释放已分配内存的显示操作方法。

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。
当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
可以。程序员可以手动执行System.gc();通知GC运行,但是Java语言规范并不保证GC一定会执行。

字符集

Java字符集采用的是通用的国际标准字符集Unicode(万国码),而并非计算机系统常用的ASCII码字符集。Unicode字符集使用2字节即16位来表示一个字符,共有65536个字符。Unicode字符集能表示迄今为止人类的所有语言文字,包括以下几类字符:
1、ASCII码字符集里的英文字母A~Z、a~z以及数字0~9。
2、多国文字字符,包括汉字、日文、朝鲜文、希腊文等。
3、常见的特殊符号字符集,如“&”、“¥”、“@”。

数据类型

Java语言是一种强类型语言,即每个变量或者表达式都必须有确定的数据类型,在对变量赋值时要进行数据类型的兼容性检查。

char

Java语言采用Unicode字符集,即用2字节来存储一个字符。

Java中char型变量可与整型变量互换。char类型的值可以自然转换为int类型,而从int类型转换为char类型时需要强制执行,即强制类型转换。

由于char型可以自然转换为int型,在许多情况下可以对字符进行算数运算操作,就好像它们是整数一样。例如,可以将两个字符相加,或者对一个字符变量的值进行增减操作。

package a_00000;

public class Test00000 {

	public static void main(String[] args) {
		
		char c = 'a';
		System.out.println(c); //print:a
		int i = (int)c;
		System.out.println(i); //print:97
		
		int i1 = c + 1;	
		c = (char)i1;
		System.out.println(c); //print:b
		
	}

}

转义字符

转义字符意义
\n换行
\t横向跳格
单引号字符
"多引号字符
\反斜杠字符
\ddd1~3位八进制数表示的字符
\uxxxx1~4位十六进制数表示的字符
\r回车
\f走纸换页
\b退格

表达式

表达式的优先级:

优先级运算符
1. []    ()
2++    –    !    ~    instanceof
3new    (type)
4*    /    %
5+    -
6>>    <<    >>>
7<    >    >=    <=
8==    !=
9&
10
11|
12&&
13||
14?:
15=    +=    -=    *=    /=    %=
16&=    |=    <<=    >>=    >>>=

输入输出

package a_00000;

import java.io.IOException;

public class Test00000 {

	/*
	 * 输入一个字母,使其大小写转换。
	 * */
	public static void main(String[] args) {
		
		int a, b;
		
		System.out.print("Please input a letter:");
		
		try {
			
			a = System.in.read();
			
			if(a>=65 && a<=90) {
				b = a + 32;
			}else {
				b = a - 32;
			}
			
			System.out.println((char)a + " has been transformed into:" + (char)b);
			
		} catch (IOException e) {			
			e.printStackTrace();
		}
			
	}

}

多线程

multithreading

1.1 概念:程序、进程、线程

1、程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码(静态对象)。
2、进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。
(1)程序是静态的,进程是动态的。
(2)进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
3、线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
(1)若一个进程同一时间 并行执行多个线程,就是支持多线程的
(2)线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
(3)一个进程中的多个线程共享相同的内存单元、内存地址空间。它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能会带来安全的隐患。

1.2 单核CPU和多核CPU

1、单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时间单元特别短,所以感觉不出来。
2、如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
3、一个Java应用程序java.exe,至少有三个线程:main()主线程、gc()垃圾回收线程和异常处理线程。当然如果发生异常,会影响主线程。

1.3 并行与并发

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

1.4 多线程程序的优点

1、提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2、提高计算机系统CPU的利用率
3、改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和
修改

1.5 何时需要多线程

1、程序需要同时执行两个或多个任务。
2、程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3、需要一些后台运行的程序时

1.6 线程的创建和启动方式一:继承Thread类

方式一:继承与Thread类
1、创建一个继承于Thread类的子类
2、重写Thread类的run() ->将此线程执行的操作声明在run()中
3、创建Thread类的子类的对象
4、通过此对象调用start()

需求:遍历10以内的所有的偶数

package multithreading;

/*
多线程的创建:方式一:继承与Thread类
1、创建一个继承于Thread类的子类
2、重写Thread类的run() ->将此线程执行的操作声明在run()中
3、创建Thread类的子类的对象
4、通过此对象调用start()

需求:遍历10以内的所有的偶数
*/
public class TestThread {

	public static void main(String[] args) {
		//3、创建Thread类的子类的对象
		MyThread myThread = new MyThread();
		//4、通过此对象调用start()
		myThread.start();

		for(int i=0; i<10; i++) {
			if(i%2==0) {
				System.out.println(i + "------------main------------");
			}
		}
	}

}

//1、创建一个继承于Thread类的子类
class MyThread extends Thread {
	
	//2、重写Thread类的run()
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			if(i%2==0) {
				System.out.println(i);
			}
		}
	}
}
package multithreading;

/*
多线程的创建:方式一:继承与Thread类
1、创建一个继承于Thread类的子类
2、重写Thread类的run() ->将此线程执行的操作声明在run()中
3、创建Thread类的子类的对象
4、通过此对象调用start()

需求:遍历10以内的所有的偶数
*/
public class TestThread {

	public static void main(String[] args) {
		//3、创建Thread类的子类的对象
		MyThread myThread = new MyThread();
		//4、通过此对象调用start();start()有两个作用:(1)启动当前线程(2)调用当前线程的run()
		myThread.start();

		/*问题1、不能通过直接调用run()的方式启动线程
		myThread.run();
		问题2、不能让已经启动的线程再次调用start(),会抛出异常IllegalThreadStateException
		Thread类中start()中源码:
		if (threadStatus != 0)
            throw new IllegalThreadStateException();
		myThread.start();
		*/
		
		//需要重新创建一个线程的对象
		MyThread myThread1 = new MyThread();
		myThread1.start();
		
		//如下仍然是在main线程中执行的
		for(int i=0; i<10; i++) {
			if(i%2==0) {
				System.out.println(Thread.currentThread().getName()+ ":" + i);
			}
		}
	}

}

//1、创建一个继承于Thread类的子类
class MyThread extends Thread {
	
	//2、重写Thread类的run()
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			if(i%2==0) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}

创建两个分线程,其中一个线程遍历10以内的偶数,另一个线程遍历10以内的奇数

package multithreading;

//创建两个分线程,其中一个线程遍历10以内的偶数,另一个线程遍历10以内的奇数
public class TestThread1 {

	public static void main(String[] args) {
		
		//MyThread myThread1 = new MyThread();
		//MyThread myThread2 = new MyThread();
		
		//myThread1.start();
		//myThread2.start();

		//创建Thread类的匿名子类的方式
		new Thread() {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					if(i % 2 == 0) {
						System.out.println(Thread.currentThread().getName() + ":" + i);
					}
				}
			}; 
		}.start();
		
		new Thread() {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					if(i % 2 != 0) {
						System.out.println(Thread.currentThread().getName() + ":" + i);
					}
				}
			}; 
		}.start();
	}

}

class MyThread1 extends Thread {
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			if(i % 2 == 0) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}

class MyThread2 extends Thread {
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			if(i % 2 != 0) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}

说明:
1、如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
2.、run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3、想要启动多线程,必须调用start()。
4、一个线程对象只能调用一次start()启动,如果重复调用了,则将抛出
“IllegalThreadStateException”异常

1.7 Thread类中的常用方法

1、start() 启动当前线程,调用当前线程的run();返回值void
2、run() 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中;线程在被调度时执行的操作
3、currentThread() 静态方法;static Thread currentThread();返回执行当前代码的线程;在Thread子类中就是this,通常用于主线程和Runnable实现类
4、getName 获取当前线程的名字;返回值:String
5、setName() 设置当前线程的名字
6、yield() 释放当前CPU的执行权;static void yield();
(1)暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
(2)若队列中没有同优先级的线程,忽略此方法
7、join() 在线程A中调用线程B的join(),此时线程A进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞状态。
(1)当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
(2)低优先级的线程也可以获得执行
8、stop() 已过时;当执行此方法时,强制结束当前线程
9、sleep(long millitime) 让当前线程“睡眠”指定的millitime毫秒。 在指定的millitime毫秒时间内,当前线程是阻塞状态
(1)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
(2)抛出InterruptedException异常
(3)static void sleep(long millis) :(指定时间:毫秒)
10、isAlive() 判断当前线程是否存活;返回boolean值

package multithreading;

/*
测试Thread类中的常用方法
1、start() 启动当前线程,调用当前线程的run()
2、run() 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3、currentThread() 静态方法,返回执行当前代码的线程
4、getName 获取当前线程的名字
5、setName() 设置当前线程的名字
6、yield() 释放当前CPU的执行权
7、join() 在线程A中调用线程B的join(),此时线程A进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞状态。
8、stop() 已过时;当执行此方法时,强制结束当前线程
9、sleep(long millitime) 让当前线程“睡眠”指定的millitime毫秒。 在指定的millitime毫秒时间内,当前线程是阻塞状态 
10、isAlive() 判断当前线程是否存活

线程的优先级
MAX_PRIORITY :10
MIN _PRIORITY :1
NORM_PRIORITY :5

如何获取和设置当前线程的优先级
getPriority()
setPriority(int p) 
高优先级的线程要抢占低优先级线程cpu的执行权,但是只是从概率上讲。高优先级的线程高概率的情况下被执行,
并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
*/
public class TestThread2 {

	public static void main(String[] args) {
		MyThread3 myThread3 = new MyThread3("Thread:1");
		
		//myThread3.setName("线程一");
		
		//设置分线程的优先级
		myThread3.setPriority(Thread.MAX_PRIORITY);
		
		myThread3.start();
		
		//给主线程命名
		Thread.currentThread().setName("主线程");
		
		//设置主线程的优先级
		Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
		for (int i = 0; i < 100; i++) {
			if(i % 2 == 0) {		
				
				System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
			}
			
//			if(i == 20) {
//				try {
//					myThread3.join(); //被子线程抢占了
//				} catch (InterruptedException e) {
//					
//					e.printStackTrace();
//				}
//			}
		}

		//System.out.println(myThread3.isAlive());
	}

}

class MyThread3 extends Thread {
	
	//通过构造器给线程起名字
	public  MyThread3(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i % 2 == 0) {
				
//				try {
//					sleep(100); //线程阻塞0.1秒钟
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				};
				
				System.out.println(Thread.currentThread().getName() + ":"  + Thread.currentThread().getPriority() + ":" + i);
			}
			
			//if(i % 20 ==0) {
				//yield(); //释放当前CPU的执行权
				//this.yield();
				//Thread.currentThread().yield();
			//}
		}
	}
}

1.8 线程的优先级

1、线程 的 优先级等级
(1)MAX_PRIORITY :10
(2)MIN _PRIORITY :1
(3)NORM_PRIORITY :5
2、涉及的方法
(1)getPriority() :返回线程优先值
(2)setPriority(int newPriority) :改变线程的优先级
说明:
(1)线程创建时继承父线程的优先级
(2)低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

1.9 线程的创建和启动方式二:实现Runnable接口

1、创建一个实现了Runnable接口的类
2、实现类去实现Runnable中的抽象方法:run()
3、创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5、通过Thread类的对象调用start()

package multithreading;

/*
创建多线程的方式二:实现Runnable接口
1、创建一个实现了Runnable接口的类
2、实现类去实现Runnable中的抽象方法:run()
3、创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5、通过Thread类的对象调用start()

比较创建线程的两种方式:
开发中优先选先实现Runnable接口的方式
原因:1、实现的方式没有类的单继承性的局限性
2、实现的方式更适合处理多个线程有共享数据的情况

联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
*/
public class TestRunnable {

	public static void main(String[] args) {
		
		//3、创建实现类的对象
		MyRunnable myRunnable = new MyRunnable();
	
		//4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
		Thread thread = new Thread(myRunnable);
		//thread.setDaemon(true); 将用户线程变成守护线程
		//5、通过Thread类的对象调用start()
		thread.setName("线程一");
		thread.start();
		
		//在启动一个线程,遍历100以内的偶数
		Thread thread1 = new Thread(myRunnable);
		thread1.setName("线程二");
		thread1.start();
	}

}

//1、创建一个实现了Runnable接口的类
class MyRunnable implements Runnable {

	//2、实现类去实现Runnable中的抽象方法:run()
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i % 2 == 0) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
		
	}
	
}

1.10 继承Thread类创建多线程并用同步代码块实现线程安全

package multithreading;

/*
创建3个窗口卖票,总票数为100
使用同步代码块解决继承Thread类的方式的线程安全问题

在继承Thread类创建多线程的方式中,慎用this来充当同步监视器,可以使用当前类充当监视器
*/
public class TestThread3 {

	public static void main(String[] args) {
		
		MyThread4 myThread1 = new MyThread4();
		MyThread4 myThread2 = new MyThread4();
		MyThread4 myThread3 = new MyThread4();
		
		myThread1.setName("窗口1");
		myThread2.setName("窗口2");
		myThread3.setName("窗口3");
		
		myThread1.start();
		myThread2.start();
		myThread3.start();

	}

}

class MyThread4 extends Thread {
	
	private static int ticket = 100;
	
	//static Object obj = new Object();
	
	@Override
	public void run() {
		
		while(true) {
			//synchronized (this) 错误的			
			//synchronized (obj) { //加同步锁
			synchronized (MyThread4.class) { //Class clazz = MyThread4.class MyThread4只会加载一次
				if(ticket>0) {
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
		
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
					ticket--;
				} else {
					break;
				}
			}
			
		}
		
	}
}

1.11 继承Thread类创建多线程并用同步方法解决线程安全

package multithreading;

/*
创建3个窗口卖票,总票数为100
使用同步方法解决继承Thread类的方式的线程安全问题
*/
public class TestThread4 {

	public static void main(String[] args) {
		
		MyThread4 myThread1 = new MyThread4();
		MyThread4 myThread2 = new MyThread4();
		MyThread4 myThread3 = new MyThread4();
		
		myThread1.setName("窗口1");
		myThread2.setName("窗口2");
		myThread3.setName("窗口3");
		
		myThread1.start();
		myThread2.start();
		myThread3.start();

	}

}

class MyThread5 extends Thread {
	
	private static int ticket = 100;
	
	@Override
	public void run() {
		
		while(true) {
			show();
		}
			
	}
	
	public static synchronized void show() { //必须是静态的
		if(ticket>0) {
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {

				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
			ticket--;
		}
	}
		
}

1.12 实现Runnable接口创建多线程并用代码块实现线程安全

package multithreading;

/*创建3个窗口卖票,总票数为100
问题:卖票过程中,出现了重票和错票
原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
解决方案:当一个线程A在操作ticket的时候,其他线程不能参与进来,直到线程A操作完ticket时,其他线程才可以开始操作ticket,
      这种情况即使线程A线程出现了阻塞,也不能被改变。
在Java中,通过同步机制来解决线程安全问题
方式一:同步代码块
synchronized(同步监视器) {
	//需要被同步的代码
}
说明:操作共享数据的代码,即为需要被同步的代码
     共享数据:多个线程共同操作的数据,比如ticket就是共享数据
   同步监视器(俗称:锁):任何一个类的对象,都可以充当锁
   要求:多个线程必须要共用同一把锁
  补充:在实现Runnable接口创建多线程的方式中,可以使用this充当监视器
 

方式二:同步方法
如果操作共享数据的代码正好完整的声明在一个方法中,将此方法声明同步的

同步的方式:
好处:解决了线程安全问题
局限性:操作同步代码时只能有一个线程参与,其他的线程等待,相当于是一个单线程的过程
*/
public class TestRunnable1 {

	public static void main(String[] args) {
		
		MyRunnable1 myRunnable = new MyRunnable1();
		
		Thread thread1 = new Thread(myRunnable);
		Thread thread2 = new Thread(myRunnable);
		Thread thread3 = new Thread(myRunnable);
		
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		thread3.setName("窗口3");
		
		thread1.start();
		thread2.start();
		thread3.start();

	}

}

class MyRunnable1 implements Runnable {

	private int ticket = 10;
	
	@Override
	public void run() {
		while(true) {
			synchronized (this) { //此时的this是唯一的MyRunnable对象
				if(ticket>0) {
					
					try {
						Thread.sleep(100); //阻塞0.1s
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName() + " : 卖票,票号为:" + ticket);
					
					ticket--;
				}else {
					break;
				}
			}
			
		}
		
	}
	
}

1.13 实现Runnable接口创建多线程并用同步方法实现线程安全

package multithreading;

/*
使用同步方法解决实现Runnable接口的线程安全问题

同步方法的总结
1、同步方法仍然涉及到同步监视器,只是不需要显示的声明
2、静态的同步方法,同步监视器是当前类本身
*/
public class TestRunnable2 {

	public static void main(String[] args) {
	
		MyRunnable2 myRunnable = new MyRunnable2();
		
		Thread thread1 = new Thread(myRunnable);
		Thread thread2 = new Thread(myRunnable);
		Thread thread3 = new Thread(myRunnable);
		
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		thread3.setName("窗口3");
		
		thread1.start();
		thread2.start();
		thread3.start();

	}

}

class MyRunnable2 implements Runnable {

	private int ticket = 100;
	
	@Override
	public void run() {
		while(true) {
	
			show();
		}
			
	}
	
	public synchronized void show() { //这里的同步监视器是this
		if(ticket>0) {
			
			try {
				Thread.sleep(100); //阻塞0.1s
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + " : 卖票,票号为:" + ticket);
			
			ticket--;
		}
	}

}

1.14 线程的分类

Java中的线程分为两类:一类是守护线程,一类是用户线程。
1、它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
2、守护线程是用来服务用户线程的,通过在start()前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
3、Java垃圾回收就是一个典型的守护线程。
4、若JVM中都是守护线程,当前JVM将退出。

1.15 线程的生命周期

JDK 中用Thread.State 类定义了线程的几种状态。
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的 五种状态:
1、新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2、就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
3、运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
4、阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
5、死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
加粗样式
在这里插入图片描述

1.16 单例设计模式之懒汉式实现线程安全

package multithreading;

//线程安全的单例模式之懒汉式
public class TestSingleton {

	public static void main(String[] args) {
		Singleton singleton = Singleton.getInstance();
		Singleton singleton1 = Singleton.getInstance();
		
		System.out.println(singleton == singleton1);
	}

}

class Singleton extends Thread{
	private Singleton() {}
	
	private static Singleton instance = null;
	
	//方式一:加同步代码块实现线程安全;效率稍差
	public static Singleton getInstance1() {
		
		synchronized (Singleton.class) {
			if(instance == null) {
				instance = new Singleton();
			}
			
			return instance;
		}
		
	}
	
	//方式二:加同步方法实现线程安全;效率稍差
	public static synchronized Singleton getInstance2() {
		if(instance == null) {
			instance = new Singleton();
		}
		
		return instance;
	}
	
	//方式三:效率更高
	public static Singleton getInstance() {
		
		if(instance == null) {
			
			synchronized (Singleton.class) {
				if(instance == null) {
					instance = new Singleton();
				}								
			}
			
		}
		
		return instance;
	}
}

1.17 线程死锁

1、不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2、出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
线程死锁代码演示

package multithreading;

/*
 演示线程的死锁问题
死锁:不同的线程分别占用对方需要的同步资源不放弃,
都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
*/
public class DeadLock1 {

	public static void main(String[] args) {
		
		StringBuilder stringBuilder = new StringBuilder();
		StringBuilder stringBuilder1 = new StringBuilder();
		
		new Thread() {
			@Override
			public void run() {
				synchronized (stringBuilder) {
					stringBuilder.append("a");
					stringBuilder1.append("1");
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized (stringBuilder1) {
						stringBuilder.append("b");
						stringBuilder1.append("2");
						
						System.out.println(stringBuilder);
						System.out.println(stringBuilder1);
					}
				}
							
			};
		}.start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				
				synchronized (stringBuilder1) {
					stringBuilder.append("c");
					stringBuilder1.append("3");
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized (stringBuilder) {
						stringBuilder.append("d");
						stringBuilder1.append("4");
						
						System.out.println(stringBuilder);
						System.out.println(stringBuilder1);
					}
				}
				
			}
		}).start();

	}

}

1.18 ReentrantLock实现线程安全

package multithreading;

import java.util.concurrent.locks.ReentrantLock;

/*
解决线程安全问题的方式三:Lock锁 --jdk1.5新增
面试题:synchronized与ReentrantLock的异同
相同点:都可以解决线程安全问题
不同点:synchronized机制在执行完相应的同步代码后,自动释放同步监视器
ReentrantLock需要手动的启动同步,结束同步也需要手动的实现unclock()
*/
public class TestLock {

	public static void main(String[] args) {
		MyRunnable3 myRunnable = new MyRunnable3();
		
		Thread thread1 = new Thread(myRunnable);
		Thread thread2 = new Thread(myRunnable);
		Thread thread3 = new Thread(myRunnable);
		
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		thread3.setName("窗口3");
		
		thread1.start();
		thread2.start();
		thread3.start();

	}

}

class MyRunnable3 implements Runnable {
	
	private int ticket = 100;
	
	//1、实例化ReentrantLock
	private ReentrantLock reentrantLock = new ReentrantLock(true);
	
	@Override
	public void run() {
		while(true) {
			try {
				
				//2、调用lock()
				reentrantLock.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()解锁
				reentrantLock.unlock();
			}
			
		}
	}
}

1.19 synchronized与ReentrantLock的异同

1、ReentrantLock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放锁。
2、ReentrantLock只有代码块锁,synchronized有代码块锁和方法锁。
3、使用ReentrantLock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:ReentrantLock->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)

1.20 案例

package multithreading;

/*
银行有一 个账户,有两个储户分别向同一个账户存3000元,每次存1000,存3次,每次存完打印账户余额。
1、是否是多线程问题?是,两个储户线程
2、是否有共享数据?有,账户(或账号余额)
3、是否有线程安全问题?有

拓展问题:可否实现两个储户交替存钱的操作
*/
public class TestExercise {

	public static void main(String[] args) {
		Account account = new Account(0);
		Customer customer = new Customer(account); 
		Customer customer1 = new Customer(account); 
		
		customer.setName("宋江");
		customer1.setName("吴用");
		
		customer.start();
		customer1.start();
	}

	
}

class Customer extends Thread {
	
	private Account account;
	
	public Customer(Account account) {
		this.account = account;
		
	}
	
	@Override
	public void run() {
		for(int i=0; i<3; i++) {
			account.deposit(1000);
		}
	} 
}

class Account {
	private double balance;
	
	public Account(double balance) {
		this.balance = balance;
	}
	
	//存钱
	public synchronized void deposit(double amt) {
		if(amt>0) {
			balance += amt;
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName()  + ":" + "存钱成功,余额为:" + balance);
		}
	}
}

1、eclipse中选中类名,按F3查看源码;按F4查看继承关系

2 多线程

1、所有运行中的任务通常对应一个进程(Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。
2、一般而言,进程包含如下三个特征:
(1)独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
(2)动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
(3)并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
3、并发性(concurrency)和并行(parallel)是两个概念,并行指在同一时刻,有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
4、对于一个CPU而言,它在某个时间点只能执行一个程序,也就是说,只能运行一个进程,CPU不断地在这些进程之间轮换执行。
5、多线程则扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程(Thread)也被称作轻量级进程(Lightweight Process),线程是进程的执行单元。线程在程序中是独立的、并发的执行流。当进程被初始化后,主线程就被创建了。对于绝大多数的应用程序来说,通常仅要求有一个主线程,但也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每个线程也是互相独立的。
6、线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他进程的其他线程共享该进程所拥有的全部资源。
7、线程可以完成一定的任务,可以与其他线程共享父进程中的共享变量及部分环境,相互之间协同来完成进程所要完成的任务。
8、线程是独立运行的,它并不知道进程中是否还有其他线程存在。线程的执行是抢占式的,也就是说,当前运行的线程在任何时候都可能被挂起,以便另外一个线程可以运行。
9、一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
10、从逻辑角度来看,多线程存在于一个应用程序中,让一个应用程序中可以有多个执行部分同时执行,但操作系统无须将多个线程看作多个独立的应用,对多线程实现调度和管理以及资源分配。线程的调度和管理由进程本身负责完成。
11、一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。
12、操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。
13、线程在程序中是独立的、并发的执行流,与分隔的进程相比,进程中线程之间的隔离程度要小。它们共享内存、文件句柄和其他每个进程应有的状态。
14、因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
15、线程比进程具有更高的性能,只是由于同一个进程中的线程都有共性–多个线程共享同一个进程虚拟空间。线程共享的环境包括:进程代码段、进程的公有数据等。利用这些共享的数据,线程很容易实现相互之间的通信。
16、当操作系统创建一个进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源;但创建一个线程则简单得多,因此使用多线程来实现并发比使用多进程实现并发的性能要高得多。
17、使用多线程编程具有如下优点:
(1)进程之间不能共享内存,但线程之间共享内存非常容易。
(2)系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程得效率高。
(3)Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程。

2.1 如何理解进程与线程

进程:操作系统进行资源调度和分配的基本单位(例如浏览器、APP、JVM)
线程:进程中的最小执行单位(可以理解为一个顺序的执行流)
说明:多个线程可以共享所属进程的所有资源

2.2 如何理解多线程中的并行与并发

并发:多线程抢占cup,可能不同时执行,侧重于多个任务交替执行。
并行:线程可以不共享cpu,可每个线程一个cup同时执行多个任务

2.3 如何理解线程的生命周期及状态变化

一个线程从创建、运行、到最后销毁的这个过程称之为线程的生命周期,在这个生命周期过程中线程可能会经历如下状态:新建状态、就绪状态、阻塞状态、死亡状态。

package com.cy.thread;

public class TestThread {

	static String content;
	
	public static void main(String[] args) throws InterruptedException {
		
		Thread thread = new Thread() {
			@Override
			public void run() { //运行状态
				content = "hello";
			} //run方法执行结束线程处于死亡状态
		}; //新建状态
		
		thread.start(); //就绪状态(可运行状态)
		 
		//Thread.sleep(1000); //阻塞1s
		
		thread.join(10); //让thread线程执行结束,主线程阻塞
		
		while(content == null) {
			Thread.yield(); //让当前线程放弃CPU,处于就绪状态
		}
		
		System.out.println(content.toUpperCase());
		

	}

}

2.4 如何理解线程安全、线程不安全

多个线程并发执行时,仍旧能够保证数据的正确性,这种现象称之为线程安全。
多个线程并发执行时,不能保证数据的正确性,这种现象称之为线程不安全。

2.4.1 案例一:模拟多个线程同时执行售票操作

package com.cy.thread;

/**
线程问题
案例:多线程执行售票任务
*/
public class TestThread01 {
	static class TicketTask implements Runnable {
		int ticket = 10;
		@Override
		public void run() {
			doTicket();
			
		}
		
		private void doTicket() {
			while(true) {
				//多线程在此代码上排队执行(同步)
				synchronized(this) { //保证操作的原子性
								
					if(ticket<=0) {
						break;
					}
					String tName = Thread.currentThread().getName();
					System.out.println(tName+":" + ticket--);
					sleep();
				} //排他锁,又叫做独占锁
			}
		}
		
		public void sleep() {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		Runtime runtime = Runtime.getRuntime();
		System.out.println("Processors=" + runtime.availableProcessors());
		
		//1、创建售票任务对象
		TicketTask task = new TicketTask();
		//2、创建多个线程模拟多个售票窗口
		Thread t1 = new Thread(task);
		Thread t2 = new Thread(task);
		Thread t3 = new Thread(task);
		Thread t4 = new Thread(task);
		
		//3、启动线程执行任务
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

2.4.2 案例二:模拟多个线程同时执行计数操作

package com.cy.thread;

import java.util.ArrayList;
import java.util.List;

public class TestThread02 {

	static class Counter implements Runnable{
		private volatile int count;
		
		@Override
		public void run() {
			for(int i=0; i<10; i++) {
				doCount();
			}
			
		}

		public synchronized void doCount() {
			count++; //此操作非原子操作,加synchronized即可变成原子操作;默认同步锁;this
		} 
		public int getCount() {
			return count;
		}
	}

	public static void main(String[] args) {
		//1、构建计数任务对象
		Counter counter = new Counter();
		//2、构建多个线程
		List<Thread> list = new ArrayList<>();
		for(int i=1; i<=100; i++) {
			list.add(new Thread(counter));
		}
		//3、启动线程
		for(Thread t : list) {
			t.start();
		}
		
		//4、所有线程执行结束,输出counter的值
		//判定线程活着的数量是否大于1
		while(Thread.activeCount()>1) {
			Thread.yield();
		}
		
		System.out.println(counter.getCount());
	}
}

2.5 导致线程不安全的因素

1、多个线程并发执行。
2、多个线程并发执行时存在共享数据集(临界资源)
3、多个线程在共享数据集上的操作不是原子操作(不可拆分的一个操作)

package com.cy.thread;

import java.util.Arrays;

public class TestThread03 {

	public static void main(String[] args) {
		Container container = new Container();
		container.get();
		for(int i=0; i<Integer.MAX_VALUE; i++) {
			container.add(new byte[1024*1024*10]);
			System.out.println(i);
		}
	}

}

class Container {
	//容器
	private Object[] array;
	//有效元素个数
	private int size;
	public Container() {
		this(16);
	}
	public Container(int cap) {
		array = new Object[cap];
	}
	
	//向容器size放数据
	public synchronized void add(Object data) {
		//1、判定容器是否已满,满了则扩容
		if(size==array.length) {
			array = Arrays.copyOf(array, 2*array.length);
		}
		//2、放数据
		array[size] = data;
		//3、修改size的值
		size++;
	}
	
	//从容器取数据,永远从第0个位置取数据
	public synchronized Object get() {
		//1、判定容器是否为空,为空则抛出异常
		if(size==0) throw new RuntimeException("容器为空");
		//2、取数据
		Object obj = array[0];
		//3、移动数据
		System.arraycopy(array, 1, array, 0, size-1);
		//4、修改size的值,有效元素减去1
		size--;
		//5、修改size位置的值
		array[size] = null; //可选
		return obj;
	}
	
	public synchronized int size() { //这里也要加锁
		return size;
	}
	
}

2.6 如何保证并发线程的安全性

1、对共享进行限制访问(例如加锁:synchromized, Lock):多线程在同步方法或同步代码块上排队执行。
2、基于CAS实现非阻塞同步(基于CPU硬件技术支持)
a、内存地址
b、期望数据值
c、需要更新的值
CAS算法支持无锁状态下的并发更新,但可能会出现ABA问题,长时间自旋问题
3、取消共享,每个线程一个对象实例(例如threadlocal)
a、Connection 不允许多线程共享,每个线程一个
b、SimpleDateFormat 不允许多线程共享,每个线程一个
c、SqlSession对象不允许多线程共享,每个线程一个
Java中线程安全问题主要主要有3个:可见性、有序性、原子性。
Java内存模型(JMM)解决了可见性和有序性问题,而锁解决了原子性问题。

2.7 synchronized关键字应用及原理分析

2.7.1 synchronized简介

1、synchronized是排它锁的一种实现,支持可重入性。
2、基于这种机制可以实现多线程在共享数据集上同步(互斥和协作)
(1)互斥:多线程在共享数据集上排队执行。
(2)协作:多线程在共享数据集上进行协作执行(通讯)。
说明:
排他性:如果线程T1已经持有锁L,则不允许除T1外的任何线程T持有该锁L。
重入性:如果线程T1已经持有锁L,则允许线程T1多次获取锁L,更确切的说,获取一次后,可多次进入锁。

package com.cy.two.thread;

public class TestSynchronized01 {
    //排他性:只能有一个线程进入方法内部执行
    static synchronized void doMethod01() {
        System.out.println("doMethod01");
        //可重入性
        doMethod02();
    }

    static synchronized void doMethod02() {
        System.out.println("doMethod02");
    }

    public static void main(String[] args) {
        doMethod01();
    }
}

在这里插入图片描述

2.7.2 synchronized应用分析

1、修饰方法:同步方法(锁为当前实例或者Class对象)
(1)修饰静态方法:默认使用的锁为方法所在类的Class对象
(2)修饰实例方法:默认使用的锁为方法所在类的实例对象
2、修饰代码块:同步代码块(代码块括号内配置的对象)

2.7.3 synchronized原理分析

基于Monitor对象实现同步
1、同步代码块采用monitorenter、monitorexit指令显示实现
2、同步方法则使用ACC_SYNCHRONIZED标记符隐式实现

2.7.4 synchronized锁优化:底层

为了减少获得锁和释放锁带来的性能消耗,jdk1.6以后的锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。
说明:锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

2.7.5 如何理解volatile关键字的应用

1、定义:volatile一般用于修饰属性变量
(1)保证共享变量的可见性(尤其多核或多cpu场景下)。
(2)禁止指令的重排序操作(例如count++;底层会有三个步骤)
(3)不保证原子性(例如不能保证一个线程执行完count++所有指令其他线程才能执行)
2、应用场景分析
(1)状态标记(例如:boolean类型属性)
(2)安全发布(线程安全单例中的对象安全发布-双重检测机制)
(3)读写锁策略(一个写,并发读,类似读写锁)

2.7.6 如何理解happen-before原则应用

在JMM中如果一个A Happened-before另一个操作B,那么A操作的结果对B操作的结果是可见的,那么我们称这种方式为happened-before原则。
说明:JMM中基于happened-before规则,判定数据是否存在竞争,线程是否安全,以及多线程环境下变量值是否是可见的

2.7.7 如何理解Java中的悲观锁和乐观锁

Java中为了保证多线程并发访问的安全性,提供了基于锁的应用,大体可归纳为两大类,即悲观锁和乐观锁
悲观锁、乐观锁的定义说明:
(1)悲观锁:假定会发生并发冲突,屏蔽一切可违反数据完整性的操作,同一时刻只能有一个线程执行写操作。
例如:Java中可以基于synchronizedLock,ReadWriteLock等实现。
(2)乐观锁:假设不会发生冲突,只在提交操作时检查是否违反数据完整性。
多个线程可以并发执行写操作,但只能有一个线程写操作成功。
例如Java中可借助CAS(Compare And Swap)算法实现(此算法依赖硬件CPU)。
update … where id=1;
悲观锁、乐观锁应用场景说明:
(1)悲观锁适合写操作比较多的场景,写可以保证写操作时数据正确。
(2)乐观锁适合读操作比较多的场景,不加锁的特点能够使其读操作的性能大幅提升。

2.7.8 如何理解线程的上下文切换

一个线程得到CPU执行的时间是有限的。当此线程用完为其分配的CPU时间以后,CPU会切换到下一个线程执行。
在线程切换之前,线程需要将当前的状态进行保存,以便下次再次获得CPU时间片时可以加载对应的状态以继续执行剩下的任务。而这个切换过程是需要耗费时间的,会影响多线程程序的执行效率,所以在使用多线程时要减少线程的频繁切换。
减少多线程上下文切换的方案如下:
1、无锁并发编程:锁的竞争会带来线程上下文的切换
2、CAS算法:CAS算法在数据更新方面,可以达到锁的效果。
3、使用最少线程:避免不必要的线程等待。
4、使用协程:单线程完成多任务的调度和切换,避免多线程。

2.7.9 如何理解死锁以及避免死锁问题

多个线程互相等待已经被对方线程正在占用的锁,导致陷入彼此等待对方释放锁的状态,这个过程称之为死锁。
如何避免死锁:
1、避免一个线程中同时获取多个锁
2、避免一个线程在一个锁中获取其他的锁资源。
3、考虑使用定时锁来替换内部锁机制,如lock.tryLock(timeout)。

2.8 线程通讯应用

2.8.1 如何理解线程通讯

线程通讯指的是线程间的一种交互方式。例如:现在社交通信软件中用户与用户的交互,微服务架构中系统间的交互等,都是基于线程通讯实现的。我们通常可以将线程通讯分为两大类,一类是“进程类”的线程通讯,还有一类是“进程间”的线程通讯。

5 AOP

5.1 AOP的应用场景

1.如果直接将缓存业务,写到业务层中,如果将来的缓存代码发生变化,则代码耦合高,必然重写编辑代码.
2.如果其他的业务也需要缓存,则代码的重复率高,开发效率低.
解决方案: 采用AOP方式实现缓存.

5.2 AOP介绍

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

5.3 AOP的实现步骤

公式: AOP(切面) = 通知方法(5种) + 切入点表达式(4种)

5.3.1 通知

1、before通知 在执行目标方法之前执行
2、afterReturning通知 在目标方法执行之后执行
3、afterThrowing通知 在目标方法执行之后报错时执行
4、after通知 无论什么时候程序执行完成都要执行的通知

上述的4大通知类型,不能控制目标方法是否执行.一般用来记录程序的执行的状态.
一般应用与监控的操作.

5、around通知(功能最为强大的) 在目标方法执行前后执行.
环绕通知可以控制目标方法是否执行.控制程序的执行的轨迹.

5.3.2 切入点表达式

1.bean(“bean的id”) 粒度: 粗粒度 按bean匹配 当前bean中的方法都会执行通知.
2.within(“包名.类名”) 粒度: 粗粒度 可以匹配多个类
3.execution(“返回值类型 包名.类名.方法名(参数列表)”) 粒度: 细粒度 方法参数级别
4.@annotation(“包名.类名”) 粒度:细粒度 按照注解匹配

学生代码

package a;

import java.util.Scanner;

public class Test2 {

	public static void main(String[] args) {
		Person1 person1 = new Person1("宋江");
		System.out.println(person1.name);
		
		Scanner scanner = new Scanner(System.in);
		System.out.println("请选择您的性别(1、男 2、女):");
		
		switch(scanner.nextInt()) {
			case 1: person1.gender = "男"; break;
			case 2: person1.gender = "女"; break;
				default: 
					System.out.println("操作错误");
					return;
		}
		
		System.out.println("请输入您的年龄:");
		person1.age = scanner.nextInt();
		person1.work();
		
		System.out.println("请重新输入您的工作理念:");
		String contect = scanner.next();
		person1.work(contect);
		
	}
	
}

class Person1 {
	
	public String name;
	public String gender;
	public int age;
	
	//无参构造方法
	public Person1() { 
		this.name = "宋江";
	}
	//有参构造方法
	public Person1(String name) {
		this.name = name;
	}
	
	public void work() {
		System.out.println(this.name + "的工作理念:干活挣钱有饭吃");
	}
	
	public void work(String contect) {
		int age = this.age;
		System.out.println(this.name + age + "岁的工作理念:干活挣钱有饭吃");
	}
	
}
package a;

import java.util.Scanner;

public class Test7 {

	public static void main(String[] args) {

		//Scanner
		
		System.out.print("请输入第一个整数:");
		Scanner scanner = new Scanner(System.in);
		int number1 = scanner.nextInt();
		System.out.print("请输入第二个小数:");
		float number2 = scanner.nextFloat();

		System.out.println("两个数的和是:" + (number1+number2));
		
		System.out.print("请输入一个字符串:");
		String string = scanner.next();
		System.out.println("您输入的字符串是:" + string);
		
	}
	
}

/*
public boolean nextBoolean()
public String next()
public byte nextByte()
public short nextShort()
public int nextInt()
public long nextLong()
public double nextDouble()
public float nextFloat()
*/

public class Test8 {

	public static void main(String[] args) {
		
		int a = 1;
		a++; //a=a+1
		a++;
		System.out.println(a);
		
		
		int b = 1;
		b++; //b=b+1
		++b; // b=b; b+1
		System.out.println(b);
		
		int c =1;
		int d;
		d = c++; //(1)d=c;d=1 c=1 (2)c=c+1 c=2 d=1
		System.out.println(d); //1
		System.out.println(c); //2
		
		int e =1;
		int f;
		f = ++e; //(1)e=e+1; e=2 (2)f=e e=2, f=2
		System.out.println(e); //2
		System.out.println(f); //2
		
	}

}


public class Test9 {

	public static void main(String[] args) {
		
		int a = 1;
		a = a+2;
		a+=2; //相当于a = a+2;
		
		System.out.println(a);
		
		//byte short int long
		byte b = 127;
		System.out.println(b);
		
		//float double
		float d = 1.1f;
		
		short s1 = 10;
		//short s1 = s1+2;
		s1 += 2;
		System.out.println(s1);
	}
	
}

import java.util.Arrays;

public class Test16 {
	
    public static void main(String[] args) {
	
    	//public static boolean equals​(int[] a, int[] a2) 判断两个数组是否相等。
    			int[] arr1 = new int[] {1, 2, 3, 4};
    			int[] arr2 = new int[] {1, 2, 3, 4};
    			boolean isEquals = Arrays.equals(arr1, arr2);
    			System.out.println(isEquals);

    			//public static String toString​(int[] a) 输出数组的所有元素
    			String stringArry = Arrays.toString(arr1);
    			System.out.println(stringArry);

    			//public static void fill​(int[] a, int val) 将数组中所有元素替换成指定值
    			Arrays.fill(arr1, 10);
    			System.out.println(Arrays.toString(arr1));

    			//public static void sort​(int[] a) 对数组进行排序。
    			Arrays.sort(arr2);
    			System.out.println(Arrays.toString(arr2));

    			//public static int binarySearch​(int[] a, int key) //查找元素key的索引
    			int[] arr3 = new int[]{-98, -34, 2, 34, 54, 66, 79, 105, 210, 333};
    			int index = Arrays.binarySearch(arr3, 22);
    			System.out.println(index);
    			if (index >= 0) {
    				System.out.println(index);
    			} else {
    				System.out.println("未找到");
    			}
    			
    }

}


public class test15 {

	public static void main(String[] args) {
		
          String[] arr= {"ab", "cd", "ef"};
		//方法一:
				for(int i=0; i<arr.length/2; i++){
					
					String temp = arr[i];
					arr[i] = arr[arr.length-i-1];
					arr[arr.length-i-1] = temp;

				}
				
				//方法二:
				for(int i=0,j=arr.length-1; i<j; i++,j--){
					
					String temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;

				}
        
	}

}


public class Test14 {

	public static void main(String[] args) {

		int[] intArray = {1, 2, 3, 4, 5};
		
		int a1 = intArray[0];
		int a3 = intArray[3];
		System.out.println(a1);
		System.out.println(a3);
		
		String[] stringArray = {"one", "two", "three"};
		
		int number;
		number =10;
		
		
		int[] ids;
		ids = new int[]{1, 2, 3, 4, 5};
		
		String[] stringArray1;
		stringArray1 = new String[]{"one", "two", "three"};
		
		String[] names = new String[5];
		
		int[] arr33 = new int[] {1, 2, 3};
		System.out.println(arr33.length);
		
		System.out.println("-------------------");
		
		for(int i=0; i<arr33.length; i++) {
			System.out.println(arr33[i]);
		}
		
		String[] arr111 = new String[4];
		
		//System.out.println(arr111[1][0]); //null
	    System.out.println(arr111[0]); //null
		
	}

}


public class test13 {

	public static void main(String[] args) {

		int a = 4;
		//
		switch(a) { //byte、short、int、char、String、枚举
		case 1: 
			System.out.println("a");
			break;
		case 2:
			System.out.println("a");
			break;
		case 3:
			System.out.println("a");
			break;
		case 4:
			System.out.println("d");
			break;
		
		case 5:
			System.out.println("e");
			break;
		default:
			System.out.println("abc");
		}

		
	}

}


public class Test12 {

	public static void main(String[] args) {

		int a = 1;
		int b = 2;
		
		if(a>b) {
			a++;
		}else {
			b++;
		}
		
		System.out.println(a);
		System.out.println(b);
		
		int c = 1;
		int d = 2;
		
		int ii = (c>d) ? (c++):(d++);
		System.out.println(ii);
		System.out.println(d);
	

	}

}


public class Test11 {

	public static void main(String[] args) {

        //&和&&的区别
		int a =3;
		int b = 4;
		
		if(a>b && ++a>4 ) { //false & false 
			System.out.println("aa");
		}else {
			System.out.println("bb");
		}
		
		System.out.println("a=" + a);
		System.out.println("b=" + b);
		
		//| 和 || 的区别
		int c = 3;
		int d = 4;
		
		if(c<d || ++d>4 ) { //true & true 
			System.out.println("cc");
		}else {
			System.out.println("dd");
		}
		
		System.out.println("c=" + c);
		System.out.println("d=" + d);

	}

}


public class Test10 {

	public static void main(String[] args) {

		int a = 1;
		int b = 11;
		
		System.out.println(a==b);
		
		System.out.println(a!=b);
		
		boolean b1 = true;
		boolean b2 = false;
		
		System.out.println(b1!=b2);
		
		System.out.println(5>6);

	}

}

import java.util.Arrays;

public class Test9 {

	public static void main(String[] args) {
		
		//1 数组角标越界的异常:ArrayIndexOutOfBoundsException
				int[] array = new int[]{1, 2, 3, 4, 5};
				
				//输出数组
				System.out.println(Arrays.toString(array));
				
				//遍历输出数组
				for(int i=0; i<=array.length; i++){
					System.out.println(array[i]); //此处异常:ArrayIndexOutOfBoundsException 
				}

			       System.out.println(array[-1]); //此处异常:ArrayIndexOutOfBoundsException

				System.out.println("hello"); //上面异常,此处代码执行不到

				//空指针异常:NullPointerException
				//example one
				int[] arr1 = new int[]{1,2,3};
				arr1 = null;
				System.out.println(arr1[0]);
				
				//example two
				int[][] arr2 = new int[4][];
				System.out.println(arr2[0][0]);

				//example three
				String[] arr3 = new String[]{"one", "two", "three"};
				arr3[0] = null;
				System.out.println(arr3[0]); //此处输出null
				System.out.println(arr3[0].toString()); //此处报异常:NullPointerException
				
			}
		
	}
	
}


public class Test8 {

	public static void main(String[] args) {
		
		int a = 1;
		a++; //a=a+1
		a++;
		System.out.println(a);
		
		
		int b = 1;
		b++; //b=b+1
		++b; // b=b; b+1
		System.out.println(b);
		
		int c =1;
		int d;
		d = c++; //(1)d=c;d=1 c=1 (2)c=c+1 c=2 d=1
		System.out.println(d); //1
		System.out.println(c); //2
		
		int e =1;
		int f;
		f = ++e; //(1)e=e+1; e=2 (2)f=e e=2, f=2
		System.out.println(e); //2
		System.out.println(f); //2
		
	}

}

package a;

import java.util.Scanner;

public class Test7 {

	public static void main(String[] args) {

		//Scanner
		
		System.out.print("请输入第一个整数:");
		Scanner scanner = new Scanner(System.in);
		int number1 = scanner.nextInt();
		System.out.print("请输入第二个小数:");
		float number2 = scanner.nextFloat();

		System.out.println("两个数的和是:" + (number1+number2));
		
		System.out.print("请输入一个字符串:");
		String string = scanner.next();
		System.out.println("您输入的字符串是:" + string);
		
	}
	
}

/*
public boolean nextBoolean()
public String next()
public byte nextByte()
public short nextShort()
public int nextInt()
public long nextLong()
public double nextDouble()
public float nextFloat()
*/

spring cloud

理论、概念

微服务架构是一种架构模式,它提倡将单一的应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上、下文,选择合适的语言、工具对其进行构建。

GRASP(General Responsibility Assignment Software Patterns)通用职责分配软件模式

版本说明

spring boot版本选择:
git源码地址:https://github.com/spring-projects/spring-boot/releases/
spring boot 2.0新特性地址:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes

spring cloud版本选择:
git源码地址:https://github.com/spring-projects/spring-cloud/wiki

spring cloud和spring boot之间的依赖关系:https://start.spring.io/actuator/info
spring cloud英文文档:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/
spring cloud中文文档:https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md
spring boot文档:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/

spring cloud命名规则:
1、采用英国伦敦地铁站的名称来命名,并由地铁站名称字母A-Z依次类推的形式来发布迭代版本。
2、是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了管理spring cloud与各子项目的版本依赖关系,发布了一个清单,其中包括了某个spring cloud版本对应的子项目版本。为了避免spring cloud版本号与子项目版本号混淆,spring cloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序。例如Angel是第一个版本,Brixton是第二个版本。当spring cloud的发布内容积累到临界点或者一个重大bug被解决后,会发布一个“service releases”版本,简称SRX版本,比如Greenwich.SR2就是spring cloud发布的Greenwich版本的第2个SRX版本。
在这里插入图片描述
在这里插入图片描述

nginx

启动:start nginx
重启:nginx -s reload
关闭:nginx -s stop
notice:命令结尾处不能加分号

欢迎使用Markdown编辑器

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
1.全新的界面设计,将会带来全新的写作体验;
2. 在创作中心设置你喜爱的代码高亮样式,Markdown将代码片显示选择的高亮样式进行展示;
3. 增加了图片拖拽功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
4.全新的KaTeX数学公式语法;
5.增加了支持甘特图的mermaid语法1 功能;
6. 增加了多屏幕编辑Markdown文章功能;
7. 增加了焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置等功能,功能按钮位于编辑区域与预览区域中间;
8. 增加了检查列表功能。

功能快捷键

撤销:Ctrl/Command+Z
重做:Ctrl/Command+Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本

引用文本

H2O is是液体。
210 运算结果是 1024.

插入链接与图片

链接: link.
图片: Alt
带尺寸的图片: Alt
居中的图片: Alt
居中并且带尺寸的图片: Alt
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值