1、多线程使用
1、创建线程
2、调用线程对象的start方法来启动线程、
线程首先是需要去争夺CPU资源,当拿到资源之后才能执行任务
java
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i + "=====");
}
}
}
java
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2、实现 Runnable 接口
第一种形式线程和任务的耦合度过高,不利于程序的扩展,所以开发中不推荐使用
- 实现解耦合
java
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i + "===");
}
}
}
java
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
thread1.start();
}
}
线程的状态
线程一共有5种状态,在特定的情况下,线程可以在不同状态之间切换
- 创建状态:实例化了一个新的线程对象,还未启动
- 就绪状态:创建好的线程对象调用了 start() 方法完成启动,进入线程池等待抢占CPU资源
- 运行状态:线程对象获取了CPU资源,在规定时间内执行任务
- 阻塞状态:正在运行的线程暂停执行任务,释放所占用的CPU资源
- 终止状态:线程运行完毕或因为异常导致线程终止运行
lambda表达式
- 使用内部类简化代码
java
public class Main {
public static void main(String[] args) {
MyRunnable3 myRunnable3 = new MyRunnable3();
Thread thread = new Thread(myRunnable3);
thread.start();
}
static class MyRunnable3 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i + "=====");
}
}
}
}
- 使用匿名内部类简化代码
java
public class Main {
public static void main(String[] args) {
Runnable myRunnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i + "====");
}
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
- 使用 lambda 表达式简化代码
java
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println(i + "====");
}
});
thread.start();
}
}
- 将接口作为参数传递的场景下,可以使用 lambda 进行简化
接口里只能有一个方法
java
public interface MyRunnable3 {
public void test();
}
java
public class MyTest {
public void test(MyRunnable3 myRunnable3) {
myRunnable3.test();
}
}
java
public class Main {
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.test(() -> {
System.out.println(111);
});
}
}
3、线程休眠
休眠是指让当前线程暂停执行,从运行状态进入阻塞状态,从而将CPU资源让给其他线程的一种调度方式,通过
sleep方法来实现
不是谁调用sleep就让谁休眠,而是看在哪调的,在哪个线程中调用sleep方法就让哪个线程休眠,和调用者无关
java
public class Main {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (i == 5) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(i + "====");
}
}).start();
}
}
java
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println(i + "Child子线程");
}
});
thread.start();
try {
thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(i + "Main主线程");
}
}
}
4、线程调度
- 线程合并
合并是指将指定的某个线程加入到当前线程中,合并为一个线程。
由两个线程交替执行变成一个线程中的两个子线程顺序执行,一个线程执行完毕之后再来执行第二个线程,通过join方法来实现合并。
线程A和线程B,线程A在执行到某个时间点的时候调用线程B的join方法,则表示从当前时间点开始CPU资源被线程B独占,线程A进入阻塞状态,直到线程B执行完毕,线程A继续获取CPU资源执行自己的任务。
谁调用join,就将谁合并到对方的线程中
java
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println(i + "子线程");
}
});
thread.start();
for (int i = 0; i < 100; i++) {
if (i == 10) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i + "主线程");
}
}
}
join方法存在重载join(long mills)
通过
join进行合并,则合并进来的线程会一直占用CPU资源,直到自己的任务结束才会释放CPU,另外一个线程才能执行
通过join(long mills)进行合并,则合并进来的线程不会一直占用CPU资源,根据 mills 时间来占用,时间到了之后无论线程是否执行完毕,都会将CPU释放出来,回到两个线程争夺CPU资源的情况
java
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(i + "Child子线程");
}
});
thread.start();
for (int i = 0; i < 100; i++) {
if (i == 10) {
try {
thread.join(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i + "Main主线程");
}
}
}
5、线程同步
多线程同时访问同一个资源,可能导致数据不准确的情况
java
public class Account implements Runnable {
private static int num;
@Override
public synchronized void run() {
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "是当前的第" + num + "位访客");
}
}
synchronized 可以修饰实例方法,也可以修饰静态方法,但两者有区别
- 修饰静态方法
java
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
test();
});
thread.start();
}
}
public synchronized static void test() {
System.out.println("===start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end=====");
}
}
如何判断多个线程是否会实现同步,看锁定的资源在内存中有几份,多线程肯定需要排队,如果有多份,则多线程不需要排队,就不会实现同步
- 修饰代码块
java
public class Main {
public static void main(String[] args) {
Main main = new Main();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
main.test();
});
thread.start();
}
}
public void test() {
synchronized (this) { // Main 只new了一份,所以会同步
System.out.println("===start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end=====");
}
}
}
- 锁定常量
包装类常量池有一个可用范围:-128~127,在此区间内常量池可用,同一个数值可以给多个线程使用,一旦超出这个范围,则常量池失效,此时就需要单独在堆中创建对应的数据,不是一个对象而是多个对象
java
public class Main {
public static void main(String[] args) {
Main main = new Main();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
main.test(127);
});
thread.start();
}
}
public void test(Integer value) {
synchronized (value) {
System.out.println("===start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end=====");
}
}
}
6、线程安全的单例模式
java
public class Account {
private static Account account;
private Account() {
System.out.println("创建了Account对象");
}
public synchronized static Account getInstance() {
if (account == null) {
account = new Account();
}
return account;
}
}
java
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
Account account = Account.getInstance();
}).start();
}
}
}
7、死锁
java
public class DeadLockRunnable implements Runnable {
public int user;
private static Object kuaizi1 = new Object();
private static Object kuaizi2 = new Object();
@Override
public void run() {
// 1号拿着筷子1,试图获取筷子2
// 2号拿着筷子2,试图获取筷子1
if (user == 1) {
System.out.println(Thread.currentThread().getName() + "获取到筷子1,等待获取筷子2");
synchronized (kuaizi1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (kuaizi2) {
System.out.println(Thread.currentThread().getName() + "拿到两根筷子,用餐完毕");
}
}
}
if (user == 2) {
System.out.println(Thread.currentThread().getName() + "获取到筷子2,等待获取筷子1");
synchronized (kuaizi2) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (kuaizi1) {
System.out.println(Thread.currentThread().getName() + "拿到两根筷子,用餐完毕");
}
}
}
}
}
java
public class Main {
public static void main(String[] args) {
DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
deadLockRunnable1.user = 1;
DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
deadLockRunnable2.user = 2;
new Thread(deadLockRunnable1, "张三").start();
new Thread(deadLockRunnable2, "李四").start();
}
}
8、重入锁
ReentrantLock是对synchronized的升级,synchronized是通过JVM实现,ReentrantLock是通过JDK实现(用方法调用的形式来使用)
重入锁的特点是可以重复上锁
synchronized是自动上锁,自动解锁,ReentrantLock是手动上锁,手动解锁
java
public class Account implements Runnable {
private static int num;
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
reentrantLock.lock();
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "是当前第" + num + "位访客");
reentrantLock.unlock();
}
}
ReentrantLock还具备限时性的特点,指可以判断某个线程在一定时间内能否获取到锁
tryLock(long timeout, TimeUnit unit)
java
public class Account implements Runnable {
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
try {
if (reentrantLock.tryLock(3, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获取到了锁");
Thread.sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (reentrantLock.isHeldByCurrentThread()) {
reentrantLock.unlock();
}
}
}
}
java
public class Main {
public static void main(String[] args) {
Account account = new Account();
new Thread(account, "张三").start();
new Thread(account, "李四").start();
}
}