进程和线程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。如下图,eclipse.exe是一个进程、谷歌浏览器是一个进程,QQ也是一个进程,进程是受操作系统管理的基本运行单元。
线程可以理解成是在进程中独立运行的子任务,比如在使用谷歌浏览器时,一边浏览网站,一边用浏览器下载文件同时还一边用浏览器播放音乐。多线程类似多任务操作系统,比方像Windows,用户可以一边打印文档,一边使用word编辑文档,还可以一边打开QQ聊天。CPU在这些任务之间不断切换和运行,而且切换的速度很快,给用户的感觉就像同时运行。使用多线程技术后,可以在同一时间内运行多个不同种类的任务
如下图,单任务环境中,任务1和任务2必须按顺序执行,任务1执行10秒,任务2执行1秒,虽然任务2执行的时间很短,但必须等待任务1,所以当单任务环境中,要等任务2执行完,必须等11秒,而多任务环境中,如果任务1和任务2之间的执行并不需要先后顺序,彼此也没关系,使用多线程技术可以将任务的执行可以来回切换,可以任务1线执行3秒,再任务2执行1秒,此时任务2已执行完,再切换回去执行任务1,不必等到任务1执行完毕后在执行任务2,运行的效率得到提升,使用多线程,也就是使用异步
开发多线程
实现多线程编程的方式主要有两种,一种是继承Thread类,一种是实现Runnable接口
继承Thread类
代码1-1
public class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
System.out.println("程序结束");
}
}
代码1-1运行结果:
程序结束
线程运行
如代码1-1所示,我们创建一个ThreadDemo对象,但并没有调用run()方法,而仅仅是调用start(),控制台打印程序结束和线程运行,调用start()可以使线程准备运行,另外也可以得出线程开始运行后,调用run()方法比较晚
实现Runnable接口
代码1-2
public class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {
RunnableDemo demo = new RunnableDemo();
Thread thread = new Thread(demo);
thread.start();
System.out.println("程序结束");
}
}
代码1-2运行结果:
程序结束
线程运行
实现Runnable接口同样需要以参数传入Thread类的构造方法构造一个Thread对象,然后再调用start()方法启动线程,可以看到代码1-1和代码1-2结果一致,另外如果一个Thread对象多次调用start()方法将会报错
线程调用的随机性
Thread对象调用start()方法后,会通知“线程规划器”此线程已准备就绪,等待调用线程对象的run()方法,而即便线程开始执行调用run()方法的中途,也会切换到其他线程
代码1-3
public class NumThread extends Thread {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "打印:" + count++);
}
}
public static void main(String[] args) {
Thread[] threads = new Thread[3];
for (int i = 0; i < 3; i++) {
threads[i] = new NumThread();
System.out.println(threads[i].getName() + "构造成功");
}
for (int i = 0; i < 3; i++) {
threads[i].start();
}
}
}
代码1-3运行结果:
Thread-0构造成功
Thread-1构造成功
Thread-2构造成功
Thread-0打印:0
Thread-1打印:0
Thread-1打印:1
Thread-0打印:1
Thread-2打印:0
Thread-0打印:2
Thread-1打印:2
Thread-2打印:1
Thread-2打印:2
java.lang.Thread.currentThread()方法是获取当前的线程,而java.lang.Thread.getName()是获取当前线程的名称,另外Thread对象也可以使用java.lang.Thread.setName(String)方法设置线程的名称,如代码1-3运行结果所示,每个线程虽然都从0打印到2,但却是线程间来回切换执行
线程安全
代码1-3中,虽然3个线程执行的顺序不一样,但每个线程最终都是从0打印到2,因为每个线程都单独享有一个count变量,如果count变量不再是实例变量,而是静态变量,在对其进行计算时,可能出现线程安全问题
代码1-4
class Num {
private int num = 5;
public void reduce() {
num--;
System.out.println(Thread.currentThread().getName() + "计算count:" + num);
}
}
class NumReduceThread extends Thread {
private Num num;
public NumReduceThread(Num num) {
super();
this.num = num;
}
@Override
public void run() {
num.reduce();
}
}
public class NumReduce {
public static void main(String[] args) {
Num num = new Num();
for (int i = 0; i < 5; i++) {
new NumReduceThread(num).start();
}
}
}
代码1-4运行结果:
Thread-1计算count:2
Thread-2计算count:2
Thread-4计算count:0
Thread-3计算count:1
Thread-0计算count:2
由代码1-4运行结果可以看到,线程Thread-1、线程Thread-2、线程Thread-0打印出的count的值都是2,说明这3个线程同时对count进行处理,产生非线程安全问题,而我们希望的结果是count依次递减
修改reduce()方法为其添加关键字synchronized如代码1-5
代码1-5
public synchronized void reduce() {
num--;
System.out.println(Thread.currentThread().getName() + "计算count:" + num);
}
修改为代码1-5后运行结果:
Thread-0计算count:4
Thread-3计算count:3
Thread-4计算count:2
Thread-2计算count:1
Thread-1计算count:0
在reduce()方法前加入synchronized关键字,使多个线程在执行reduce()方法时得以排队处理。当一个线程调用reduce()方法时会先判断有没有被上锁,如果上锁,说明有其他线程正在调用reduce()方法,必须等待其他线程执行完reduce()方法后释放锁,才可以获得锁再执行reduce()方法。synchronized可以对任意对象及方法上锁,而加锁的这段代码称为“互斥区”或“临界区”
方法java.lang.Thread.isAlive()的作用是判断线程是否处于活动状态
方法java.lang.Thread.sleep(long)的作用是在指定的毫秒数内让当前正在执行的线程暂停执行
方法java.lang.Thread.getId()的作用是取得线程的唯一标识
代码1-6
class AliveThread extends Thread {
@Override
public void run() {
try {
System.out.println("线程id" + getId() + "开始运行");
System.out.println("线程id" + getId() + "执行状态1:" + isAlive());
Thread.sleep(1000);
System.out.println("线程id" + getId() + "暂停1s后结束");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Alive {
public static void main(String[] args) throws InterruptedException {
AliveThread thread = new AliveThread();
thread.start();
Thread.sleep(100);
System.out.println("线程id" + thread.getId() + "执行状态2:" + thread.isAlive());
Thread.sleep(2000);
System.out.println("线程id" + thread.getId() + "执行状态3:" + thread.isAlive());
}
}
代码1-6运行结果:
线程id8开始运行
线程id8执行状态1:true
线程id8执行状态2:true
线程id8暂停1s后结束
线程id8执行状态3:false
java.lang.Thread.interrupt()为线程打上中断标记,而非中断线程,如果要中断线程需要另外的条件判断
java.lang.Thread.interrupted()测试当前线程是否已中断,执行后具有将状态标识清除为false的功能
java.lang.Thread.isInterrupted()测试线程是否已中断,但不清除标识
public class InterruptedThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "中断状态1:" + Thread.interrupted());
for (int i = 0; i < Integer.MAX_VALUE / 100; i++) {
Math.random();
}
System.out.println(Thread.currentThread().getName() + "中断状态2:" + Thread.interrupted());
System.out.println(Thread.currentThread().getName() + "中断状态3:" + Thread.interrupted());
}
public static void main(String[] args) throws InterruptedException {
InterruptedThread thread = new InterruptedThread();
thread.start();
Thread.sleep(100);
thread.interrupt();
System.out.println("thread执行中断");
}
}
代码1-7运行结果:
thread执行中断
Thread-0中断状态2:true
Thread-0中断状态3:false
代码1-8
public class IsInterruptedThread extends Thread {
@Override
public void run() {
System.out.println(getName() + "中断状态1:" + isInterrupted());
for (int i = 0; i < Integer.MAX_VALUE / 100; i++) {
Math.random();
}
System.out.println(getName() + "中断状态4:" + isInterrupted());
System.out.println(getName() + "执行interrupted(),返回中断状态5:" + Thread.interrupted());
System.out.println(getName() + "中断状态6:" + isInterrupted());
}
public static void main(String[] args) throws InterruptedException {
IsInterruptedThread thread = new IsInterruptedThread();
thread.start();
Thread.sleep(100);
System.out.println(thread.getName() + "执行中断");
thread.interrupt();
System.out.println(thread.getName() + "中断状态2:" + thread.isInterrupted());
System.out.println(thread.getName() + "中断状态3:" + thread.isInterrupted());
}
}
代码1-8运行结果:
Thread-0中断状态1:false
Thread-0执行中断
Thread-0中断状态2:true
Thread-0中断状态3:true
Thread-0中断状态4:true
Thread-0执行interrupted(),返回中断状态5:true
Thread-0中断状态6:false
由代码1-8运行结果的中断状态5和中断状态6可以看到,运行Thread.interrupted(),会将原先的中断标识清除为false
暂停中中断
如果某一线程处于sleep状态下,调用该线程的中断方法,线程停止休眠将抛出中断异常
代码1-9
class SleepInterruptThread extends Thread {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "开始运行");
Thread.sleep(1000 * 10);
System.out.println(Thread.currentThread().getName() + "暂停10s后结束");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "进入异常");
e.printStackTrace();
}
}
}
public class SleepInterrupt {
public static void main(String[] args) throws InterruptedException {
SleepInterruptThread thread = new SleepInterruptThread();
thread.start();
Thread.sleep(1000);
System.out.println(thread.getName() + "调用中断");
thread.interrupt();
}
}
代码1-9运行结果:
Thread-0开始运行
Thread-0调用中断
Thread-0进入异常
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.jerry.ch1.SleepInterruptThread.run(SleepInterrupt.java:8)
循环停止
代码1-10
class CycleInterruptThread extends Thread {
private int count = 0;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始运行");
while (!interrupted()) {
System.out.println(Thread.currentThread().getName() + "打印count:" + count++);
// for循环为耗时操作
for (int i = 0; i < Integer.MAX_VALUE / 100; i++) {
Math.random();
}
}
System.out.println(Thread.currentThread().getName() + "被中断,结束运行");
}
}
public class CycleInterrupt {
public static void main(String[] args) throws InterruptedException {
CycleInterruptThread thread = new CycleInterruptThread();
thread.start();
Thread.sleep(1000 * 10);
System.out.println(Thread.currentThread().getName() + "调用中断方法");
thread.interrupt();
}
}
代码1-10运行结果:
Thread-0开始运行
Thread-0打印count:0
Thread-0打印count:1
Thread-0打印count:2
Thread-0打印count:3
Thread-0打印count:4
Thread-0打印count:5
Thread-0打印count:6
Thread-0打印count:7
Thread-0打印count:8
Thread-0打印count:9
Thread-0打印count:10
main调用中断方法
Thread-0被中断,结束运行
线程优先级
在操作系统中,线程可以划分优先级,优先级较高的线程有较大的希望可以得到CPU资源,也就是CPU优先执行的对象,优先级分为1~10这10个等级,如果是小于1或者大于10,则会抛出异常
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
线程的优先级具有继承性,如果A线程启动B线程,如果不明确指定B线程的优先级,B线程的优先级与A线程是一样的
代码1-11
class ThreadA extends Thread {
private ThreadB threadB;
public ThreadA(String name, ThreadB threadB) {
super(name);
this.threadB = threadB;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "优先级:" + getPriority());
threadB.start();
}
}
class ThreadB extends Thread {
public ThreadB(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "优先级:" + getPriority());
}
}
class ThreadC extends Thread {
private ThreadD threadD;
public ThreadC(String name, ThreadD threadD) {
super(name);
this.threadD = threadD;
}
@Override
public void run() {
threadD.start();
System.out.println(Thread.currentThread().getName() + "优先级:" + getPriority());
}
}
class ThreadD extends Thread {
public ThreadD(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "优先级:" + getPriority());
Thread.currentThread().setPriority(MAX_PRIORITY);
System.out.println("将" + Thread.currentThread().getName() + "优先级设置为" + getPriority());
System.out.println("线程E由线程D启动");
new ThreadE("线程E").start();
}
}
class ThreadE extends Thread {
public ThreadE(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "优先级:" + getPriority());
}
}
public class PriorityExtends {
public static void main(String[] args) throws InterruptedException {
ThreadB threadB = new ThreadB("线程B");
ThreadA threadA = new ThreadA("线程A", threadB);
threadA.start();
Thread.sleep(100);
Thread.currentThread().setPriority(8);
ThreadD threadD = new ThreadD("线程D");
ThreadC threadC = new ThreadC("线程C", threadD);
threadC.start();
}
}
代码1-11运行结果:
线程A优先级:5
线程B优先级:5
线程C优先级:8
线程D优先级:8
将线程D优先级设置为10
线程E由线程D启动
线程E优先级:10
代码1-12
import java.util.Map;
import java.util.TreeMap;
public class PriorityThread extends Thread {
private long count = 0;
@Override
public void run() {
while (!interrupted()) {
count++;
}
}
public long getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
PriorityThread[] threads = new PriorityThread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new PriorityThread();
threads[i].setPriority(i + 1);
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
Thread.sleep(1000);
for (int i = 0; i < 10; i++) {
threads[i].interrupt();
}
TreeMap<Long, PriorityThread> threadMap = new TreeMap<Long, PriorityThread>();
for (int i = 0; i < 10; i++) {
threadMap.put(threads[i].getCount(), threads[i]);
}
for (Map.Entry<Long, PriorityThread> entry : threadMap.entrySet()) {
System.out.println("count值:" + entry.getKey() + " 线程名称:" + entry.getValue().getName() + " 线程优先级:" + entry.getValue().getPriority());
}
}
}
代码1-12运行结果:
count值:2219601 线程名称:Thread-0 线程优先级:1
count值:2418380 线程名称:Thread-1 线程优先级:2
count值:6557231 线程名称:Thread-3 线程优先级:4
count值:10153073 线程名称:Thread-2 线程优先级:3
count值:11196754 线程名称:Thread-4 线程优先级:5
count值:11611442 线程名称:Thread-6 线程优先级:7
count值:11978366 线程名称:Thread-7 线程优先级:8
count值:12100987 线程名称:Thread-5 线程优先级:6
count值:12303622 线程名称:Thread-8 线程优先级:9
count值:12406222 线程名称:Thread-9 线程优先级:10
如代码1-12可以看到,较高的优先级的count值更高,但优先级的执行也具有随机性,例如优先级为3的count值比优先级为4的count的值来的大,优先级为6的count值比优先级为7和8的count的值来的大
守护进程
Java线程中有两种线程,一种是用户线程,一种是守护线程
守护线程是一种特殊的线程,当进程不存在非守护线程了,则守护线程自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要,自动销毁。将方法java.lang.Thread.setDaemon(boolean)设置为true则是将线程设置为守护线程,默认为false
代码1-13
public class DaemonThread extends Thread {
private long count = 0;
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
count++;
}
}
public long getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
DaemonThread[] threads = new DaemonThread[6];
for (int i = 0; i < 6; i++) {
threads[i] = new DaemonThread();
threads[i].setDaemon(true);// 将线程设置为守护线程,默认为false
threads[i].start();
}
Thread.sleep(300);
for (int i = 0; i < 6; i++) {
System.out.println(threads[i].getName() + " count:" + threads[i].getCount());
}
}
}
代码1-13运行结果:
Thread-0 count:167638707
Thread-1 count:168747478
Thread-2 count:145635422
Thread-3 count:148750296
Thread-4 count:150255081
Thread-5 count:145468478
yield方法
java.lang.Thread.yield()方法的作用是使当前线程放弃CPU资源,将它让给其他任务去占用CPU资源,但放弃时间不定,可能刚放弃,马上又获得CPU资源
代码1-14
public class YieldThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "打印i:" + i);
if (i == 5) {
Thread.yield();
}
}
}
public static void main(String[] args) {
new YieldThread().start();
new YieldThread().start();
}
}
代码-14运行结果:
Thread-0打印i:0
Thread-1打印i:0
Thread-0打印i:1
Thread-1打印i:1
Thread-0打印i:2
Thread-1打印i:2
Thread-0打印i:3
Thread-1打印i:3
Thread-0打印i:4
Thread-1打印i:4
Thread-0打印i:5
Thread-1打印i:5
Thread-0打印i:6
Thread-1打印i:6
Thread-1打印i:7
Thread-1打印i:8
Thread-1打印i:9
Thread-0打印i:7
Thread-0打印i:8
Thread-0打印i:9
当i等于5的时候,线程会放弃CPU资源,交由其他线程执行,但也有可能刚放弃又马上获得CPU资源