0%

Math类的常用方法

Math类都是静态的方法我们只需要直接使用即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Learn;

public class Test {
public static void main(String[] args) {
double num=-9.2;
//ad返回绝对值
System.out.println(Math.abs(num));
//pow,求幂
System.out.println(Math.pow(num,2));
//ceil求正,求取>=最小的整数
System.out.println(Math.ceil(num));
//floor,求取<=的最大整数
System.out.println(Math.floor(num));
//round,四舍五入,返回的int
System.out.println(Math.round(num));
//sqrt,求开平方
System.out.println(Math.sqrt(9));
//求随机数,返回0-1之间的一个随机小数0<=a<1
//输出2-7之间的一个数
System.out.println(Math.round(Math.random()*5+2));
System.out.println((int)(Math.random()*6+2));

}
}

StringBuffer结构解析

  • 我们查看以下的结果,可以看出StringBuffer的父类以及相应的接口,表示该对象可以进行串行化以及可以用来比较

image-20220301193814501

  • 我们查看StringBuffer的父类AbstractStringBuilder,可以看到源码中存在着byte[] value,而且不是final类型,而该value数组存放着我们的字符串内容,因此可以了解到该字符存放在堆中,并不是存放在常量池中,所以对比前面学到的String,String保存的是字符串常量,里面的值并不能更改,每次更改需要改变地址。而StringBuffer保存的是字符串变量,里面的值可以更改,每一次更新实际可以更新内容,不用每一次更新地址(到我们的地址指向的空间不够时,地址才会改变),效率比较高。

image-20220301194352958

  • StringBuffer是一个final类,并不能被继承

StringBuffer的转换

  • StringBuffer的构造方法

image-20220301195420797

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public StringBuffer() {
super(16);
}
//创建一个大小为16的char[]数组,用于存放字符内容
StringBuffer stringBuffer = new StringBuffer();

public StringBuffer(int capacity) {
super(capacity);
}
//创建一个初始容量为100的char[]数组
StringBuffer stringBuffer = new StringBuffer(100);

public StringBuffer(String str) {
super(str);
}
//创建一个容量为stringBuffer.length+16的char[]数组,用于存放字符
StringBuffer stringBuffer = new StringBuffer("ascvcacv");


public StringBuffer(CharSequence seq) {
super(seq);
}

  • String与StringBuffer相互转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Learn;

import java.util.Arrays;
import java.util.Locale;

public class Test {
public static void main(String[] args) {
//string转换为StringBuffer
String str="hello";
//方法1:使用构造器,对str本身没有影响
StringBuffer stringBuffer1=new StringBuffer(str);
//方法2:使用append的方法
StringBuffer stringBuffer2=stringBuffer1.append(str);


//StringBuffer转换为String
StringBuffer stringBuffer=new StringBuffer("java");
//方法1:tostring
String str1=stringBuffer.toString();
//方法2:使用构造器
String str2=new String(stringBuffer);
}
}

StringBuffer的常见方法

方法名 用法
append
delete
replace
indexOf
insert 插入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Learn;

public class Test {
public static void main(String[] args) {
StringBuffer stringBuffer=new StringBuffer("java");
stringBuffer.append("ee");
System.out.println(stringBuffer);
stringBuffer.delete(4,6);
System.out.println(stringBuffer);
stringBuffer.replace(0,1,"ha");
System.out.println(stringBuffer);
System.out.println(stringBuffer.indexOf("v"));
stringBuffer.insert(1,"ha");
System.out.println(stringBuffer);
}
}

image-20220301201856495

练习:

一步一步调试,追溯源码,可以看出最终两个的差别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Learn;

public class Test {
public static void main(String[] args) {
String str=null;
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append(str);
System.out.println(stringBuffer.length());
System.out.println(stringBuffer);

StringBuffer stringBuffer1=new StringBuffer(str);
System.out.println(stringBuffer1.length());

}
}

image-20220301202739437

学会利用StringBuffer格式化输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package Learn;

