多线程的创建

1.继承Thread类,重写run方法

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start(); // start()会调用操作系统的api创建线程,以run()作为入口方法执行。
        while (true) {
            System.out.println("Hello World");
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() { // 该方法会在合适的时机调用
        while (true) {
            System.out.println("Hello Thread");
        }
    }
}

此时创造了多线程,Hello ThreadHello World会同时循环打印。
每个线程都是独立的线程流,都能执行独立的逻辑。

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.run(); // 将start()改成了run()
        while (true) {
            System.out.println("Hello World");
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("Hello Thread");
        }
    }
}

start()改成run()实际上只有一个main线程。此时由于run()先被调用,因此只会循环打印Hello Thread而不会打印Hello World

2.实现runnable接口,重写run方法

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myRunnable = new MyRunnable(); // Runnable没用start()方法,只能与Thread配合使用
        Thread t = new Thread(myRunnable);
        t.start();
        while (true) {
            System.out.println("Hello Thread");
            Thread.sleep(1000);
        }
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("Hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  1. 第一种写法,把线程和线程要执行的任务耦合在一起了
  2. 第二种写法,Runnable表示要执行的任务,Thread表示线程。将两者解耦合了

3.使用匿名内部类,重写run方法

public class Test {
    public static void main(String[] args) throws InterruptedException {
    
        Thread t = new Thread() { // 此处的t是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);
        }
    }
}
  1. 只需要使用一次
  2. 代码逻辑简单

4.使用匿名内部类,基于Runnable

public class Test {
    public static void main(String[] args) throws InterruptedException {
        
        Thread t = new Thread(new Runnable() {
            @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);
        }
    }
}

5.基于lambda表达式(匿名内部类)

public class Test {
    public static void main(String[] args) throws InterruptedException throws InterruptedException {
    
        Thread t = new Thread(() -> {
            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);
        }
    }
}

Thread的构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target,String name)使用Runnable对象创建线程对象,并命名

Thread的几个常见属性

此处的ID是Jvm分配的,和系统分配的ID是不同内容。Java中无法看到操作系统分配的线程ID。

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台进程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

1.isDaemon()

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {
            while (true) {
                System.out.println("Hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "这是一个线程");
        t.setDaemon(true); // true -> 成为后台线程
                           // 此时只有一个前台线程,main线程结束了,进程就结束了
        t.start();
    }
}

2.isAlive()

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {
           for (int i = 0; i < 3; i++) {
               System.out.println("Hello Thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });

        System.out.println(t.isAlive()); // false

        t.start();

        Thread.sleep(1000);

        System.out.println(t.isAlive()); // true

        Thread.sleep(3000);

        System.out.println(t.isAlive()); // false
    }
}

线程的启动

针对一个Thread对象,只能调用一次start!

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {
            System.out.println("Hello Thread");
        });

        t.start();

        System.out.println("线程第一次启动");

        t.start();

        System.out.println("线程第二次启动");
    }
}

// 线程第一次启动
// 线程第二次启动
// Hello Thread
// Exception in thread "main" java.lang.IllegalThreadStateException
//	at java.base/java.lang.Thread.start(Thread.java:802)
//	at Test.main(Test.java:13)

线程的终止

Java中没有“强制终止”操作。让一个线程结束的核心方式,是让线程的入口方法能够执行完。

1.使用标志位

import java.util.Scanner;

public class Test {

    private static boolean running = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {
           while (running) {
               System.out.println("Hello Thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
            System.out.println("end");
        });
        t.start();
        Scanner scanner = new Scanner(System.in); // 当用户输入后,线程终止
        System.out.println("输入任意内容,让t线程终止");
        scanner.next();
        running = false;
    }
}
  1. 将变量写为成员变量,本质是lambda作为匿名内部类访问外部类。如果将成员变量改为局部变量,要保证变量running不被修改。
  2. 如果设置Thread.sleep(10000);,此时结束操作不能及时处理。

2.使用interrupt方法

import java.util.Scanner;

