1.5.2 泛型

1.5.2.1泛型的定义

​ 看表面的意思是,泛型是广泛的,普通的类型,泛型可以帮助我们在【类型明确】的工作延迟到对象或方法的时候。意思就是我定义类不管他是什么类型,new这个对象或调用方法的时候才确定具体的类型。

​ 泛型类就是把泛型定义到类上,这样在用户使用类的时候才可以报类型确定下来。具体的方法就是<> 加一个未知数,通常用KTV大写字母表示,事实上是一个单词就行。

java 中泛型标记符:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • - 表示不确定的 java 类型

​ 泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ydl.template;

public class Test {
public <T> T show(T t){
System.out.println(t);
return t;
}

public static void main(String[] args) {
Test test = new Test();
Integer show = test.show(1425);

}
}

类型通配符感觉和泛型方法差不多,只是不能用在使用前进行定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;

public class GenericTest {

public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();

name.add("icon");
age.add(18);
number.add(314);

getData(name);
getData(age);
getData(number);

}

public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
}

1.5.3 枚举enum

​ 在一些情况,一个类的对象有限且固定,比如说星期一到星期五,春夏秋冬等,这时候就可以使用。

静态方法

1
2
3
4
public static final Season DEFAULT= new Season("秋天", 4);
public static final Season WINTER = new Season("冬天", 3);
public static final Season SPRING= new Season("春天", 1);
public static final Season SUMMER = new Season("夏天", 2);
1
2
3
public enum SeasonEnum {
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER,
}
1
2
3
4
5
6
7
8
9
10
public enum SeasonEnum {
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER;
public static void main(String[] args) {
System.out.println(SeasonEnum.JANUARY.name());//姓名
System.out.println(SeasonEnum.JANUARY.ordinal()); //序号

}

}

​ enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:

  • values() 返回枚举类中所有的值。
  • ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
  • valueOf()方法返回指定字符串值的枚举常量。

1.5.4 多线程

​ Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

​ 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

​ 这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

​ 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。

img

  • 新建状态:

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

  • 就绪状态:

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

  • 运行状态:

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

  • 阻塞状态:

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

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

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


1.5.4.1线程的优先级

​ 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。


1.5.4.2创建一个线程

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。

(1) 继承Thread重写run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ydl.thread;

public class UseThread extends Thread {
@Override
public void run() {
System.out.println("2");
}

public static void main(String[] args) {
System.out.println(1);
new UseThread().start(); //开启线程
System.out.println(3);
}
}

(2) Runnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ydl.thread;

public class UseRunnable implements Runnable {
@Override
public void run() {
System.out.println(1);
}

public static void main(String[] args) throws InterruptedException {
System.out.println(2);
new Thread(new UseRunnable()).start();
new Thread(new UseRunnable()).sleep(20);
System.out.println(3);
}
}

(3) 通过 Callable 和 Future 创建线程。

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 com.ydl.thread;

import java.util.Formattable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class UseCallable implements Callable<Integer> {

@Override
public Integer call() throws Exception {
Thread.sleep(1000);
return 2;
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
// futureTask 保存返回值
FutureTask<Integer> futureTask = new FutureTask(new UseCallable());
new Thread(futureTask).start(); //开启线程
System.out.println("1");
Integer integer = futureTask.get(); //等待线程获取返回值
System.out.println("3");
System.out.println(integer);
}
}
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
package com.ydl.thread;

import java.util.Formattable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class UseCallable implements Callable<Integer> {
private int from;
private int to;

public UseCallable(int from, int to) {
this.from = from;
this.to = to;
}
public UseCallable() {

}

@Override
public Integer call() throws Exception {
int res=0;
for (int i= from; i < to; i++) {
res +=i;
}
return res;
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
int res= 0;
for (int i = 0; i <100000 ; i++) {
res+=i;
}
long end = System.currentTimeMillis();
System.out.println(end-start);
System.out.println(res);
//多线程
start = System.currentTimeMillis();
res= 0;
FutureTask[] futureTask = new FutureTask[5];
for (int i = 0; i <5 ; i++) {
FutureTask<Integer> task = new FutureTask<>(new UseCallable(i*20000,(i+1)*20000));
new Thread(task).start(); //开启线程
futureTask[i]=task;
}
for (int i = 0; i < futureTask.length; i++) {
int sum = (int)futureTask[i].get();
res+=sum;
}
end = System.currentTimeMillis();
System.out.println(end-start);
System.out.println(res);
}
}



下表列出了Thread类的一些重要方法:

序号 方法描述
1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority) 更改线程的优先级。
5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt() 中断线程。
8 public final boolean isAlive() 测试线程是否处于活动状态。

上述方法是被 Thread 对象调用的,下面表格的方法是 Thread 类的静态方法。