import java.util.Scanner;

public class Test {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
String name=scanner.next();
String price=scanner.nextDouble()+"";
StringBuffer price1=new StringBuffer(price);
int i=price1.lastIndexOf(".");
while (i>=3){
price1.insert(i-3,",");
i=i-3;
}
System.out.println(price1);

}
}

image-20220301204926140

StringBuilder结构剖析

  1. StringBuilder是一个可变的字符序列。此类提供一个与StringBuffer兼容的API,但是不保证同步(StringBuilder不是线程安全的,所有的方法都没有做互斥处理)。该类被设计用作StringBuffer的一个简易的替换,用在字符串缓冲区被单个线程使用时候,可以优先采用此类,大多数线程中比StringBuffer快
  2. 下面的图表明StringBuilder与StringBuffer继承了相同的类与接口,StringBuilder也是final的,不可以被继承

image-20220301205717395

StringBuilder速度比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package Learn;

import java.util.Scanner;

public class Test {
public static void main(String[] args) {
StringBuffer stringBuffer=new StringBuffer("花花");
long startTime=System.currentTimeMillis();
for (int i=0;i<2000;i++){
stringBuffer=stringBuffer.append("haha");
}
long endTime=System.currentTimeMillis();
long totalTime=endTime-startTime;
System.out.println("时间为"+totalTime);

StringBuilder stringBuilder=new StringBuilder("花花");
startTime=System.currentTimeMillis();
for (int i=0;i<2000;i++){
stringBuilder=stringBuilder.append("haha");
}
endTime=System.currentTimeMillis();
totalTime=endTime-startTime;
System.out.println("时间为"+totalTime);
}
}


image-20220301211620315

String,StringBuffer与StringBuilder比较

  1. StringBuffer与StringBuilder非常类似,均可以表示可变的字符序列,方法也一样
  2. String不可变的字符序列,效率低,复用性高,如果我们对String做大量的修改,不要使用String
  3. StringBuffer可变的字符序列,效率比较高,线程安全
  4. StringBuilder可变,效率最高,线程不安全

String类

String类型的基本认识

  1. String对象用于保存字符串,也就是一组字符序列

  2. 字符串常量的对象是用双引号括起的字符序列

  3. 字符串通常使用Unicode字符编码,一个字符(不管时字母还是汉字)占用两个字节

  4. 字符串String拥有很多的构造器image-20220705105223155

  5. String类时final类,不能被其他的类所继承。private final byte[] value;String类拥有value属性,并且为final性质,表示不能被做出修改。比如从TOM变成TOO。也就是一句话,当我们的str发生更改之后,其地址已经改变,原地址的内容不会发生改变。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package Learn;
    public class Test{
    public static void main(String[] args) {

    final char c[]={'t','t','0'};
    //这里我们可以发现我们成功更改了c的值,并且没有报错
    for (int i=0;i< c.length;i++){
    c[i]='v';
    }
    System.out.println(c);
    //但是当我们重新写一个数组
    final char b[]={'t','t','0'};
    //下面的这个赋值就是错误的
    //c=b;

    //说完上面的,我们来看一下String类不可变的
    String str1="tom";
    System.out.println(str1.hashCode());
    System.out.println(str1);
    //我将m替换为o,我们可以根据结果发现,这个根本就没有改变,而且地址没有改变
    //说明在replace过程中其实创建的新的去存储这个字符串,而源地址的字符串是不会发生改变的
    str1.replace('m','0');
    System.out.println(str1.hashCode());
    System.out.println(str1);


    //但是我们在平常的时候,明明可以改变String的值,这是因为我们new了一个string对象,str1指向的对象地址改变了
    String str2="jack";
    str1=str2;
    System.out.println(str1);
    System.out.println(str1.hashCode());
    }
    }

    image-20220705105255820

  6. image-20220705105304838

查看上述的图可以发现,String继承了以上的接口,而继承Serializable表示该字符可以在网络上传输,而对于Comparable则表示可以用于比较。

