背景:只使用单个线程完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需要多线程呢?
多线程程序的有点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率。
3.改善程序结构。将即长又复杂的进程分为多个线程,独立运行,利于理解和修改。
并发和并行:
继承Thread类时,只能创建不同的类,线程类间的资源不可共享,而实现Runnable接口后,可以共享线程资源。
并发是两个任务可以在重叠的时间段内启动,运行和完成。并行是任务在同一时间运行,例如,在多核处理器上。
并发是独立执行过程的组合,而并行是同时执行(可能相关的)计算。
并发是一次处理很多事情,并行是同时做很多事情。
应用程序可以是并发的,但不是并行的,这意味着它可以同时处理多个任务,但是没有两个任务在同一时刻执行。
应用程序可以是并行的,但不是并发的,这意味着它同时处理多核CPU中的任务的多个子任务。
一个应用程序可以即不是并行的,也不是并发的,这意味着它一次一个地处理所有任务。
应用程序可以即是并行的也是并发的,这意味着它同时在多核CPU中同时处理多个任务。
并行才是我们通常认为的那个同时做多件事情,而并发则是在线程这个模型下产生的概念
并发表示同时发生了多件事情,通过时间片切换,哪怕只有单一的核心,也可以实现“同时做多件事情”这个效果。
/**
* 模拟火车站售票窗口,开启三个窗口,总票数为100张
*/
class Window extends Thread {
// 使用static声明的变量是共用的
static int ticket = 10000;
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
} else {
break;
}
}
}
}
public class TestWindow {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
上面的 代码如果ticket变量不是静态的(static修饰的),就会出现问题。使用static声明的变量是共用的。
现在用实现Runnable接口的类来实现该功能。
// 使用实现Runnable接口的方式,售票
class Window1 implements Runnable {
// 实现接口不用定义为静态的
int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
} else {
break;
}
}
}
}
public class TestWindow1 {
public static void main(String[] args) {
// 我就new了一个对象,只不过把这个对象放到了三个构造器中创建了三个线程
Window1 window1 = new Window1();
Thread thread1 = new Thread(window1);
Thread thread2 = new Thread(window1);
Thread thread3 = new Thread(window1);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
对比两种创建线程的方式,那个比较好? 只要是多线程,一定和 Runnable 接口相关的。
分析可得实现的方式优于继承的方式。
原因:
1.实现的方式避免了java单继承的局限性。
2.如果多个线程要操作一份资源数据,更适合使用实现的方式。
多线程的创建步骤:
1.创建一个实现Runnable接口的类
2.实现接口的抽象方法
3.创建一个Runnable接口实现类的对象
4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程
5.启动,调用start()方法,并执行run()
守护线程:
Java中的线程分为两类:一种是守护线程,一种是用户线程。
如果设置一个线程是守护线程,则在JVM进程中所有非守护线程结束生命周期后,所有守护线程才会结束生命周期。所以守护线程一般用于后台执行的任务,比如当我关闭游戏客户端的时候,我希望所有数据传输线程都结束,那么我就可以把数据传输相关的线程设置为守护线程。
Java用setDeamon(true)将线程设置为守护线程只能发生在start之前。
Java垃圾回收就是一个典型的守护线程。若JVM中都是守护线程,当前JVM将会自动退出。
public class DeamonThread {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("working...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);
thread.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("main线程结束生命周期。");
}
}
yield()方法
Thread的yield是一个静态方法,API中说当一个线程调用yield方法时,该线程将倾向于让出CPU和其他资源,自己从running状态转换为runnable状态,但是到底让不让出资源,还是CPU调度说了算。
一下代码如果注释掉Thread里面的条件判断,则有时打印0,1,有时打印1,0。若把注释去掉,更倾向打印0,1(感觉没什么用)
public class Yield {
public static void main(String[] args) {
IntStream.range(0, 2).mapToObj(Yield::create).forEach(Thread::start);
}
private static Thread create(int index) {
return new Thread(() -> {
//if (index == 0)
// Thread.yield();
System.out.println(index);
});
}
}
priority
对于Java多线程而言,设置优先级对线程的执行并没有可见的影响。
setPriority对CPU也是一种hint,如果CPU忽略了它,那这个优先级就没用了。
public class Priority {
private static int index = 1;
private static int max = 100;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
printT("t1");
});
Thread t2 = new Thread(() -> {
printT("t2");
});
t1.setPriority(1);
t2.setPriority(10);
t2.start();
t1.start();
}
private static void printT(String T) {
while (index <= max) {
synchronized (Thread.class) {
if (index > max) break;
System.out.println(index + "\t" + T);
index++;
}
}
}
}
线程的优先级不大于它所在线程租的优先级,如果设置优先级大于线程租优先级,将会设置成线程组优先级。
interrupt()方法
当线程正在执行可被中断的方法时,会尝试捕获InterruptedException,在执行该方法时,一旦被其他线程执行该线程的interrupt方法,就会抛出异常。
如果该线程没有执行可被中断的线程或者线程已经死亡,则线程会忽略该中断。
public class Interrupt {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
System.out.println("Fuck! I am be interrupt!");
}
});
t1.start();
TimeUnit.MICROSECONDS.sleep(4);
t1.interrupt();
}
}
isInterrupted()方法:
判断线程是否被打断过,如果之前执行了interrupt方法,则返回true。该方法不会对interrupt状态做修改。
interrupted()方法:
判断线程是否被打断过,返回true或false,同时重置interrupt。
join()
在一个线程中执行另一个线程的join()方法,此时,本线程将会进入BLOCKED状态,直到另一个线程执行结束后,才会继续执行本线程。
public class Join {
public static void main(String[] args) throws InterruptedException {
List<Thread> threads = IntStream.range(0, 2).mapToObj(Join::create).collect(Collectors.toList());
threads.forEach(Thread::start);
for (Thread thread : threads) {
thread.join();
}
for (int k = 0; k < 20; k++) {
System.out.println("线程 main : " + k);
}
}
public static Thread create(int i) {
return new Thread(() -> {
for (int k = 0; k < 20; k++) {
System.out.println("线程" + i + " : " + k);
}
});
}
}
当线程0和1完全打印结束后,才会打印main线程。
join是个很有用的方法,比如Task任务的拓扑处理,先执某些线程之后才能执行此线程。
线程的生命周期:
JDK中用Thread.State枚举表示了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历一下五种状态:
1.新建:当一个Thread类或其子类的对象被声明并创建,新生的线程对象处于新建状态。
2.就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
3.运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
4.阻塞:在某周特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
5.死亡:线程完成了它的全部工作或线程被提前强制性的中止