1 Java线程简介
进程是程序的基本执行实体,进程可以容纳若干线程,是线程的容器。线程就是轻量级进程,线程的运行成本远远小于进程,所以我们用多线程去设计并发程序,而不是多进程。
线程的生命周期
jdk1.5以后,在java.lang.Thread的内部枚举类State中定义了线程的几种状态:
public enum State {
/**
* 新建一个线程,但还没有启动。
*/
NEW,
/**
* 线程调用了start方法开始执行
*/
RUNNABLE,
/**
*线程阻塞于锁(同步代码块或同步方法)
*/
BLOCKED,
/**
* 线程处于等待状态,调用了如下方法:
* Object#wait(),Thread#join(),LockSupport.park()
*/
WAITING,
/**
* 线程处于一个明确时间的等待状态,调用了如下方法:
* Thread.sleep,Object#wait(long), Thread#join(long)
* LockSupport#parkNanos, LockSupport#parkUntil
*/
TIMED_WAITING,
/**
* 中止状态:表示线程执行完毕
*/
TERMINATED;
}
2 线程基本操作
2.1 新建线程
方法1:继承自Thread类,并重载其run方法,写成内部类就是:
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Hello");
}
};
方法2:实现Runnable接口,使用Thread构造方法传入
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
});
在jdk8中推荐使用lambda表达式构建Runnable:
//单行
Thread thread1 = new Thread(() -> System.out.println("Hello"));
//多行
Thread thread2 = new Thread(() -> {
System.out.println("Hello");
System.out.println("World");
});
//也可以单独构建runnable
Runnable task = () -> System.out.println("Hello");
启动线程,需要调用Thread#start()
方法(注意不是run
方法):
Runnable task = () -> System.out.println("Hello");
new Thread(task).start();
2.2 终止线程Thread#stop()
使用Thread#stop()方法可以立即终止一个线程,不过这个方法会把执行到一半的线程强行终止,jdk中已经不再推荐使用。
Thread thread = new Thread(() -> {
while (true) {
System.out.print("Hello ");
try {
Thread.sleep(3000);//模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("World");
}
});
thread.start();
Thread.sleep(1000);
thread.stop();
新建一个线程,要求线程每次完整的打印Hello World,当用了stop方法,你会发现只打印了Hello,造成了数据不一致。解决方法就是增加一个终止标志位,来让线程决定什么时候自己停止。
public class StopThread {
volatile static boolean isStop = false;//标志位
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.print("Hello ");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("World");
if (isStop) {
break;
}
}
System.out.println("已终止");
});
thread.start();
Thread.sleep(1000);
isStop = true;//终止
}
}
2.3 线程中断 (interrupt)
上节我们使用了终止标志位来正确地终止线程,而在JDK中对线程提供了更好的中断支持,Thread类提供了三个方法,来支持中断操作:
public void interrupt() //把线程设置为中断状态(注意:并不会真正中断线程,仅改变状态)
public boolean isInterrupted() //获取线程是否为中断状态
public static boolean isInterrupted() 获取线程是否为中断状态,并清除中断状态
使用方法如下:
public class InterruptedTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()+"线程被中断");
break;
}
System.out.println("hello world");
}
},"t1");
thread.start();
Thread.sleep(2000);
System.out.println(thread.getName() + "当前中断状态:" + thread.isInterrupted());
thread.interrupt();
}
}
中断对Thread.sleep()的影响
Thread.sleep会让当前线程休眠,当线程休眠中调用中断方法时,会抛出一个InterruptedException方法。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("线程被中断:" + e.getMessage());
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
2.4 线程的等待(wait)与通知(notify)
Object类提供了两个方法 wait、notify来实现线程的等待与通知
public class WaitNotify {
static final Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable task1 = () -> {
System.out.println("task1 start");
synchronized (object) {
System.out.println("task1 enter lock");
try {
Thread.sleep(1000);
System.out.println("task1 wait");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task1 is notified by task2");
}
};
Runnable task2 = () -> {
System.out.println("task2 start");
synchronized (object) {
System.out.println("task2 enter lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task2 start");
object.notify();
}
};
new Thread(task1).start();
new Thread(task2).start();
}
}
2.5 线程的挂起
Thread的suspend()方法会导致线程挂起,但不会释放锁
Thread的resume()方法会继续执行该线程。
由于挂起时并不会释放锁资源,这两个方法已经过时 。
public class SuspendTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("hello:");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(1000);
thread.suspend();//此时thread线程被挂起
System.out.println("suspend...3s");//挂起thread 3秒
Thread.sleep(3000);
thread.resume();//thread线程恢复
}
}
2.6 线程等待join
很多时候,线程之间需要互相协作来进行工作。比如下面的例子:
使用thread线程进行计算1到100的相加结果,main输出结果
public class JoinTest {
volatile static int i = 0;
@Test
public void test() {
Thread thread = new Thread(() -> {
for (int j = 1; j <= 100; j++) {
i = i + j;
}
});
thread.start();
System.out.println(i);//使用thread线程进行计算1到100万的相加结果,main输出结果
}
}
结果却输出0,原因是main线程没有等到thread线程执行完。
使用join方法改写上面的例子:
public class JoinTest {
volatile static int i = 0;
@Test
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
for (int j = 1; j <= 100; j++) {
i = i + j;
}
});
thread.start();
thread.join();//这里main线程会停下来等待thread线程执行完
System.out.println(i);//输出5050
}
}
2.7 线程谦让yield
public static native void yield();
执行这个方法后会让出CPU资源,当前线程会和其他线程一起进行CPU资源的争夺。
3 volatile
volatile关键字保证了不同线程对其他线程修改数据的后可见性。
4 线程组
线程组可以把一些线程放到同一组中,对这组线程进行统一管理:
public class ThreadGroupTest {
@Test
public void test1() {
ThreadGroup group = new ThreadGroup("group 1");
Thread t1 = new Thread(group, () -> System.out.println("i am t1"));
Thread t2 = new Thread(group, () -> System.out.println("i am t2"));
t1.start();
t2.start();
//对线程组各种操作
group.stop();
group.interrupt();
//...等等
}
}
5 守护线程
守护线程是一种特殊的线程,当非守护线程全都退出时,守护线程也会退出。
public class DaemonTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("i am Daemon thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);//如果注释掉这行,thread将一直会循环打印下去
thread.start();
Thread.sleep(3000);
}
}
6 线程优先级
线程优先级使用 Thread#setPriority(int)
指定。
Thread thread = new Thread();
thread.setPriority(Thread.MAX_PRIORITY);//Thread.MAX_PRIORITY = 10
thread.setPriority(Thread.NORM_PRIORITY);//=5
thread.setPriority(Thread.MIN_PRIORITY);//=1
值得注意的是:设置优先级并不能完全保证优先级高的线程竞争资源更有优势。
7.synchronized
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;