public class Test {
   public static void main(String[] args) {
       Thread t = new Thread( () -> {
           while (Thread.currentThread().isInterrupted()) {
               System.out.println("Hello Thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
           System.out.println("线程 t 终止");
       });
       t.start();
       Scanner sc = new Scanner(System.in);
       sc.next();
       t.interrupt();
   }
}

// Hello Thread
// Hello Thread
// ...
// 1
// Exception in thread "Thread-0" java.lang.RuntimeException: // java.lang.InterruptedException: sleep interrupted
//	 at Test.lambda$main$0(Test.java:11)
//	 at java.base/java.lang.Thread.run(Thread.java:842)
// Caused by: java.lang.InterruptedException: sleep interrupted
//	 at java.base/java.lang.Thread.sleep(Native Method)
//	 at Test.lambda$main$0(Test.java:9)
//	 ... 1 more
// 
// Process finished with exit code 0
  1. t.interrupt()代表终止线程操作,内置了标志位
  2. Thread.currentThread().isInterrupted()中,isInterrupted()方法可以获取到当前线程是否被终止,Thread.currentThread()指代当前线程的引用
  3. try-catch中的throw new RuntimeException(e);是指当t线程处于阻塞状态时,有interrupt方法终止线程,会导致t线程触发InterruptedException异常
  4. 由于Thread.sleep()占据大部分时间,因此几乎不会输出"线程 t 终止"这个结果
  5. 实际开发中,catch中可以进行以下操作:进行重试、记录错误日志、触发监控报警

线程的等待

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多millis毫秒
public void join(long millis, int nanos)同上,提高精度

如果需要A线程工作完后的结果交回主线程进行结果计算,需要主线程等待A线程的结束。因此需要主动控制线程的结束,即主线程等待A线程的结束。

public class Test {
    private static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {
            int sum = 0;
            for (int i = 0; i < 1000; i++) {
                sum += i;
            }
            result = sum;
        });
        t.start();
        t.join(); // t线程是被等待的对象
        System.out.println(result);
    }
}

例如计算1+2+3+...+1000的结果返回给主线程,由于System.out.println(result);这段代码执行的更快,如果不控制线程结束时间,结果会打印0。因此,调用t.join();方法,当t线程已经结束,则t.join()方法不会执行;如果t线程还在执行,则t.join()会阻塞主线程等待,直到t线程结束。不过,任何一个线程都可以被等待:

public class Test {
    private static int result = 0;

    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();

        Thread t = new Thread( () -> {
            try {
                mainThread.join();
                System.out.println(result);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();

        int sum = 0;
        for (int i = 0; i < 1000; i++) {
            sum += i;
        }
        result = sum;
    }
}

线程的状态

通过t.getState()方法获取

NEW:Thread对象创建了,还未启动

RUNNABLE:就绪状态,正在被cpu执行或随时可以调度到cpu执行

TERMINSTED:线程的入口方法执行完了,系统中的线程已经销毁

WAITING:由join()引起的无固定期限的阻塞

TIMED_WAITING:由于sleep()join()引起的有固定期限阻塞

BLOCKED:由于锁竞争引起的阻塞

线程安全

代码在单线程或多线程上都没有问题,叫做线程安全。

public class Demo1 {
    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread( () -> {
            for (int i = 0; i < 100000; i++) {
                count++;
            }
        });

        Thread t2 = new Thread( () -> {
            for (int i = 0; i < 100000; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

// 154448 (不符合预期,且每次结果都不同)

原因:

  1. 线程调度是随机的,一个线程执行任何一个指令都可能被cpu调度走
  2. 多个线程同时操作一个变量
  3. 针对变量的修改操作不是“原子”的
  4. 内存可见性引起的线程安全问题
  5. 指令重排序引起的线程安全问题

解决方法:通过锁,让一个变量只能被一个线程修改。

synchronized (锁对象) { (-> 加锁)
    // 需要操作的变量
} (-> 解锁)
  1. 锁对象可以是任意对象,可以是Object, String, 甚至是ArrayList
  2. 一般初始化锁对象的方法Object locker = new Object();
  3. 当一个线程对一个锁对象加锁时,另一个线程继续对同一个锁对象加锁时,会发生锁竞争,此时另一个线程会产生阻塞等待,即BLOCKED状态,直到锁对象被解锁。
public class Demo1 {
    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread( () -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (locker) {
                    count++;
                }
            }
        });

        Thread t2 = new Thread( () -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (locker) {
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}