文章目录
多线程并发是在java开发过程中比较重要的技术,从本节文章开始总结一些对java并发编程的一些学习心得和实战经验。
一、线程是什么?
进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程是进程的一个执行路径,一个进程中至少有一个线程,进程中多个线程共享进程的资源。
二、线程实现方法
1.继承Thread类重写run方法
代码如下:
package cn.com.lingee.thread;
/**
* 多线程实现方式一
*
* @author lingee
* @date 2020-11-04
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("子线程" + this.getName() + "启动...");
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyThread myThread = new MyThread();
myThread.setName("myThread-" + i);
myThread.start();
}
}
}
2.实现Runable接口实现run方法
代码如下:
package cn.com.lingee.thread;
/**
* 多线程实现方式二
*
* @author lingee
* @date 2020-11-04
*/
public class RunableTest {
static class MyRunable implements Runnable {
@Override
public void run() {
System.out.println("子线程" + Thread.currentThread().getName() + "启动...");
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new MyRunable());
thread.setName("myThread-" + i);
thread.start();
}
}
}
3.FutureTask创建任务,实现Callable接口的call()方法
代码如下:
package cn.com.lingee.thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* 多线程实现方式三
* * @author lingee
* @date 2020-11-04
*/
public class CallableTest {
static class CallerTask implements Callable<String> {
@Override
public String call() {
return "子线程" + Thread.currentThread().getName() + "运行成功";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
FutureTask<String> future = new FutureTask<>(new CallerTask());
new Thread(future).start();
futures.add(future);
}
for (Future<String> future : futures) {
while (true) {
if (future.isDone() && !future.isCancelled()) {
String result = future.get();
System.out.println(result);
break;
} else {
Thread.sleep(1000);
}
}
}
}
}
三、线程常用方法
- Thread.currentThead():获取当前线程对象
- getPriority():获取当前线程的优先级
- setPriority():设置当前线程的优先级
注意:线程优先级高,被CPU调度的概率大,但不代表一定会运行,还有小概率运行优先级低的线程。
-
isAlive():判断线程是否处于活动状态 (线程调用start后,即处于活动状态)
-
join():调用join方法的线程强制执行,其他线程处于阻塞状态,等该线程执行完后,其他线程再执行。有可能被外界中断产生InterruptedException 中断异常。
-
sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。休眠的线程进入阻塞状态。
-
yield():调用yield方法的线程,会礼让其他线程先运行。(大概率其他线程先运行,小概率自己还会运行)
-
interrupt():中断线程
-
wait():导致线程等待,进入堵塞状态。该方法要在同步方法或者同步代码块中才使用的
-
notify():唤醒当前线程,进入运行状态。该方法要在同步方法或者同步代码块中才使用的
-
notifyAll():唤醒所有等待的线程。该方法要在同步方法或者同步代码块中才使用的
四、线程状态
- NEW(线程创建状态)
- RUNNABLE(线程就绪状态)
- RUNNING(线程运行状态)
- BLOCKED(线程阻塞状态)
- TERMINATED(线程死亡状态)
生命周图:
1. 线程的NEW状态
当使用new关键字来创建一个线程对象时,在没有调用该对象的start方法之前,该线程并没有处于执行状态,因为线程没有启动,压根儿就不存在。和创建一个其他普通对象一样,只有通过调用start方法才能使线程进入到RUNNABLE状态。
2. 线程的RUNNABLE状态
只有调用线程对象的start方法之后,线程才会进入到该状态。但是进入到该状态的线程并不会立刻去执行。必须等到CPU调度,得到CPU分配的时间片之后,才可以执行,进入到RUNNING状态。所以在调用start方法之后,进入RUNNING之前的这段时间,线程状态为RUNNABLE状态,即:可执行状态。该状态下只能进入到RUNNING状态或者意外终止,不能进入到BLOCKED状态或者TERMINATED状态。
3. 线程的RUNNING状态
当处于RUNNABLE状态的线程得到CPU分配的时间片之后,才可以真正去执行线程内部的处理逻辑,处于RUNNING状态的线程,本身也是RUNNABLE的,但是处于RUNNABLE状态的线程不能也是RUNNING的。处于该状态下的线程,通过CPU调度或者中断等操作可以发生以下的状态转变:
- 调用线程的stop方法进入到TERMINATED状态
- 调用线程的sleep方法或者wait方法进入到BLOCKED状态
- 执行带阻塞的IO操作,进入到BLOCKED状态
- 进行锁竞争,进入到锁等待队列中,处于BLOCKED状态
- 由于受到CPU的调度,将时间片分配给了其他线程,则会进入到RUNNABLE状态
- 线程本身调用了yield方法,将CPU执行权放弃,进入到RUNNABLE状态
4. 线程的BLOCKED状态
线程由于锁竞争,IO阻塞,CPU调度或者其他原因进入到了BLOCKED状态之后,可以再次切换到以下几种状态:
- 调用stop()方法或者由于JVM意外崩溃而直接进入到TERMINATED状态
- 阻塞状态结束,比如IO操作完成之后,会再次进入到RUNNABLE状态
- 调用sleep之后,过了一段时间,休眠完成,会重新进入到RUNNALBE状态
- 调用wait方法之后,由于其他线程使用了notify或者notifyAll,将线程唤醒,此时会进入到RUNNABLE状态
- 锁竞争中,等待一段时间之后获取到了锁资源,进入到RUNNABLE状态
5. 线程的TERMINATED状态
如果线程进入到了TERMINATED状态之后,将没有机会再次进入到其他状态。因为该状态为线程生命周期中的最后一个状态,下面的几种情况将会使线程进入到TERMINATED状态:
- 线程生命周期正常结束,也就是线程中的执行逻辑正常结束
- 线程运行时出现异常意外结束
- JVM进程崩溃,会导致线程直接进入到TERMINATED状态
五、线程同步
因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。
1.使用synchronized关键字
1.1同步方法
即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。示例:
package cn.com.lingee.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Synchronized测试类
*
* @author lingee
* @date 2020-11-19
*/
public class SynchronizedTest {
private int num = 0;
public synchronized void testSync() {
num++;
}
public static void main(String[] args) {
SynchronizedTest syncTestMethod = new SynchronizedTest();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
syncTestMethod.testSync();
System.out.println("同步方法:" + syncTestMethod.getNum());
});
}
}
public int getNum() {
return num;
}
}
输出结果:
同步方法:1
同步方法:2
同步方法:3
同步方法:4
同步方法:5
同步方法:6
同步方法:7
同步方法:8
同步方法:9
同步方法:10
1.2同步代码块
即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步,示例:
package cn.com.lingee.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Synchronized测试类
*
* @author lingee
* @date 2020-11-19
*/
public class SynchronizedTest {
Object obj = new Object();
private int num = 0;
public void testSyncBlock() {
synchronized (obj) {
num++;
}
}
public static void main(String[] args) {
SynchronizedTest syncTestMethod = new SynchronizedTest();
ExecutorService executorService = Executors.newFixedThreadPool(10);
SynchronizedTest syncTestBlock = new SynchronizedTest();
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
syncTestBlock.testSyncBlock();
System.out.println("同步代码块:" + syncTestBlock.getNum());
});
}
}
public int getNum() {
return num;
}
}
输出结果:
同步代码块:1
同步代码块:3
同步代码块:6
同步代码块:2
同步代码块:8
同步代码块:9
同步代码块:7
同步代码块:5
同步代码块:4
同步代码块:10
2.lock接口
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。代码示例:
package cn.com.lingee.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lock同步测试类
*
* @author lingee
* @date 2020-11-20
*/
public class LockSyncTest {
private int num = 0;
public void testLock() {
Lock lock = new ReentrantLock();
try {
lock.lock();
num++;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
LockSyncTest lockSyncTest = new LockSyncTest();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
lockSyncTest.testLock();
System.out.println("lock方法:" + lockSyncTest.getNum());
});
}
}
public int getNum() {
return num;
}
}
输出结果:
lock方法:1
lock方法:2
lock方法:3
lock方法:4
lock方法:5
lock方法:6
lock方法:7
lock方法:8
lock方法:9
lock方法:10
ReentrantLock(可重入锁)是Lock的一个实现类,可以代替synchronized的功能,但是ReentrantLock需要手动释放锁,要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁。
3.使用volatile实现线程同步
volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值,volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。代码示例:
package cn.com.lingee.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* volatile同步测试类
*
* @author lingee
* @date 2020-11-20
*/
public class VolatileSyncTest {
private volatile int num = 0;
public void testVolatile() {
num++;
}
public static void main(String[] args) {
VolatileSyncTest volatileSyncTest = new VolatileSyncTest();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
volatileSyncTest.testVolatile();
System.out.println("volatile同步:" + volatileSyncTest.getNum());
});
}
}
public int getNum() {
return num;
}
}
输出结果:
volatile同步:1
volatile同步:2
volatile同步:3
volatile同步:4
volatile同步:5
volatile同步:6
volatile同步:7
volatile同步:8
volatile同步:9
volatile同步:10
4.使用原子变量实现线程同步
在JDK5新增并发编程包中有一个子包,java.util.concurrent.atomic,此包中定义了对单一变量执行原子操作的类。所有类都有get和set方法,工作方法和对volatile变量的读取和写入一样。此包提供了原子变量的9种风格(AtomicInteger,AtomicLong,AtomicReference、AtomicBoolean,原子整型,长型,引用、及原子标记引用和戳记引用类的数组形式,其原子地更新一对值)原子变量类共有12个,分成4组:计量器,域更新器,数组以及复合变量。最常用的原子变量是计量器:AtomicInteger,AtomicLong,AtomicBoolean及AtomicReference。代码示例:
package cn.com.lingee.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 原子类同步测试类
*
* @author lingee
* @date 2020-11-20
*/
public class AtomicSyncTest {
private AtomicInteger num = new AtomicInteger();
public int testAtomic() {
return num.addAndGet(1);
}
public static void main(String[] args) {
AtomicSyncTest atomicSyncTest = new AtomicSyncTest();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println("原子类同步:" + atomicSyncTest.testAtomic());
});
}
}
}
结果输出:
原子类同步:1
原子类同步:2
原子类同步:3
原子类同步:4
原子类同步:5
原子类同步:6
原子类同步:7
原子类同步:8
原子类同步:9
原子类同步:10
5.使用阻塞队列实现线程同步
上面关于同步的实现方式是Java并发程序设计基础的底层构建块,在实际的编程使用中,使用较高层次的类库会相对安全方便。对于典型的生产者和消费者问题,可以使用阻塞队列解决,这样就不用考虑锁和条件的问题了。
java.util.concurrent包提供了几种形式的阻塞队列:
- LinkedBlockingQueue:无容量限制,链表实现;
- LinkedBlockingDeque:双向队列,链表实现;
- ArrayBlockingQueue:需指定容量,可指定公平性,循环数组实现;
- PriorityBlockingQueue:无边界优先队列,用堆实现。
代码示例:
package cn.com.lingee.sync;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 阻塞队列同步测试类
*
* @author lingee
* @date 2020-11-20
*/
public class BlockQueueSyncTest {
private int num = 0;
BlockingQueue blockingQueue = new ArrayBlockingQueue(10);
public void testBlockQueue() {
try {
if (null != blockingQueue.poll()) {
num = (int) blockingQueue.take();
}
blockingQueue.offer(++num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BlockQueueSyncTest blockQueueSyncTest = new BlockQueueSyncTest();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
blockQueueSyncTest.testBlockQueue();
try {
System.out.println("阻塞队列同步:" + blockQueueSyncTest.getBlockingQueue().take());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
public BlockingQueue getBlockingQueue() {
return blockingQueue;
}
}
输出结果:
阻塞队列同步:1
阻塞队列同步:2
阻塞队列同步:3
阻塞队列同步:4
阻塞队列同步:5
阻塞队列同步:6
阻塞队列同步:7
阻塞队列同步:8
阻塞队列同步:9
阻塞队列同步:10
总结
以上就是本篇文章要讲的内容,本文从线程的定义、线程实现方式、线程常用方法、线程状态迁移、线程同步的五种方法来介绍了线程的基本概念。