序号 方法描述
1 public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
2 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3 public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
5 public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。
1.5.4.3 线程安全的实现方法
  1. 数据不可变

    在Java当中,一切不可变的对象(immutable) 一定是线程安全的,无论是对象的方法实现还是方法的调用者。

  2. 互斥同步

    互斥同步是常见的一种正确性的保障手段,同步是指在多个线程并发访问共享数据,保证共享数据在同一时刻被同一线程使用。

  3. 非阻塞同步

    互斥同步面临的主要问题是,进行线程阻塞和唤醒带来的性能开销,因此这种同步也被称为阻塞同步,从解决问题的方式上来看互斥同步是一种【悲观的并发策略】,其总是认为,只要不去做正确的同步措施,那就肯定会出现问题,无论共享的数据是否真的出现,都会进行加锁。这
    将会导致用户态到内核态的转化、维护锁计数器和检查是否被阻塞的线程需要被唤醒等等开销。
    随看硬件指令级的发展,我们已经有了另外的选择,基于冲突检的乐观并发策略。通俗的说,就是不管有没有风险,先进行操作,如果没有其他线程征用共享数据,那就直接成功,如果共享数据确实被征用产生了冲突,就再进行补偿策略,常见的补偿策略就是不折的重试,直到
    出现设有竞争的共享数据为止,这种乐观并发策略的实现,不再需要把线程阻塞挂起,因此同步操作也被称为非阻塞同步,这种措施的代码也常常被称之为【无锁编程】。

  4. 无同步方案

    ​ 在我们这个工作当中,还经常遇到这样一种情况,多个线程需要共享数据,但是这些数据又可以再单独的线程当中计算,得出结果,而不被其他的线程所影响,如果能保证这一点,我们就可以把共享数据的可见范国限制在一个线程之内,这样就无需同步,也能够保证个个线程之间不出现数据征用的问题,说人话就是数据拿过来,我用我的,你用你的,从而保证线程安全,比如说咱们的ThreadLocal。,
    ​ ThreadLoca提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过ge和set方法就,可以得到当前线程对应的值。

1.5.5锁机制

1.5.5.1 synchronized简介

​ 在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重呈级锁。但是,随看Java SE 1.6对synchronized进行了各种优化之后,有些情况下它就并不那么重,JavaSE1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁。
synchronized有三种方式来加锁,分别是

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ydl.thread;

public class lock {
public synchronized void ray(){
System.out.println("hello");
}
public static void say() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("word");
}

public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new Thread(()->{
say();
}).start();
}
}
}

1.5.5.2 死锁

锁升级中涉及的四把锁:

  • 无锁:不加锁

  • 偏向锁:不加锁,只有一个线程争夺时,偏心某一个线程,这个线程来了不加锁。

  • 轻量级锁:少量量线程来了之后,先尝试自旋,不挂起线程

    注:挂起线程和恢复线程的操作都需要转入内核态中完成这些操作,给系统的并发性带来很大的压力。在许多应用上共享数据的锁定状态
    只会持续很短的一段时间,为了这段时间去挂机和恢复现场并不值得,我们就可以让后边请求的线程稍等一下,不要放弃处理器的执行时
    间,看看持有锁的线程是否很快就会释放,锁为了让线程等待,我们只需要让线程执行一个言循环也就是我们说的自旋,这项技术就是所调
    的【自旋锁】

  • 重量级锁:排队挂起线程

    死锁

​ 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部部在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能
正常终止。
java死锁产生的四个必要条件:

  • 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  • 不可抢占,资源清求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

​ 当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。下面用java代码来模拟一下死锁的产生。

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
package com.ydl.thread;

public class DeadLock {
public static final Object MONITOR1= new Object();
public static final Object MONITOR2= "12345";

public static void main(String[] args) {
new Thread(() -> {
synchronized (MONITOR1) {
System.out.println(Thread.currentThread().getName() + "获取一号锁");

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MONITOR2) {
System.out.println(Thread.currentThread().getName() + "获取二号锁");
}
}
},"thread1").start(); //开启线程

new Thread(() -> {
synchronized (MONITOR2) {
System.out.println(Thread.currentThread().getName() + "获取二号锁");

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MONITOR1) {
System.out.println(Thread.currentThread().getName() + "获取一号锁");
}
}
},"thread1").start();
}

}

image-20221208113445732

wait和notify 等待和唤醒

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 com.ydl.thread;

public class WaitTest {
public static final Object MONITOR= new Object();

public static void main(String[] args) {
new Thread(()->{
synchronized (MONITOR) {
System.out.println("线程1开始");
try {
MONITOR.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1结束 ");
}
}).start();
new Thread(()->{
synchronized (MONITOR) {
System.out.println("线程2开始");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
MONITOR.notify();
System.out.println("线程2结束");


}
}).start();
}
}

1.5.5.3 lock

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
package com.ydl.thread;

import sun.rmi.runtime.Log;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class lock {
private static final Lock lock = new ReentrantLock();
public synchronized void ray(){
System.out.println("hello");
}
public static void say() throws InterruptedException {
lock.lock();
try {
System.out.println("hello");
Thread.sleep(1000);
} finally {
lock.unlock();
}
System.out.println("word");
}

public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new Thread(()->{
try {
say();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}

1.5.4 线程池

Java通过Executors提供四种线程池,分别为:

  1. newCachedThreadPoo1创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPoo1创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
  4. newsing1 eThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.ydl.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Pool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Runnable task =() ->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("__________");
};
for (int i = 0; i <500 ; i++) {
executorService.submit(task); //提交任务
}

}
}