基本概念
线程与进程
线程:系统资源分配的最小单位,使用独立的数据空间
进程:程序执行的最小单位,线程共享进程的数据空间
并发与并行
并发:同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
并行:同单位时间内多个任务同时执行
并发的好处与坏处
JVM的内存模型与JMM(Java Memory Model)
JMM定义了程序中变量的访问规则。所有的共享变量都存储在主内存中,每个线程有自己的工作内存,工作内存保存的是共享变量的副本。线程对变量的读写操作必须在自己的工作内存中进行,而不能直接读写主内存中的变量
线程的基本操作
线程的创建方式
- 实现Runnable接口
// 创建任务
Runnable task = () -> {
...
}
// 运行任务
new Thread(task).start()
- 继承Thread类
class Task extends Thread{
public void run(){
...
}
}
Task task = new Taskl();
task.start();
线程的状态(Thread.State)
from 《Java 并发编程艺术》
Thread.run() 和 Thread.start()的区别
- Thread.run()
public void run() {
if (target != null) {
// target的类型是Runnable,这里调用的是Runnable的run方法
target.run();
}
}
- Thread.start()
public synchronized void start() {
// 如果线程的状态不是0(not yet started),则会抛出运行异常,即一个线程不能够多次调用start方法
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
直接调用run方法是串行执行对象的run方法体.调用start方法是开启一个线程并使得线程处于RUNNABLE状态,当分配到时间片后就会执行run方法体.
wait(等待) and notify(通知)
@Test
public void waitAndNotify() {
final String flag = "";
// 线程1等待直到被唤醒后打印消息
Runnable task1 = () -> {
try {
// 去掉synchronized,抛出java.lang.IllegalMonitorStateException
synchronized (flag) {
flag.wait();
}
System.out.println("I am notified!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 线程2等待2s后唤起线程1
Runnable task2 = () -> {
try {
Thread.sleep(2000);
// 去掉synchronized,抛出java.lang.IllegalMonitorStateException
synchronized (flag) {
flag.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 启动
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
// 下面这段代码在junit测试中是必须的,如果是在main中则没有必要
try {
// 将线程1加入到当前线程,即当前测试线程会阻塞等待线程1运行完毕
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- wait和notify是Object中的方法
- wait和notify执行之前都需要获得目标对象的监视器(使用synchronized),执行后会释放监视器.
- wait和sleep的区别和联系:
- 联系:两者都可以暂停线程的执行
- 区别:
- sleep没有释放锁,而wait释放了锁;
- wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一对象上的notify或notifyAll方法.而sleep执行完成后,线程会自动苏醒;
- 所以,wait常被用于线程之间的通信.sleep常被用于暂停执行
suspend(挂起) and resume(继续执行)
- suspend 的线程必须等到resume才能够继续执行,另外,suspend的线程在挂起时不会释放锁资源,因此可能会造成死锁(一直占用锁资源不释放)
- 这两个方法被废弃了
@Test
public void suspendAndResume() throws InterruptedException {
Object obj = new Object();
Runnable task = () -> {
synchronized(obj){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " will be suspended!");
thread.suspend();
System.out.println(thread.getName() + " was resumed!");
}
};
Thread t1 = new Thread(task, "task1");
t1.start();
// 如果注释掉part1则可能会造成死锁.由于resume先于suspend被调用,也就是一直处于被挂起状态
// 记为part1 >>>
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// <<<
t1.resume();
t1.join();
}
join and yield
- join是谁加入时?谁等待?
public static void main(String[] args){
int count = 0;
Thread t = new Thread(
() -> {
for(int i = 0; i < 1000; i++){
count++;
}
}
);
t.join(); // 将t这一线程加入到main(当前线程),所以main线程等待t完成
}
stop and interrupt
- stop和interrupt的不同:
Thread.interrupt()
被调用时不会使目标线程立即退出,只是通知目标线程中断,也就是设置中断标志位。而Thread.stop()
是立即终止线程(操作过程中立即终止可能会造成数据不一致)。 - Thread.sleep过程如果中断会抛出异常,并且它会清除中断标志(所以如有需要应重新加上中断标志)
- 常用方法
- void interrupt()
- boolean isInterrupted() 判断是否被中断,不会清除标志位
- static boolean interrupted() 判断是否被中断,并清除中断标志位
- stop方法被废弃了
@Test
public void stopAndInterrupt() throws InterruptedException {
@ToString
class People {
String name = "001";
String id = "001";
}
People p1 = new People();
People p2 = new People();
Runnable t1 = () -> {
Thread thread = Thread.currentThread();
while(!thread.isInterrupted()){
System.out.println(thread.getName());
p1.name = "002";
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
p1.id = "002";
}
};
Runnable t2 = () -> {
Thread thread = Thread.currentThread();
while(!thread.isInterrupted()){
System.out.println(thread.getName());
p2.name = "002";
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// Thread.sleep由于抛出异常它会清除中断标志,所以需要重新加上
thread.interrupt();
}
p2.id = "002";
}
};
/** 使用stop暴力停止线程:数据会不一致 **/
Thread thread1 = new Thread(t1, "thread1");
thread1.start();
Thread.currentThread().sleep(1000);
thread1.stop();
System.out.println(p1);
/** 使用interrupt中断线程 **/
Thread thread2 = new Thread(t2, "thread2");
thread2.start();
Thread.currentThread().sleep(1000);
thread2.interrupt();
thread2.join(); // 必须阻塞,否则也不能获得正确的结果(因为thread2还没有修改 )
System.out.println(p2);
}
结果
thread1
People(name=002, id=001)
thread2
People(name=002, id=002)