Java并发编程
进程与线程
进程
- 程序由指令和数据构成,但这些指令要运行,数据要读写,就必须将指令加载在CPU,数据加载进内存,在指令运行过程中还需要用到磁盘、网络等设备,进程就是用来加载指令、管理内存、管理IO的
- 当一个程序被运行,从磁盘加载带这个程序的代码到内存,此时就开启了一个进程
- 进程就可以认为是程序的一个实例。
线程
- 一个进程可以分为一到多个线程
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行
- Java中线程作为最小的调度单位,进程最为资源分配的最小单位,在Windows中进程是不活动的,只是作为线程的容器
进程与线程的对比
- 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间通信较为复杂
- 同一台计算机的进程通信称为 IPC(Inter-process communication)
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
- 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
并行与并发
- 并发(concurrent)是同一时间应对(dealing with)多件事情的能力
- 并行(parallel)是同一时间动手做(doing)多件事情的能力
Java线程
创建和运行线程
1. 直接使用Thread
Thread t = new Thread(){
@Override
public void run(){
log.debug("running");
}
};
t.setName("t1");
t.start();
2. 使用Runable配合Thread
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("running");
}
};
Thread thread = new Thread(runnable,"t2");
thread.start();
- 与方法1比较
- 1是将线程和方法合并在了一起,2是把线程和任务分开
- 使用Runable更容易与线程池等高级API配合
- 用Runable让任务类脱离了Thread继承体系,更灵活
3. FutureTask配合Thread
- FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
FutureTask<Integer> futureTask = new FutureTask<>(
new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("running...");
Thread.sleep(1000);
return 100;
}
});
Thread thread = new Thread(futureTask,"t3");
thread.start();
log.debug("{}",futureTask.get());
start与run
调用Run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
}
};
t1.run();
log.debug("完成。。。。。。");
}
22:03:29.423 [main] DEBUG c.Test09 - main
22:03:29.425 [main] DEBUG c.Test09 - 完成。。。。。。
调用start
- 将上述的run改成start,输出日志如下,两个输出以不同的线程输出
22:06:42.781 [t1] DEBUG c.Test09 - t1
22:06:42.781 [main] DEBUG c.Test09 - 完成。。。。。。
总结
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
sleep与yield
方法名 | static | 功能说明 |
---|
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出 cpu的时间片给其它线程 |
yield() | static | 提示线程调度器让出当前线程对CPU的使用 |
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
线程优先级的设定
- 线程对象可以通过
setPriority();
方法来设定优先级 - 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
join方法
join()方法分为有参和无参,
方法名 | static | 功能说明 |
---|
join | | 等待线程运行结束 |
join(long n) | | 等待线程运行结束,最多等待 n毫秒 |
join()
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test12();
}
private static void test12() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
- 第一个 join:等待 t1 时, t2 并没有停止, 而在运行
- 第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s
- 输出为
22:31:55.281 [main] DEBUG c.Test12 - r1: 10 r2: 20 cost: 2005
join(long n)
等待够时间
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test11();
}
public static void test11() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
22:34:22.580 [main] DEBUG c.Test11 - r1: 10 r2: 0 cost: 1006
等待超时
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test13();
}
public static void test13() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
22:36:17.886 [main] DEBUG c.Test13 - r1: 0 r2: 0 cost: 1506
interrupt 方法
- 打断 sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态
- 打断 sleep 的线程, 会清空打断状态,以 sleep 为例
private static void test1() throws InterruptedException {
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
log.debug(" 打断状态: {}", t1.isInterrupted());
}
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.wjl.code01.Test01.lambda$test1$0(Test01.java:18)
at java.lang.Thread.run(Thread.java:750)
22:39:48.973 [main] DEBUG c.Test01 - 打断状态: false
private static void test2() throws InterruptedException {
Thread t2 = new Thread(()->{
while(true) {
Thread current = Thread.currentThread();
boolean interrupted = current.isInterrupted();
if(interrupted) {
log.debug(" 打断状态: {}", interrupted);
break;
}
}
}, "t2");
t2.start();
Thread.sleep(500);
t2.interrupt();
}
22:42:04.128 [t2] DEBUG c.Test01 - 打断状态: true