String类型的创建剖析

  1. 方式一:直接赋值

    1
    String str1="jack";

    先从常量池查看是否有”jack“是数据空间,如果有,则直接指向;如果没有,则重新创建,然后指向。str1最终指向的是常量池的地址空间。

    image-20220705105320961

  2. 调用构造器:

    1
    String str2=new String("tom");

    先在堆中创建空间,里面维护了value属性,指向常量池tom空间。如果常量池没有tom,重写创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。但是由于value是String的私有方法,所以我们无法查看value的值

image-20220705105340537

练习1

1
2
3
4
5
6
7
8
9
package Learn;
public class Test{
public static void main(String[] args) {
String str1="jack";
String str2=new String("jack");
System.out.println(str1==str2);
System.out.println(str1.equals(str2));
}
}

image-20220705105607057

他们虽然值相等,但是他们的地址却不相等。

练习2

  • intern

    1
    public String intern()

    返回字符串对象的规范表示。

    最初为空的字符串池由StringString

    当调用intern方法时,如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。 否则,此String对象将添加到池中,并返回对此String对象的引用。

    由此可见,对于任何两个字符串sts.intern() == t.intern()true当且仅当s.equals(t)true

    所有文字字符串和字符串值常量表达式都被实体化。 字符串文字在The Java™ Language Specification的 3.10.5节中定义。

    结果

    一个字符串与该字符串具有相同的内容,但保证来自一个唯一的字符串池。

1
2
3
4
5
6
7
8
9
10
package Learn;
public class Test{
public static void main(String[] args) {
String str1="jack";
String str2=new String("jack");
System.out.println(str1==str2);
System.out.println(str1.equals(str2));
System.out.println(str2.intern()==str1);
}
}

image-20220705105704532练习3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package Learn;
public class Test{
public static void main(String[] args) {
Person person1 = new Person();
Person person2=new Person();
person1.name="jack";
person2.name="jack";
System.out.println(person1==person2);
System.out.println(person1.name== person2.name);
System.out.println(person1.name.equals(person2.name));

String str1=new String("tom");
String str2=new String("tom");
System.out.println(str1==str2);
System.out.println(str1.equals(str2));
}
}
class Person{
String name;
}

image-20220705110015100String类型字符串的特性

  • 编译器会堆常量池的对象进行优化。下面的一段代码中,按照本意会生成三个对象,但是编译器会自动识别出其中的关系,从而仅仅创建一个adcdac对象。查看以下代码之后我们可以看出,在str3中是创建了一个新对象的,否则不会与4和5的对象不相同。在3执行的过程,首先创建了StringBuilder对象,利用append接受str1与str2,然后在将值给与str3,由于使用了StringBuilder,所以本次的str3与String str3=new String(“adcdac”);效果相同。所以常量的相加在,常量池中进行操作,如果是变量则是在堆中进行操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

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

    String str1="adc";
    String str2="dac";
    String str3=str1+str2;
    String str4="adcdac";
    String str5="adc"+"dac";
    System.out.println(str1.hashCode());
    System.out.println(str2.hashCode());
    System.out.println(str3.hashCode());
    System.out.println(str4.equals(str3));
    System.out.println(str4==str3);
    System.out.println(str5==str3);
    System.out.println(str4==str5);
    }
    }

image-20220705110448855Java中当数组与字符串在方法中被更改时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class Test {
public String str="java";
public final char[]c={'j','a','v','a'};
public void change(String str,char[]c){
str="hello";
c[0]='s';
System.out.println(str.hashCode());
}
public static void main(String[] args) {
Test test = new Test();
test.change(test.str, test.c);
System.out.println(test.str);
System.out.println(test.str.hashCode());
System.out.println(test.c);
String str2="hello";
System.out.println(str2.hashCode());
}
}

image-20220705110807142String类的常见方法

