在目前的现实场景中,为了提高生产率与高效地完成任务,处处都会采用多线程和并发的运作方式。首先从并发与并行、进程与线程的概念讲起。
并发与并行
并发是指在某个时间段内,多任务交替处理的能力。每个CPU不可能只顾着执行某个进程,让其他进程一直处于等待的状态下,故CPU把可执行时间均匀地分为若干份,每个进程执行一段时间后,记录当前的工作状态,释放相关的执行资源并进行等待状态,让其他进程使用CPU资源。
并行是同时处理多任务的能力。现在的CPU都是多核的,可以同时执行多个互不依赖的指令及执行块。
并发与并行的核心区别是进程是否同时执行。
以银行前台为例。假设有两个窗口同时工作,这就是两个并行任务;其中一个窗口,一会办理存取款业务,一会办理转账业务,一会有办理开户服务在进行存取款服务,这就是并发。
进程与线程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程(有自身的产生、存在和消亡的过程),是应用程序运行的载体。
线程:进程中的一个执行任务(控制单元),由进程创建,是进程的一个实体,负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程。一个进程中的多个线程共享相同的内存单元/内存地址空间,他们从同一堆中分配对象,可以访问相同的变量和对象。这样的好处是线程间的通信简便、高效,缺点是线程操作共享的系统资源可能会带来安全隐患。
进程是操作系统资源分配和调度的基本单位,而线程是处理器任务调度和执行的基本单位。
线程在官方文档中有六种状态:
新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITTING)、计时等待(TIME_WAITTING)、终止(TERMINATED)。
大家也许听过线程有7种状态,其实就是在RUNNABLE这一状态中又分为就绪状态(READY)和运行状态(RUNNING)。
线程同步
引入案例
在使用多线程时,不得不考虑线程安全的问题,因为各个线程轮流占用CPU的计算资源,可能会出现某个线程尚未执行完就出现不得不中断的情况,容易导致线程不安全。
比如银行取钱的时候,假设两个客户对同一个账户随机进行存、取钱操作,此时两个客户就是两个线程。在取钱的时候,首先要判断客户取钱金额小于等于账户余额。但是此时会出现一个问题,就是客户A判断余额条件成立,还没有取钱的时候,B客户已经进来取钱并完成,那么A客户的取钱操作就很容易导致账户余额小于0,这是不允许的。
那么为了保证线程安全,在多个线程并发地竞争共享资源时,同步机制协调各个线程的执行,以确保的得到正确的结果。
概念
当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,知道该线程完成操作,其他线程才能对此内存进行操作,即在同一时刻内,最多只有一个线程访问,以保证数据的完整性。
同步具体方法
1.同步代码块
synchronized (对象) { //得到对象的锁,才能操作同步代码
//需要被同步代码
}
2.同步方法
public synchronized void m (参数列表){
//需要被同步代码
}
同步具体案例
下边就以取钱问题实现上边案例,在账户余额为0时,停止操作。
1.同步代码块
public class Bank {
int money;//账户余额
boolean flag = true;//停止标记
public Bank(int money){
this.money = money;
}
//存钱
public void saving(int smoney) {
money += smoney;
}
//取钱
public void withdraw(int wmoney) {
if((money - wmoney)<0){
flag = false;
}else {
money -= wmoney;
}
}
}
public class Client implements Runnable{
Bank b;
private boolean flag = true;
public Client(Bank b){
this.b = b;
}
@Override
public void run() {
while (flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//同步代码块
synchronized (this) {
if (b.flag) {
Random r = new Random();
int a = r.nextInt(2);//1存钱 0取钱
int c = r.nextInt(1000) + 1;//存取钱的随机金额
if (a == 1) {
b.saving(c);
System.out.println(Thread.currentThread().getName() +
"线程存入" + c + "元,账户余额:" + b.money);
} else if (a == 0) {
b.withdraw(c);
//根据停止标记判断操作是否停止
if (b.flag == false) {
flag = false;
System.out.println(Thread.currentThread().getName() +
"线程提取" + c + "元,账户余额不足,账户余额:" + b.money );
System.out.println("结束");
} else {
System.out.println(Thread.currentThread().getName() +
"线程提取" + c + "元,账户余额:" + b.money );
}
}
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Bank bank = new Bank(1000);//账户余额为1000元
Client client = new Client(bank);
new Thread(client,"客户A").start();
new Thread(client,"客户B").start();
}
}
执行结果:
2.同步方法
public class Client implements Runnable{
Bank b;
private boolean flag = true;
public Client(Bank b){
this.b = b;
}
@Override
public void run() {
while (flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
m();
}
}
//同步代码块
public synchronized void m(){
if (b.flag) {
Random r = new Random();
int a = r.nextInt(2);//1存钱 0取钱
int c = r.nextInt(1000) + 1;
if (a == 1) {
b.saving(c);
System.out.println(Thread.currentThread().getName() +
"线程存入" + c + "元,账户余额:" + b.money);
} else if (a == 0) {
b.withdraw(c);
if (b.flag == false) {
flag = false;
System.out.println(Thread.currentThread().getName() +
"线程提取" + c + "元,账户余额不足,账户余额:" + b.money );
System.out.println("结束");
} else {
System.out.println(Thread.currentThread().getName() +
"线程提取" + c + "元,账户余额:" + b.money );
}
}
}
}
}
执行结果