JAVA多线程

进程和线程的区别

进程:操作系统应用程序。占用系统的 cpu 和内存

线程:是进程更小的执行单元。一个进程可以有多个线程。线程与线程之间是相互独立的,但是是要共用进程的资源。

区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中,这些线程可以共享数据,因此线程间的通信比较简单,消耗的系统开销也相对较小。

Thread 线程类

构造方法

1
2
Thread thread = new Thread();
Thread thread = new Thread(线程对象,"线程名字");

静态方法

1
2
3
4
5
"查看线程最大级别":Thread.MAX_PRIORITY

"查看线程最小级别":Thread.MIN_PRIORITY

"查看线程默认级别":Thread.NORM_PRIORITY

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
"让线程开始执行":thread.start();  "自动调用线程的run()方法"

"返回线程的名字":thread.getName();

"返回线程的优先级":thread.getPriority();

"等待线程死亡":thread.join();
"等待线程死亡":thread.join(毫秒);

"给线程设置名字":thread.setName(名字);

"让线程睡眠":thread.sleep(毫米数);
"当前线程让出强占cpu的机会 自己马上处于就绪状态":thread.yield();

线程状态

  • 新建状态(New):使用new 关键字创建线程对象,仅仅被分配了内存
  • 就绪状态(Ready):线程对象被创建后,等待它的 start 方法被调用,以获得 CPU 的使用权
  • 运行状态(Running)执行 run 方法,此时的线程的对象正占用 CPU
  • 睡眠状态(Sleeping)调用 sleep 方法,线程被暂停,睡眠时间结束后,线程回到就绪状态,睡眠状态的线程不占用 CPU
  • 死亡状态(Dead)run 方法执行完毕后,线程进入死亡状态
  • 阻塞状态(Blocked):线程由于某些事件(如等待键盘输入)放弃 CPU,暂停运行,直到线程重新进入就绪状态,才有机会转到运行状态

创建多线程的方法

继承 Thread 类

实现步骤:

  1. 创建一个 Thread 类的子类
  2. 在 Thread 类的子类中重写 Thread 类中的 run 方法,设置线程任务
  3. 创建 Thread 类的子类对象
  4. 调用 Thread 类中的方法 start 方法,开启新的线程,执行 run 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyThread extends Thread {
// 重新run方法就是创建了多线程
@Override
public void run() {
// 放需要执行的代码
}

// 使用
public static void main(String[] args) {
MyThread et1 = new MyThread();
// 使用start方法执行
et1.start();
}
}

实现 Runnable+Thread 类的带参构造

实现步骤:

  1. 创建一个 Runnable 接口的实现类
  2. 在实现类中重写 Runnable 接口的 run 方法,设置线程任务
  3. 创建一个 Runnable 接口的实现类对象
  4. 创建 Thread 类对象,构造方法中传递 Runnable 接口的实现类对象
  5. 调研 Thread 类中的 start 方法,开启新的线程执行 run 方法

实现 Runnable 接口创建多线程程序的好处:

  1. 避免了单继承的局限性,一个类只能继承一个类,类继承了 Thread 类就不能继承其他的类,实现了 Runnable 接口,还可以继承其他的类,实现其他的接口
  2. **增强了程序的扩展性,降低了程序的耦合性(解耦)**,实现 Runnable 接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyRunnable implements Runnable{
// 实现run方法
@Override
public void run() {
// 放需要执行的代码
}
// 使用
public static void main(String[] args) {
// 用Runnable的接口实现类的实例作为Thread的target
Thread t1 = new Thread(new MyRunnable());
// 调用
t1.start();
}
}

实现 Callable+Future 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CallableTest {
public static void main(String[] args) throws Exception {
Callable<Integer> call = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 需要执行的代码
// 返回值为Integer
return 1;
}
};
// Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果
// FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口
FutureTask<Integer> task = new FutureTask<>(call);
Thread t = new Thread(task);
t.start();
}
}

匿名内部类

匿名内部类作用:简化代码

  1. 把子类继承父类,重写父类的方法,创建子类对象合一步完成
  2. 把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
  3. 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class App {
public static void main(String[] args){
// 第一个方法,使用Runnable的方式
new Thread(new Runnable() {
@Override
public void run() {
// 需要执行的代码
}
}).start();

// 第二个方法
new Thread(){
@Override
public void run(){
// 需要执行的代码
}
}.start();
}
}

java.util.Timer 工具类