方法名 用法
equals 区分大小写,判断是否相等
equalsIgnoreCase 忽略大小写,判断是否相等
length 获得长度
indexOf 获得字符在字符串中第一次出现的索引,从0开始,找不到就返回-1
lastindexOf 获得字符在字符串中最终出现的位置,索引从0开始
substring(a,b) 截取指定范围的字符串(不包含位置b)
trim 去除前后空格
charAt 获取某索引处的字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Learn;
public class Test {
public static void main(String[] args) {
String str="java";
String str1="JAva";
System.out.println(str.equals(str1));
System.out.println(str.equalsIgnoreCase(str1));
System.out.println(str.indexOf('a'));
System.out.println(str.indexOf("av"));
System.out.println(str.lastIndexOf('a'));
System.out.println(str.substring(0,2));
System.out.println(str.charAt(0));
System.out.println(" hello".trim());
}
}

image-20220705110943469

方法名 用法
toUpperCase 变成大写
toLowerCase 变成小写
concat 获得长度
replace 替换字符串中的字符
split 分割字符串
compareTo 比较字符串的大小
toCharArray 转换成字符数组
format 格式化字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package Learn;

import java.util.Arrays;
import java.util.Locale;

public class Test {
public static void main(String[] args) {
String str2="hello";
String str1="JAva";
System.out.println(str1.toUpperCase());
System.out.println(str1.toLowerCase());
System.out.println(str1.concat(str2));
System.out.println(str1.replace('a','n'));
String poem="楚河,焊接,北京";
String []a=poem.split(",");
for (int i=0;i<a.length;i++){
System.out.println(a[i]);
}

System.out.println(str1.compareTo(str2));
System.out.println(str1.compareTo("JAvaFH"));
System.out.println(str1.toCharArray());
System.out.println(String.format("我是字符串%s",str1));
}
}

image-20220705110925625注意

对于喜欢使用表情包的同学,例如,其实表情包的表示使用的是两个代码单元,这是想要如果我们想要得到的第三个char字符不是最后一个,如果此时想要精确使用,就需要我们调用另外的方法。

1
2
3
4
5
6
7
8
9
10
11
/**
* @author 21050
*/
public class Test {
public static void main(String[] args) {
String str="😄你好";
for (char i:str.toCharArray()){
System.out.println(i);
}
}
}

image-20220705111045879此时我们输出length为4,如果想要输出正确的结果,需要首先转换为数组

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author 21050
*/
public class Test {
public static void main(String[] args) {
String str = "😄你好";
//此时我们需要先将其转换为一个数组
System.out.println(str.length());
int []array=str.codePoints().toArray();
System.out.println(array.length);
}
}

image-20220705111105430

额,怎么顺利遍历正在思考中。。。,在《Java技术核心卷》中建议不要使用charAt方法,

多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog =new Dog();
//不可调用
//cat.run();
Thread thread = new Thread(cat);
Thread thread1=new Thread(dog);
thread.start();
thread1.start();
}
}


/**当一个类继承了Thread类,该类就可以当作线程使用,run方法来源于Runnable接口*/
class Cat implements Runnable{
@Override
public void run() {
int j=0;
while (true){
System.out.println("喵喵");
try{
Thread.sleep(1000);}
catch(InterruptedException e){
System.out.println("喵喵出问题");
e.printStackTrace();
}
j++;
if (j==5){
break;
}
}

}
}
class Dog implements Runnable{
@Override
public void run(){
int i=0;
while (true) {
System.out.println("汪汪");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
System.out.println("汪汪出问题");
e.printStackTrace();
}
i++;
if (i>=5){
break;
}
}
}
}

image-20220317113800843

上述中一个主线程和两个子线程,主线程执行优先执行完毕,但是子线程还没由结束,主线程会提结束,而子线程继续执行。

售票问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package windowSale;

