Java多线程基础

进程和线程的简介

进程

进程是程序的一次执行过程,是系统运行程序的基本单位。一个程序的运行就是从一个进程创建开始的。在Windows中查看任务管理器,就可以看见当前Windows运行的进程(.exe文件)

1340

线程

线程和进程相似,但是线程是一个比进程更小的执行单位。一个进程的执行过程中可以产生多个线程。多个线程共享同一个内存空间和一组系统资源。所以系统在产生一个线程或者是在线程之间的切换,负担要比进程小得多,所以线程又被称为轻量级的进程。

几个重要概念

同步异步

同步和异步通常形容一次方法的调用。同步方法的调用,一旦开始,调用者必须在方法调用调用返回以后,才能执行后续的操作。异步方法的调用更像是一个消息的传递,一旦开始,方法调用就会立刻放回,调用者可以执行后续的操作。

并发(Concurrency)和并行(Parallelism)

并发和并行是一个相似的概念。他们都可以表示多个任务同时执行。但是并行才是真正意义上的同时执行。并发,是多个任务交替执行。

多线程在单核CPU中是交替执行(并发)。多核CPU,每个CPU有自己的运算器,所以可以同时执行(并行)。

并行:两列队伍,两个咖啡机。

并发:两列队伍,一个咖啡机。

1458

高并发(High Concurrency)

高并发通常是指,通过设计保证系统能够同时并行处理很多请求。

高并发相关的常用指标:响应时间(response time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户等。

临界区

临界区是表示一种公共资源或者是共享数据,可以被多个线程使用。但是,同时间只能有一个线程可以使用它,一旦临界区的资源被占用,其他线程想要使用这个资源,就必须等待。在并行程序中,临界区资源是保护对象。

阻塞和非阻塞

非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前的线程,而会立刻返回,而阻塞与之相反。

使用多线程的三种常见方式

前面的两种方式很少使用,一般使用的是第三种的线程池方式。

继承Thread类

MyThread.java

1
2
3
4
5
6
7
public class MyThread extends Thread{
@Override
public void run(){
super.run();
System.out.println("MyThread");
}
}

Run.java

1
2
3
4
5
6
7
public class Run{
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束");
}
}

运行结果:

1629

从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者是随机的时间来调用线程中的run方法。

实现Runnable接口

推荐实现Runnable接口方式,因为Java单继承但是可以实现多个接口。

MyRunnable.java

1
2
3
4
5
6
public class MyThread implements Runnale{
@Override
public void run(){
System.out.println("MyRunnable");
}
}

Run.java

1
2
3
4
5
6
7
8
public class Run{
public static void main(String[] args){
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束");
}
}

运行结果:

1655

使用线程池

使用线程池的方式是最推荐的一种方式, 另外,在《阿里巴巴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
public class MyThread extends Thread {
private int count = 5;

public MyThread(String name){
super();
this.setName(name);
}

@Override
public void run(){
super.run();
while (count > 0){
count--;
System.out.println("由"+MyThread.currentThread().getName()+"计算,Count = "+count);
}
}

public static void main(String[] args) {
MyThread a = new MyThread("a");
MyThread b = new MyThread("b");
MyThread c = new MyThread("c");
a.start();
b.start();
c.start();
}
}

运行结果:

1706

由此可以看出每个线程都有一个属于自己实例变量count,他们之间相互不影响。

共享数据的情况

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 class SharedVariableThread extends Thread{
private int count = 5;

@Override
public void run(){
super.run();
while (count > 0){
count--;
System.out.println("由"+MyThread.currentThread().getName()+"计算,Count = "+count);
}
}

public static void main(String[] args) {
SharedVariableThread thread = new SharedVariableThread();
Thread a = new Thread(thread,"a");
Thread b = new Thread(thread,"b");
Thread c = new Thread(thread,"c");
Thread d = new Thread(thread,"d");
a.start();
b.start();
c.start();
d.start();
}
}

运行结果:

1714

可以发现这里已经出现了错误,

在大多数的JVM中,Count–的操作分为一下三步:

1.取得原有的Count的值

2.计算Count-1

3.对Count赋值

所以多个线程同时访问出现问题是难以避免的了。

那么有没有什么解决的方法呢

第一种是利用synchronize关键字(保证任意时刻只能有一个线程执行该方法)

第二种是利用AtomicInteger类。

一些常用的方法

currentThread()

返回当前正在执行的线程对象的引用。

getId()

返回当前线程的标识符。

getName()

返回当前线程的名称。

isAlive()

测试这个线程是否处于活动的状态。

什么是活动状态

活动状态就是线程已经启动且尚未终止。线程处于正在运行或者准备运行的状态。

sleep()

是当前正在执行的线程以指定的毫秒数”休眠”(暂时停止执行)。

interrupt()

中断当前线程。

interrupted()和isInterrupted()

interrupted():测试当前线程是否是中断状态,执行后清除状态标志为false的线程。

isInterrupted():测试当前线程是否是中断状态,但是不清楚状态标志。

setName()

更改当前线程的名称

isDeamon()

测试当前线程是否是守护线程。

setDeamon(boolean on)

将当前线程标记为deamon线程或用户线程。

join()

很多情况下,主线程生成启动了子线程,如果子线程里需要进行大量的耗时的计算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用子线程的处理结果,也就是主线程需要等到子线程执行完成以后再结束,这个时候就要用到join()方法

join()的作用:“等待该线程结束”。

yield()

yield()方法的作用就是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。但是放弃占用的时间不一定,可能很快就会重新获得CPU时间。

setPriority(int newPriority)

更改当前线程的优先级。