1.认识线程
1.1线程是什么
1.2为什么要用线程
其一,并发编程成为刚需。单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源,有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
其二,虽然多进程也能实现并发编程, 但是线程比进程更轻量.
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.
其三,线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程"
1.3进程与线程的区别
1)进程包含线程
每一个进程至少有一个线程存在,即主线程。
2)进程与进程之间是不共享内存空间的,而同一个进程的线程之间是共享内存空间的。
3)进程是系统分配资源的最小单位,线程是系统调度的最小单位
1.4第一个多线程程序
多线程程序与普通程序的区别在于:每个线程都是一个独立的执行流,多个线程之间是并发执行的
import java.util.Random;
public class ThreadDemo {
private static class MyThread extends Thread {
@Override
public void run() {
Random random = new Random();
while (true) {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
// 随机停止运行 0-9 秒
使用 jconsole 命令观察线程
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
Random random = new Random();
while (true) {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
// 随机停止运行 0-9 秒
e.printStackTrace();
}
}
}
}
1.5创建线程(重点)
方法一:继承Thread类
class MyThread extends Thread{
//继承以后,要将父类中的方法重写
@Override
public void run() {
//线程的入口方法
while (true){
System.out.println("hello thread");
//这个地方只能用try catch,因为父类的方法没有throws,所以子类的也没法throws
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
//start和run都是thread的成员。
//run只是描述了线程的入口(线程要做什么任务)
//start则是真正调用了系统API,在系统中创建出线程,让线程再调用run
//t.run();
t.start();
while (true){
System.out.println("hello main");
Thread.sleep(3000);
}
}
}
方法二:实现Runnable接口
class MyRunnable implements Runnable{
@Override
public void run() {
while(true) {
System.out.println("Hello MyRunnable");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class demo2 {
public static void main(String[] args) throws InterruptedException {
//Runnable表示一个可以运行的任务,这个任务是交给线程负责执行,还是交给其他实体来执行
//Runnable本身并不关心
//终究还是通过t.start来调用系统api来完成创建线程的工作
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
//Thread t = new Thread(new MyRunnable()); //等价写法
t.start();
while (true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
对比以上两种方法:
继承Thread类,直接使用this就表示当前线程对象的引用。
实现Runnable接口,this 表示的是 MyRunnable 的引用。需要使用 Thread.currentThread()
方法三:匿名内部类创建Thread子类对象
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t =new Thread(){
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while (true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
方法四:匿名内部类创建Runnable子类对象
public class demo4 {
public static void main(String[] args) throws InterruptedException {
// Runnable runnable = new Runnable() {
// @Override
// public void run() {
// while (true) {
// System.out.println("hello");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
// }
// };
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t.start();
while (true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
方法五:lambda表达式创建Runnable子类对象(最推荐)
public class demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("hello lambda");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
2.Thread类及常见方法
2.1Thread的常见构造方法
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
属性 | 获取方法 |
ID | getld() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID是线程的唯一标识,不同线程不会重复。
名称是各种调试工具可以用到
2.2启动一个线程
start启动,重写run只是相当于修改了run的内容,只有使用t.start()方法,才是真正执行线程。
2.3中断一个线程
public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
target.isQuit = true;
}
}
第二种如下(推荐)
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
// 两种方法均可以
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()
+ ": 有内鬼,终止交易!");
// 注意此处的 break
break;
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
thread.interrupt();
}
}
注意区分Thread.interrupted()和Thread.currentThread().isInterrupted(),可以理解为是否需要归位。比如变为true或false以后,后续操作是继续保持当前的true/false还是归位成初始的true/false。
//使用interrupt会清楚标志位
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
thread.start();
thread.interrupt();
}
}
// 只有一开始是 true,后边都是 false,因为标志位被清
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
thread.start();
thread.interrupt();
}
}
// 全部是 true,因为标志位没有被清
2.4等待一个进程join()
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ": 我还在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我结束了!");
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始工作");
thread1.start();
thread1.join();
System.out.println("李四工作结束了,让王五开始工作");
thread2.start();
thread2.join();
System.out.println("王五工作结束了");
}
}
3.线程的状态
3.1观察线程的所有状态
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
3.2观察线程状态和转移
观察1:关注NEW、RUNNABLE、TERMINATED的状态的转换
//使用isAlive()来判断线程是否活着
public class ThreadStateTransfer {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 1000_0000; i++) {
}
}, "李四");
System.out.println(t.getName() + ": " + t.getState());
t.start();
while (t.isAlive()) {
System.out.println(t.getName() + ": " + t.getState());
}
System.out.println(t.getName() + ": " + t.getState());
}
}
观察2:关注WATTING、BLOCKED、TIME_WAITTING状态的转换
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("hehe");
}
}
}, "t2");
t2.start();
}
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("张三");
// 先注释掉, 再放开
// Thread.yield();
}
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("李四");
}
}
}, "t2");
t2.start();
4.多线程的安全问题(重点)
4.1现象
多线程存在安全问题,主要是各个线程中指令交叉执行。
//线程安全
public class demo11 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
//对count变量自增5w次
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
count++;
}
}
});
Thread t2 = new Thread(() -> {
//对count变量自增5w次
for (int i = 0; i < 50000; i++) {
synchronized (locker){
count++;
}
}
});
t1.start();
t2.start();
t1.join();
//如果没有join,线程还没执行完就开始打印了
t2.join();
//预期结果应该是10w,但是此时结果与10w相差甚远
System.out.println("count:" + count);
}
}
4.2原因
4.3解决线程不安全的方法
static class Counter {
public int count = 0;
synchronized void increase() {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
final Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.count);
}
4.4synchronized 关键字
synchronized有以下特性
1)互斥
2)可重入
static class Counter {
public int count = 0;
synchronized void increase() {
count++;
}
synchronized void increase2() {
increase();
}
}
4.5synchronized使用示例
1)直接修饰普通方法。
public class SynchronizedDemo {
public synchronized void methond() {
}
}
2)修饰静态方法。
public class SynchronizedDemo {
public synchronized static void method() {
}
}
3)修饰代码块。
//锁当前对象
public class SynchronizedDemo {
public void method() {
synchronized (this) {
}
}
}
//锁类对象
public class SynchronizedDemo {
public void method() {
synchronized (SynchronizedDemo.class) {
}
}
}
5.volatile关键字
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
// do nothing
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
// 执行效果
// 当用户输入非0值时, t1 线程循环不会结束. (这显然是一个 bug)
static class Counter {
public volatile int flag = 0;
}
// 执行效果
// 当用户输入非0值时, t1 线程循环能够立即结束.
static class Counter {
volatile public int count = 0;
void increase() {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
final Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.count);
}
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (true) {
synchronized (counter) {
if (counter.flag != 0) {
break;
}
}
// do nothing
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}