简述进程、线程、多线程和线程安全问题
1. 什么是进程
- 一个软件,在操作系统中运行时,我们称其为进程。
- 进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
2. 什么是线程
- 在一个进程中,每个独立的功能都需要独立的去运行,这时又需要把当前这个进程划分成多个运行区域,每个独立的小区域(小单元)称为一个线程。线程是进程的一个实体,是 CPU 调度和分派的基本单位,是比进程更小的能独立运行的基本单位。
- 一个程序至少有一个进程,一个进程至少有一个线程。
3. 什么是多线程
- 首先我们来了解一下串行、并行和并发三个概念,这有助于我们了解多线程。
- 串行:一条线程按照顺序依次执行所有任务。
- 并行:同一时间多个线程同时执行,没有执行的先后顺序。
- 并发:同一时间有多个可以执行的线程,一条执行,其它等待。
- 一个进程如果只执行一条执行任务或一个线程依次执行多个执行任务,则称为单线程程序。一个进程拥有多个执行任务,并且同时开启多个线程去完成这些任务(并发),则称为多线程程序。
- 多个线程同步执行多个任务,这样做的优点是防止线程堵塞,增强用户体验和程序的效率,但同时也会面临一个问题:多个线程同时访问相同的资源并进行读写操作可能会出现线程安全问题。
4. 如何实现多线程
- 在Java中实现多线程一共有四种方法,这四种方法其实也可以分为两类
- java 5 之前就有的方法
- 1.继承Thread类,重写run方法,没有返回值(其实Thread类本身也实现了Runnable接口)
- 2.实现Runnable接口,重写run方法,没有返回值
- java 5 之后出现的方法
- 3.实现Callable接口,重写call方法,有返回值
- 4.使用线程池
- 1.继承Thread
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("my thread .....");
}
}
- 2.实现Runable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("my runnable ....");
}
}
- 3.实现 Callable 接口
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class MyTask implements Callable<Integer> {
private int upperBounds;
public MyTask(int upperBounds) {
this.upperBounds = upperBounds;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= upperBounds; i++) {
sum += i;
}
return sum;
}
}
class Test01 {
public static void main(String[] args) throws Exception {
List<Future<Integer>> list = new ArrayList<>();
ExecutorService service = Executors.newFixedThreadPool(10);
for(int i = 0; i < 10; i++) {
list.add(service.submit(new MyTask((int) (Math.random() *
100))));
}
int sum = 0;
for(Future<Integer> future : list) {
// while(!future.isDone()) ;
sum += future.get();
}
System.out.println(sum);
}
}
- 4.实现线程池。Java API 提供了 Executor 框架让你可以创建不同的线程池,有单线程的,也有多线程的相关的。共有4种线程池:
- 1)newCachedThreadPool 创建一个可缓存线程池
public void cacheThreadPoolTest() { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 1; i <= 5; i++) { final int j = i; try { Thread.sleep(j * 1); } catch (InterruptedException e) { e.printStackTrace(); } cachedThreadPool.execute(()->out.println("线程:" + Thread.currentThread().getName() + ",执行:" + j)); } }
- 2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。
public void fixTheadPoolTest() { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { final int j = i; fixedThreadPool.execute(() -> { out.println("线程:" + Thread.currentThread().getName() + ",执行:" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }); } }
- 3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
public void sceduleThreadPoolTest() { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); Runnable r1 = () -> out.println("线程:" + Thread.currentThread().getName() + ",执行:3秒后执行此任务"); scheduledThreadPool.schedule(r1, 3, TimeUnit.SECONDS); Runnable r2 = () -> out.println("线程:" + Thread.currentThread().getName() + ",执行:延迟2秒执行一次后每1秒执行一次"); scheduledThreadPool.scheduleAtFixedRate(r2, 2, 1, TimeUnit.SECONDS); Runnable r3 = () -> out.println("线程:" + Thread.currentThread().getName() + ",执行:普通任务"); for (int i = 0; i < 3; i++) { scheduledThreadPool.execute(r3); } scheduledThreadPool.shutdown(); }
- 4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。
public void singleTheadExecutorTest() { ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++) { final int j = i; singleThreadExecutor.execute(() -> out.println("线程:"+Thread.currentThread().getName() + ",执行:" + j)); } }
5. 什么是线程安全问题
- 多线程环境中,且存在数据共享,一个线程访问的共享数据被其它线程修改了,那么就发生了线程安全问题;整个访问过程中,无一共享的数据被其他线程修改,就是线程安全的。
- 程序中如果使用成员变量,且对成员变量进行数据修改,就存现数据共享问题,也就是线程安全问题。
6. Java如何解决线程安全问题
-
使用线程同步机制,使得在同一时间只能由一个线程修改共享数据
- Lock 锁机制
Lock lock = new ReentrantLock(); lock.lock(); try{ //可能会出现线程安全的操作 }finally{ //释放锁 lock.unlock();
- synchronized 关键字
public synchronized void test01(){ //该方法同一时间只能被一个线程访问 } public void test02(String str){ System.out.println("111"); synchronized (str) { //该区域同一时间只能被一个线程访问 } System.out.println("2222"); }
- 第三方工具
可以使用第三方工具实现锁,例:redis、zookeeper、mysql
- 消除共享数据:即多个线程数据不共享或者共享的数据不做修改。
- 使用局部变量,不使用成员变量。
- 如果使用成员变量,对成员变量不进行修改。