Java技术栈 —— 线程基础知识(一)
一、基础知识
1.1 创建线程
/**
* (1)更符合面向接口编程的原则,因为Runnable是一个独立的接口,可以更好地支持代码的扩展和复用。
* (2)不强制你继承Thread类,可以保持代码的简洁和灵活性。
* *(3)可以将Runnable对象传递给其他线程,实现多线程之间的共享和协作。这点是关键点
*/
class MyRunnable implements Runnable{ //更灵活更推荐
// 线程执行的代码逻辑
@Override
public void run() {
System.out.println("MyRunnable...");
}
}
/**
* 线程类已经继承了Runnable接口,所以你不需要再显式地实现Runnable接口。
* 可以直接使用Thread类的方法,如start、sleep、join等。
* 由于继承了Thread类,你的类将与Thread类有直接的继承关系,可能导致代码的耦合性较高。
* Thread本身也是 implements Runnable接口的,也就是说这两种方式的本质都是要implements Runnable接口的
* */
class MyThread extends Thread{ //不关心复用Runnable对象或不关心代码灵活性时使用该方式
// 线程执行的代码逻辑
@Override
public void run() {
System.out.println("MyThread...");
}
}
class Test{ //发现在运行test_lambda_Runnable的时候,进入Thread构造函数时,这个类是implements Runnable接口的
void test_lambda_Runnable(){
Thread thread = new Thread(new Runnable() { //相当于内部新建了一个匿名对象并implements了Runnable接口,但是从debug效果上看是有区别的
@Override
public void run() {
System.out.println("等价实现: Lambda 表达式实现 Runnable...");
}
});
thread.start();
}
}
public class Runnable_Thread_Test {
/**
* 总体而言,实现Runnable接口是更灵活和推荐的方式,因为它更好地符合面向对象编程的原则。
* 但在某些情况下,继承Thread类可能更方便,特别是当你不需要复用Runnable对象或不关心代码的灵活性时。
* 需要注意的是,无论你选择哪种方式,都需要重写run方法来定义线程的执行逻辑。
* */
public static void main(String[] args) {
//创造线程的方法1
// MyRunnable myRunnable = new MyRunnable();
// Thread thread = new Thread(myRunnable);
// thread.start();
//方法1的等价实现(1)
// Thread thread = new Thread(new Runnable() { //相当于内部新建了一个匿名对象并implements了Runnable接口,但是从debug效果上看是有区别的
// @Override
// public void run() {
// System.out.println("等价实现: Lambda 表达式实现 Runnable...");
// }
// });
// thread.start();
//方法1的等价实现(2)
Test test = new Test();
test.test_lambda_Runnable();
//创造线程的方法2
MyThread myThread = new MyThread();
myThread.start(); //与Runnable的区别是可以直接在这个类上调用.start()方法
}
}
1.2 线程方法start()与join()
public class JoinExample {
public static void main(String[] args) {
int count = 1000;
while (count-- > 0){
// 创建并启动线程
Thread thread = new Thread(new MyRunnable());
thread.start();
// 等待线程执行完毕
try {
thread.join(); // 这句话意味着until thread.join() main thread,要等到thread执行完加入到main线程中
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread exiting.");
System.out.println("注意两句话的打印先后顺序,有没有join()是不一样的,没有join完全就是随机的,看thread执行情况,有join后线程执行顺序就会固定了");
System.out.println();
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running.");
}
}
}
二、线程高级
2.1 volatile关键字(涉及硬件特性)
/**
* 多线程内存模型,每个线程会把自己用到的变量,拷贝一份副本放到自己所在CPU的working_cache中
* CPU1(Thread1) -> CPU1_working_cache -> RAM
* CPU2(Thread2)-> CPU2_working_cache -> RAM
* 总线的缓存一致性协议MESI,其它总线通过嗅探机制感知到数据变化,从而使自己CPU内存的变量失效
* 把这个类的汇编代码打印出来
* 并发编程:可见性,有序性,原子性
* */
//JMM内存模型
//讲解地址 https://v.shanhuketang.com/772388bdvodtranscq1317978474/b6e05eb83270835013110899340/v.f1440843.mp4
public class VolatileVisibilityTest {
// private static boolean initFlag = false;
/**
* volatile保证底层可见性,实际上是开启了硬件的一些特殊作用,说的就是lock
* */
private static volatile boolean initFlag = false; //加一个volatile关键字去解决问题,就会打印success
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("waiting data...");
while(!initFlag){
}
System.out.println("=================success");
}
}).start();
Thread.sleep(2000);
//虽然没有明确地实现Runnable接口,但 Java 中的 Lambda 表达式可以自动推断并隐式地实现Runnable接口。
// new Thread(()->prepareData()).start();
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start(); //上面的等价实现
}
public static void prepareData(){
System.out.println("prepare data...");
initFlag = true;
System.out.println("prepare data end...");
}
}