我们的进程是为了并发执行而设计的,但是由于频繁的创建和销毁进程十分浪费时间,因此发明了线程
进程包含于线程,当我们的进程创建时,就默认其内部有一个线程,当我们在一个进程中创建多个线程,那么这几个线程共用进程的资源,不需要额外的开销。因此我们开创线程的开销比开创进程要小,而在销毁线程时,只有把进程中的最后一个线程销毁才会把这个进程的所占用的资源销毁,所以把线程称之为轻量化的进程,
我们之前介绍通过PCB描述进程,实际上是每一个PCB对应一个线程,一个进程所有的线程对应的一系列PCB共同来描述这个进程。
虽然线程是轻量化的,但是如果我们对一份小小的内存空间开辟很多的线程,一样会出现问题。并不是越多线程任务执行的效率就越高。我们维护线程的调度是有开销的,因此当开销越来越大效率也就越来越低了。就好像同一片草原虽然一开始羊多羊群发展就越快,但是当羊的数量大于某个值后草原就容不下这么多羊了
并且,由于同一个进程下多个线程占用同一份内存资源,因此有可能出现两个线程同时访问修改的情况,这样就是线程不安全的,这个问题稍后会详细讲解
另外,如果同一个进程下的一个线程出现了问题,还有可能导致其他的线程的都无法工作
在Java中,操作系统提供的一系列关于线程的API封装成了Thread类,下面讲解一下如何使用这个类
我们继承Thread类并且重写下面的run方法,其中run方法的内容就是线程要执行的代码,然后在main中创建我们的类的实例,并且调用start方法,就可以启用这个线程。其中我们的main方法中的代码和Thread中的代码是一起执行的,也就是并发执行的关系,其执行的先后顺序是随机的
class MyThread extends Thread{
@Override
public void run() {
System.out.println("thread");
}
}
public class test {
public static void main(String[] args) {
Thread t3 = new MyThread();
t3.start();
}
}
我们可以用以下代码让线程阻塞等待,也就是休眠一段时间,单位是毫秒
Thread.sleep(1000);
public class test {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("thread");
}
};
t1.start();
}
}
Runnable是一个接口,通过重写下面的run方法,然后实例化一个runnable,在Thread实例化时调用,也可实现类似的效果,我们把线程的工作和线程本身分开了,也就是解耦合了。避免了以后在代码变更时代价很大。
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("Thread");
}
}
public class test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t4 = new Thread(myRunnable);
t4.start();
}
}
public class test {
public static void main(String[] args) {
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread");
}
});
t2.start();
}
}
public class test {
public static void main(String[] args) {
Thread t5 = new Thread(() -> {
System.out.println("Thread");
});
System.out.println(t5);
}
我们可以通过如下代码测试一下并行执行和串行执行的效率区别
public static final long COUNT = 10_0000_0000L;
public static void serial(){
long begin = System.currentTimeMillis();
long a = 0;
for (long i = 0; i < COUNT; i++) {
a++;
}
a = 0;
for (long i = 0; i < COUNT; i++) {
a++;
}
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
public static void concurrency() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
long a = 0;
for (long i = 0; i < COUNT; i++) {
a++;
}
});
Thread t2 = new Thread(() -> {
long a = 0;
for (long i = 0; i < COUNT; i++) {
a++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
需要注意的是下面这个方法是为了将线程阻塞等待,也就是当调用t1.join()的时候,main线程就阻塞等待,直到t1线程执行完毕
这两个代码执行后比较,并行执行并不是比串行执行要正好快50%,因为两个进程之间并不只是并行执行,也有并发执行
也就是两个线程各执行各的,互不干扰。
两个线程相互抢占CPU资源,因此可能t1读到的a是t2更改后的,因此效率无法预估