1
2
3
4
5
6
7
8
9
10
11
public class TimerTest {
public static void main(String[] args) throws Exception {
// 启动定时器线程,并在10000毫秒后执行TimerTask实例的run方法,每隔3000毫秒执行一次
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// 需要执行的代码
}
}, 10000,3000);
}
}

java.util.concurrent.Executors 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ThreadPoolTest {
public static void main(String[] args) {
// 创建线程池,10个
// 通过java.util.concurrent.ExecutorService接口对象来执行任务
// 该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
@Override
public void run() {
// 需要执行的代码
}
});
}
}
}

线程同步方法

同步代码块

synchronized(互斥锁)把代码包裹起来,必须是同一把锁

注意:

  1. 通过代码块中的锁对象,可以使用任意的对象
  2. 但是必须保证多个线程使用的锁对象是同一个
  3. 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
  4. synchronized锁定的是一个对象,不是代码块
  5. 一个synchronized代码块是原子操作
  6. synchronized的锁是可重入的,即当前线程内,一个同步方法调用另一个同步方法或者子类同步方法调用父类同步方法,是可以再次获得锁
1
2
3
synchornized("锁"){
"出现线程安全的逻辑"
}

同步方法

  • 在方法的定义上面加synchronized 关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"这里锁定的是当前对象"
public synchronized void sale() {"对象锁 (必须是同一把锁 saleWindow)"
if (tickets <= 30) {
System.out.println(Thread.currentThread().getName() + "卖了第" + tickets + "张");// 获得当前线程的名称
tickets++;
}
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 等同于上面的锁方法
private Object o = new Object();
private int count = 1;
public void sale2(){
"上锁"
synchronized(o){
count++;
}
}
  • 锁定静态方法
1
2
3
4
5
6
7
8
9
10
11
12
private int count = 1;
// 这里相当于synchronized(T.class)
// 因为在静态方法中没有this
public synchronized static void m(){
count++:
}

public static void n(){
synchronized(T.class){
count++;
}
}
  • 可重入锁
1
2
3
4
5
6
7
8
9
// 在同一个线程内获得锁后可以再获得一次
synchronized void t1(){
// 执行逻辑
t2();
}

synchronized void t2(){
// 执行逻辑
}

Lock+ReentranLock 手动上锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Integer tickets = 1;
"创建一把锁"
private Lock lock = new ReentrantLock();
public void run() {
for (int i = 1; i <= 100; i++) {
"手动上锁"
lock.lock();

if (tickets <= 100) {
tickets++;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
"手动解锁"
lock.unlock();
}
}

sleep、yield、join 和 wait

  • sleep 和 yield 都是 Thread 类的静态方法,都会使当前处于运行状态的线程放弃 CPU,但两者的区别在于:
  1. sleep 给其它线程运行的机会,但不考虑其它线程的优先级;但 yield 只会让位给相同或更高优先级的线程
  2. 当线程执行了 sleep 方法后,将转到阻塞状态,而执行了 yield 方法之后,则转到就绪状态
  3. sleep 方法有可能抛出异常,而 yield 则没有,在一般情况下,我们更建议使用 sleep 方法
  • join 方法用于等待其它线程结束,当前运行的线程可以调用另一线程的 join 方法,当前运行线程将转到阻塞状态,直至另一线程执行结束,它才会恢复运行
  • wait 方法是 Object 类的方法,让当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法才会执行,必须是锁对象才能调用

线程的生命周期

线程生命周期.png

内存可见性

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

public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
if(td.isFlag){
System.out.println("-----")
break;
}
}
}

class ThreadDemo implements Runnable {
// 省略getter setter
private boolean flag = false

@Override
public void run(){
try {
Thread.sleep(200);
} catch (Exception e){}
flag = true;
}
}

内存可见性

volatile关键字

当多个线程进行操作共享数据时,可以保证内存中的数据可见,相较于synchronized是一种较为轻量级的同步策略

注意:

  1. volatile不具备互斥性
  2. volatile不能保证变量的原子性

原子变量

jdk1.5后java.util.concurrent.atomic包提供了常用的原子变量

功能:

  1. 使用volatile保证内存可见性
  2. CAS(Compare-And-Swap)算法保证数据的原子性

CAS

是硬件对于并发操作共享数据的支持,包含了三个操作数

内存值V,预估值A,更新值B,并且当V == A时,V = B,否则,将不做任何操作

CountDownLatch

闭锁,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行

LatchDemo类

使用

相关文章

JAVA基础

JDBC

JUC