/**
* @author zss
*/
public class WindowSale1 {
public static int countTicket=10;

public static void main(String[] args) {
Window1 window1 = new Window1();
Window1 window2 = new Window1();
Window1 window3 = new Window1();
window1.start();
window2.start();
window3.start();
}
}
class Window1 extends Thread{
boolean judge=true;
@Override
public void run(){
while (judge){
sell();
}
}
public void sell(){
if (WindowSale1.countTicket<=0){
judge=false;
System.out.println("售票结束");

}
else {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
WindowSale1.countTicket = WindowSale1.countTicket - 1;
System.out.println(Thread.currentThread().getName() + "售卖一张票" + "还剩" + WindowSale1.countTicket);
}

// System.out.println();

}
}


三个窗口售卖,会发现我们的票数剩余0张还可以继续售卖。这是因为在我们的票数还有两张时,我们的1号窗口进来还没有进行减去操作,2号与3号都进来,导致2张票被买了三次。

线程同步机制

在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任意同一时刻,最多由一个线程访问,保证数据的完整性。

同步的具体方法

  • synchronized(对象){//得到对象的锁,才能操作同步带代码

​ //需要被同步的代码},操作的必须是同一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package windowSale;

/**
* @author zss
*/
public class WindowSale1 {
public static void main(String[] args) {
SellTicket sellTicket=new SellTicket();
Thread thread1=new Thread(sellTicket);
Thread thread2=new Thread(sellTicket);
Thread thread3=new Thread(sellTicket);
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket extends Thread{
int ticketNum=100;
boolean judge=true;
Object obj=new Object();
@Override
public void run() {
while (judge){
sell();
}
}
public void sell(){

synchronized (this.obj){
if (ticketNum<=0){
System.out.println("售票结束");
judge=false;
}
else {
ticketNum--;
System.out.println(Thread.currentThread().getName()+"售卖一张票,号数为"+(ticketNum+1)+"还剩"+ticketNum);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
  • synchronized还可以放在方法声明中,表示整个方法为同步方法

public synchronized void m(String name){

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package windowSale;

/**
* @author zss
*/
public class WindowSale1 {
public static void main(String[] args) {
SellTicket sellTicket=new SellTicket();
Thread thread1=new Thread(sellTicket);
Thread thread2=new Thread(sellTicket);
Thread thread3=new Thread(sellTicket);
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket extends Thread{
int ticketNum=100;
boolean judge=true;
@Override
public void run() {
while (judge){
sell();
}
}
public void sell(){
if (ticketNum<=0){
System.out.println("售票结束");
judge=false;
}
else {
ticketNum--;
System.out.println(Thread.currentThread().getName()+"售卖一张票,号数为"+(ticketNum+1)+"还剩"+ticketNum);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

image-20220318091456954

此时发现没有超票售出,但是售票的顺序却不正确,而且其实内部已经发生了超卖现象,但是我们只不过用代码块掩盖 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package windowSale;

/**
* @author zss
*/
public class WindowSale1 {
public static void main(String[] args) {
SellTicket sellTicket=new SellTicket();
Thread thread1=new Thread(sellTicket);
Thread thread2=new Thread(sellTicket);
Thread thread3=new Thread(sellTicket);
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket extends Thread{
int ticketNum=100;
boolean judge=true;
@Override
public void run() {
while (judge){
sell();
}
}
public synchronized void sell(){
if (ticketNum<=0){
System.out.println("售票结束");
judge=false;
}
else {
ticketNum--;
System.out.println(Thread.currentThread().getName()+"售卖一张票,号数为"+(ticketNum+1)+"还剩"+ticketNum);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

image-20220318092230291

线程进行的机制

在我们运行一个进程的时候,会出现主线程和子线程,主线程并不会因为子线程的运行而发生阻塞。

image-20220317101908436

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
for (int i=0;i<100;i++){
System.out.println("我是主线程");
try{
Thread.sleep(1000);}catch (Exception e){
System.out.println("线程发生异常");
}
}
}
}
/**当一个类继承了Thread类,该类就可以当作线程使用,run方法来源于Runnable接口*/
class Cat extends Thread{
int time=0;
@Override
public void run(){
while (true){
System.out.println("喵喵,我是小喵咪");
time++;
try{
Thread.sleep(1000);}catch (Exception e){
System.out.println("发生了错误");
}
if (time==20){
break;
}
}
}
}

image-20220317102212202

运行以上程序,可以发现主线程的for循环并没有因为子线程cat的运行而进行停滞,而是不断的交替运行。

为什么使用Start

使用我们cat.run方法也可以调用这个方法,但是为什么使用start方法??

如果我们直接调用Run方法,会发现控制他的线程是main方法,而不是我们的Thread-0线程,这样的就不会交替执行,而是必须将方法执行完毕才可以执行下一个方法。这样就是串行化执行,而不是多线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void run(){
while (true){
System.out.println("喵喵,我是小喵咪");
System.out.println(Thread.currentThread().getName());
time++;
try{
Thread.sleep(1000);}catch (Exception e){
System.out.println("发生了错误");
}
if (time==20){
break;
}
}

image-20220317103303292

Start源码

image-20220317104213240

他的底层调用了start0方法,但是start0方法是一个native方法,由jvm底层进行调用,而不是由我们程序员进行调用

image-20220317104331963

在start方法调用start0方法之后,线程并不是立刻执行,而是进入了可执行的状态,而最终的运行是由cpu统一进行调度。

互斥锁

每一个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证任意时刻,只有一个线程可以访问该对象,但是会导致执行效率比较低,同步方法的锁可以是this,也可以是其他对象,同步方法的锁为当前类的本身。上面买票的问题可以是this本身,也可以object,但是必须同一个对象,不可以是new的多个对象。

如果在静态代码中,必须当前对象

1
2
3
4
5
6
7
8
9
10
11
12
13
class AA implements Runnable{

public static void sell() {
synchronized (AA.class){

}
}

@Override
public void run() {

}
}

线程死锁

多线程占用对方的资源,但是不肯相让,导致了死锁。线程一直得不到资源,而一直在相互等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Block block=new Block(true);
Block block1=new Block(false);
block.start();
block1.start();
}
}
class Block extends Thread{
boolean flag;
static Object object1=new Object();
static Object object2=new Object();
public Block(boolean flag){
this.flag=flag;
}
@Override
public void run() {
if (flag){
synchronized (object1){
System.out.println("a我进入到了1");
synchronized (object2){
System.out.println("a我进入到了2");
}
}
}
else {
synchronized (object2) {
System.out.println("b我进入到了2");

synchronized (object1) {
System.out.println("b我进入到了1");
}
}
}
}
}

image-20220318140102914

可以看到程序一直卡在这里等待对方释放资源

释放锁

  1. 当前线程的同步方法,同步代码块执行结束(正常的执行结束)
  2. 当前线程在同步代码块,同步方法中遇到return,break(不得已而出来)
  3. 当前线程在同步代码块,或者方法遇到了未处理的错误或者异常,导致异常结束
  4. 当前线程在同步代码块,同步方法中执行了对象的wait方法,当前线程暂停,注意但是sleep方法不会释放锁
  • 不会释放锁:线程执行同步代码块或者同步方法时,程序调用sleep,yield方法会暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.Scanner;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Num num=new Num();
num.start();

Scanner scanner=new Scanner(System.in);
while (true) {
String letter = scanner.next();
if (letter.equals("Q")) {
System.out.println("程序结束");
num.flag=false;
break;
}
}
}
}
class Num extends Thread{
boolean flag=true;

@Override
public void run() {
while (flag){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Math.round(Math.random()*100));
}
}
}

image-20220318142935716

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @author zss
*/
public class Test {
public static void main(String[] args) {
Credit credit=new Credit();
Thread thread=new Thread(credit);
Thread thread1=new Thread(credit);
thread.start();
thread1.start();
}
}
class Credit implements Runnable{
double balance=1000.00;
static Object object=new Object();
@Override
public void run() {
while (true){
synchronized (object){
if (balance<=0){
System.out.println("您的余额不足");
break;
}

balance=balance-100;
System.out.println(Thread.currentThread().getName()+"取出100元,还剩"+balance);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}
}

image-20220318144512275

简介

  • 程序:是为了完成特定的任务,用某种语言编写的一组指令的集合。
  • 进程就是指运行中的程序,进程是程序的一次执行过程,或者是正在运行的一个程序。是一个动态的过程,有他自身的产生,存在和消亡。
  • 线程是进程创建的,是进程的一个实体,一个进程可以拥有多个线程。
  • 单线程同一个时刻只允许执行一个线程
  • 多线程,同一个时刻,可以执行多个线程
  • 并发:同一个时刻,多个任务交替执行,造成一个“貌似同时”的错觉,简单来说,单核cpu实现多任务就是并发。
  • 并行:同一个时刻,多个任务同时执行,多核cpu可以实现并行。并发和并行可以同时存在,如果任务过多。

创建线程的两种方法

继承Thread类,重写run方法

练习1

每隔一秒输出一个语句,20秒结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
}
}
/**当一个类继承了Thread类,该类就可以当作线程使用,run方法来源于Runnable接口*/
class Cat extends Thread{
int time=0;
@Override
public void run(){
while (true){
System.out.println("喵喵,我是小喵咪");
time++;
try{
Thread.sleep(1000);}catch (Exception e){
System.out.println("发生了错误");
}
if (time==20){
break;
}
}
}
}

image-20220316170923325

通过实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
//不可调用
//cat.run();
Thread thread = new Thread(cat);
thread.start();
}
}

/**当一个类继承了Thread类,该类就可以当作线程使用,run方法来源于Runnable接口*/
class Cat implements Runnable{
@Override
public void run() {
while (true){
System.out.println("喵喵");
}
}
}

image-20220317110132194

其实我们的底层使用了静态代理模式,而之所以可以使用Thread方法传入cat,是因为构造函数中存在Thread(Runnable),而Cat继承了Runnable。

1
2
Thread thread = new Thread(cat);
thread.start();

image-20220317111254838

模拟线程代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ThreadProxy implements Runnable{
private Runnable target=null;
@Override
public void run() {
if (target!=null){
//动态绑定机制到我们新定义的run
target.run();
}
}
//源码中含有一个构造函数
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0();
}
public void start0(){
run();
}
}

总结

其实总源码上来看,这两个创建线程的机制没有本质的区别,他们的底层都是调用start0机制进行线程的创建,而且Thread其实是调用了Runnable接口的。

但是Runnable接口更加适合多个线程共享一个资源的情况(可以将两个线程同时调用cat对象),并且避免了单继承的限制。

Java线程生命周期

image-20220317220651261

注意当我们运行start时,线程并不是立即执行的,而是根据cpu的执行状况来进行判定。线程的sleep与wait是两个不同的方法,sleep会自动结束阻塞状态,而wait是进入锁池状态,需要进行唤醒。

image-20220317222950788

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
AA aa=new AA();
Thread thread=new Thread(aa);
thread.start();
for (int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println(i+""+thread.getState());
}
}
}
class AA implements Runnable{

@Override
public void run() {
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}

image-20220317223959672

利用主线程控制子线程

通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package windowSale;

/**
* @author zss
*/
public class WindowSale1 {
public static int countTicket=10;

public static void main(String[] args) {
Window1 window1 = new Window1();
Thread thread=new Thread(window1);
thread.start();
while (true){
if (countTicket<=5){
System.out.println("小于5张,请停止售票");
window1.setJudge(false);
break;
}
else {System.out.println("不要停下来");}
try{
Thread.sleep(50);
}catch (InterruptedException interruptedException){
interruptedException.printStackTrace();
}
}

}
}
class Window1 implements Runnable{
private boolean judge=true;
@Override
public void run() {
while (judge){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售卖一张票");
WindowSale1.countTicket=WindowSale1.countTicket-1;
System.out.println("还剩"+WindowSale1.countTicket);
}
}

public void setJudge(boolean judge) {
this.judge = judge;
}
}

image-20220317153122728