1. 线程
1.1 进程和线程
进程:是系统进行资源分配和调度的基本单位,进程是程序的基本执行实体;进程是程序的容器
线程:线程是轻量级的进程,是程序执行的最小单位。并发程序设计使用多线程是因为线程间的切换和调度的成本远低于进程。
1.2 线程的所有状态
NEW:刚创建的线程,还没开始执行,调用start()方法才表示线程开始执行;
RUNNABLE:线程执行时,处于该状态,表示线程所需的一切资源都已经准备好;
BLOCKED:线程在执行过程中遇到synchronized同步块,则进入阻塞状态,线程就会暂停执行,直到获得请求的锁;
WAITING:进入一个无时间限制的等待
TIMED_WAITING:有时间限制的等待
TERMINATED:程序执行完毕后,进入该状态,程序结束。
1.3 线程的基本操作
1.3.1 新建线程
//线程Thread有个run()方法,start()方法创建一个线程,并让这个线程执行run()方法
Thread t1 = new Thread();
t1.start();
1.3.2 运行线程
默认情况下,Thread的run()方法什么也没做,可以进行重载run()方法,执行用户自定义任务
Thread t1 = new Thread(){
@override
public void run(){
System.out.println("Hello World");
}
};
t1.start();
因为Java单继承的特性,继承本身是一个宝贵的资源。所以我们可以通过实现Runnable接口来实现同样的操作。
//Runnable接口是一个单方法接口,只有一个run()方法
public interface Runnable{
public abstract void run();
}
//实现Runnable接口,传入Thread
public class CreateThread3 implements Runnable{
public static void main(String [] args){
Thread t1 = new Thread(new CreateThread3);
t1.start();
}
@override
public void run(){
System.out.println("Oh, I am Runnable");
}
}
1.3.3 终止线程:
一般线程执行完毕后就会节输,但是一些常驻系统的后台线程可能需要手动关闭;
stop()方法已经被废弃的方法:因为stop方法过于暴力,强行执行到一半的线程也会被终止,可能引起数据不一致。
**正确的停止线程的方法:**自行定义一个标志(flag),通过true或false判断是否终止线程
public static class ChangeObjectThread extends Thread{
//设立标志位
volatile boolean flag = false;
public void stopMe(){
flag = true;
}
@Override
public void run() {
while (true){
//通过标志位判断是否跳出循环
if (flag){
System.out.println("exit");
break;
}
synchronized (u){
int v = (int)(System.currentTimeMillis()/1000);
u.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
}
Thread.yield();
}
}
}
1.3.4 线程中断
线程中断是一种重要的线程协作机制。
//中断线程
public void interrupt();
//判断是否被中断
public void isInterrupted();
//判断是否被中断,并清除当前中断状态
public static boolean interrupted();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
@Override
public void run() {
while (true){
if (Thread.currentThread().isInterrupted()){
System.out.println("Interrupted!");
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interruted when sleep");
//设置中断状态
Thread.currentThread().interrupt();
}
Thread.yield();
}
}
};
t1.start();
//Thread.sleep()方法由于中断而抛出异常,此时会清除中断标记,不加处理的话,下一次循环无法捕捉这个中断。
Thread.sleep(2000);
t1.interrupt();
}
1.3.5 等待(wait)和通知(notify)
wait和notify是为了支持多线程之间的协作,这两个方法定义在Object类中。
public final void wait() throws InterruptedException;
public final native void notify();
如果一个线程调用了wait方法,那么它就会进入object对象的等待队列。等待队列中,会有多个线程,因为系统运行多个线程同时等待同一个对象。当notify()方法被调用时,会从等待队列中随机选择一个线程将其唤醒。这里的选择是不公平的,不是先等待的线程会优先被选择。如图2.5.
object.wait()方法必须包含在对应的synchronized语句中,无论是wait()还是notify()都需要先获得目标对象的一个监视器,如图2.6.
简单的wait()和notify()案例:
public class SimpleWN {
final static Object object = new Object();
public static class T1 extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(System.currentTimeMillis() + ":T1 start!");
try {
System.out.println(System.currentTimeMillis() +":T1 wait for object");
//释放object的锁
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+":T1 end!");
}
}
}
public static class T2 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":T2 start ! notify one thread");
//唤醒T1线程,自己进入wait()
object.notify();
System.out.println(System.currentTimeMillis() + ":T2 end!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
}
}
//================OUT=================
// 1571284398647:T1 start!
// 1571284398647:T1 wait for object
// 1571284398647:T2 start ! notify one thread
// 1571284398647:T2 end!
// 1571284400648:T1 end!
Object.wait()和Thread.sleep()都会让线程等待若干时间。wait()会释放目标对象的锁,sleep()方法不会释放任何资源。
为什么wait/notify/notifyAll定义在Object中而不是Thread中?
一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。 。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。
1.3.6 挂起(suspend)和继续执行(resume)线程
挂起和继续执行是一对相反的操作,被挂起的线程,必须等到resume()操作后,才能继续指定。
这两个方法已标注为废弃并且不推荐使用:
suspend()不推荐使用:因为suspend()在导致线程暂停的同时,并不会释放人和锁资源,其他线程访问这个锁的时候,都会无法继续正常运行。
可以通过wait和notify,在应用层面实现suspend和resume功能
1.3.7 等待线程结束(join)和谦让(yield)
join():一个线程等待依赖线程执行完毕,才能继续执行。
//线程无限等待,一直阻塞当前线程,直到目标线程执行完毕
public final void join() throws InterruptedException;
//给出了最大等待时间,超出给定时间目标线程还在执行,则当前线程仍然继续往下执行
public final synchronized void join (long millis) throws InterruptedException
实例:
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread{
@Override
public void run() {
for (i = 0; i < 10000; i++);
}
}
public static void main(String[] args) throws InterruptedException {
AddThread at = new AddThread();
at.start();
//有了join函数,输出是10000,主线程愿意等待AddThread执行完毕
//如果没有join函数,输出为0;
at.join();
System.out.println(i);
}
}
Thread.yield():一旦执行,它会使当前线程让出CPU,让出CPU后,也会进行资源争夺。
public static native void yield();
1.4 volatile与Java内存模型(JMM)
JMM是围绕原子性、有序性和可见性展开的。volatile就是确保这些特性的关键字之一。
volatile声明变量:这个变量极有可能被某些程序修改,虚拟机会采用一些手段保证应用程序范围内所有线程都能够看到这个改动(可见性)。
volatile虽然有助于保证操作的原子性,但是不能代替锁。
1.5 线程组:分门别类的管理
一个系统中,如果线程数量很多,而且功能分配比较明确,就可以将相同功能的线程放置在一个线程组中。
public class NotConsistencyTest implements Runnable{
public static void main(String[] args) {
//建立一个"PrintGroup"的线程组
ThreadGroup tg = new ThreadGroup("PrintGroup");
//将T1和T2两个线程加入这个组
Thread t1 = new Thread(tg, new NotConsistencyTest(), "T1" );
Thread t2 = new Thread(tg, new NotConsistencyTest(), "T2" );
t1.start();
t2.start();
//activeCount()可以获得活动线程的综述
System.out.println(tg.activeCount());
//打印线程组中所有的线程信息
tg.list();
}
public void run() {
String groupAndName = Thread.currentThread().getThreadGroup().getName()
+ "-" + Thread.currentThread().getName();
while (true){
System.out.println("I am" +groupAndName);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.6 守护线程(Daemon)
守护线程作为系统的守护者,在后台默默完成系统性的服务,如:垃圾回收线程,JIT线程。
在一个Java应用内,只有守护线程时,Java虚拟机会自然退出。
public class DaemonDemo {
public static class DaemonT extends Thread{
@Override
public void run() {
while(true){
System.out.println("i am alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new DaemonT();
//将线程t设置为守护进程
t.setDaemon(true);
t.start();
//下面这个线程是主线程,在主线程休眠两秒退出后,守护进程也自然退出
Thread.sleep(2000);
}
}
设置守护进程必须在线程start()之前设置,否则会有如下异常:
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1359)
at DaemonDemo.main(DaemonDemo.java:20)
1.7 线程优先级:先做重要的事
Java中,使用1-10表示线程优先级,一般使用内置的三个静态标量表示:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
高优先级的线程倾向于更快的完成;
可能会使得低优先级的线程一直抢占不到资源,从而始终无法运行,而产生饥饿。
public static void main(String[] args) {
Thread high = new HightPriority();
Thread low = new LowPriority();
high.setPriority(10);
low.setPriority(1);
low.start();
high.start();
}