《Java高并发编程详解》前三章之认识线程、深入理解线程、线程API
继承 Thread类和实现Runnable接口的区别
- 继承Thread类时,只能创建不同的类,线程类间的资源不可共享,而实现Runnable后,可以共享线程资源。
一个号码机有四个出票口,总共只能出10张票,现在用java线程实现。
public class NumberMachine {
public static void main(String[] args) {
TicketWindow t1 = new TicketWindow("1号", "1号启动");
TicketWindow t2 = new TicketWindow("2号", "2号启动");
TicketWindow t3 = new TicketWindow("3号", "3号启动");
TicketWindow t4 = new TicketWindow("4号", "3号启动");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketWindow extends Thread {
private static final int MAX = 10;
//注意这里的int类型不是static的
private int index = 1;
private String name;
public TicketWindow(String name, String msg) {
this.name = name;
System.out.println(msg);
}
@Override
public void run() {
while (index <= MAX) {
System.out.println(name + ": 您叫到的号码是 " + (index++));
try {
TimeUnit.MICROSECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面代码中的index变量不是static的,因此运行达不到想要的结果。每个出口都能输出1到10是个数字。
1号启动
2号启动
3号启动
3号启动
1号: 您叫到的号码是 1
2号: 您叫到的号码是 1
3号: 您叫到的号码是 1
4号: 您叫到的号码是 1
1号: 您叫到的号码是 2
4号: 您叫到的号码是 2
3号: 您叫到的号码是 2
2号: 您叫到的号码是 2
3号: 您叫到的号码是 3
1号: 您叫到的号码是 3
2号: 您叫到的号码是 3
4号: 您叫到的号码是 3
3号: 您叫到的号码是 4
4号: 您叫到的号码是 4
1号: 您叫到的号码是 4
2号: 您叫到的号码是 4
3号: 您叫到的号码是 5
4号: 您叫到的号码是 5
1号: 您叫到的号码是 5
2号: 您叫到的号码是 5
3号: 您叫到的号码是 6
4号: 您叫到的号码是 6
1号: 您叫到的号码是 6
2号: 您叫到的号码是 6
3号: 您叫到的号码是 7
2号: 您叫到的号码是 7
1号: 您叫到的号码是 7
4号: 您叫到的号码是 7
3号: 您叫到的号码是 8
4号: 您叫到的号码是 8
1号: 您叫到的号码是 8
2号: 您叫到的号码是 8
4号: 您叫到的号码是 9
1号: 您叫到的号码是 9
3号: 您叫到的号码是 9
2号: 您叫到的号码是 9
4号: 您叫到的号码是 10
2号: 您叫到的号码是 10
3号: 您叫到的号码是 10
1号: 您叫到的号码是 10
现在用实现Runnable接口的类来实现
package topic1;
import java.util.concurrent.TimeUnit;
/**
* @Author: Knox
* @Date: 2018/11/22 11:17 AM
* @Description: You Know
* @Version 1.0
*/
public class NumberMachine {
public static void main(String[] args) {
// TicketWindow t1 = new TicketWindow("1号", "1号启动");
// TicketWindow t2 = new TicketWindow("2号", "2号启动");
// TicketWindow t3 = new TicketWindow("3号", "3号启动");
// TicketWindow t4 = new TicketWindow("4号", "3号启动");
// t1.start();
// t2.start();
// t3.start();
// t4.start();
TicketWindow rw = new TicketWindow("票口", "票口");
new Thread(rw, "1号").start();
new Thread(rw, "2号").start();
new Thread(rw, "3号").start();
new Thread(rw, "4号").start();
}
}
class TicketWindow implements Runnable {
private static final int MAX = 50000;
//注意这里的int类型不是static的
private int index = 1;
private String name;
public TicketWindow(String name, String msg) {
this.name = name;
System.out.println(msg);
}
@Override
public void run() {
while (true) {
synchronized (this) {
if (index > MAX)
break;
System.out.println(Thread.currentThread().getName() + ": 您叫到的号码是 " + (index++));
}
}
}
}
注意上面的的代码我加了同步(synchronized),如果不加同步的话,会发省生并发异常,导致结果错误。
守护线程
- 如果设置一个线程是守护线程,则在Jvm进程中所有非守护线程结束生命周期后,所有守护线程都结束生命周期, 所以守护线程一般用于后台执行的任务,比如当我关闭游戏客户端的时候,我希望所有数据传输线程都结束,那么我就可以把数据传输相关的线程设置为守护线程。
- java用setDeamon(true)将线程设置为守护线程只能发生在start之前
java代码
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是一个静态方法,文档上是说当一个线程调用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
我发现设置优先级对线程的执行并没有可见的影响,比如下面的代码,我设置t1的优先级小于t2,但是在运行的时候并没有很大的影响,甚至,t1的打印次数有时会t2多。
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任务的拓扑处理,先执行某些线程之后才能执行此线程。现实生活中有很多应用场景。