进程是指可执行程序并存放在计算机存储器的一个指令序列,它是一个动态执行的过程。

任务管理器中可以看出有些软件只对应一个进程,而有些软件是由多个进程组成的。

早期的操作系统都是单任务的操作系统,也就是比如QQ、音乐播放器只能有一个在运行。一个运行结束之后才能进行下一个程序的执行。比如在听歌曲,听完了才可以回复QQ好友的消息。

线程是比进程还要小的运行单位,一个进程包含多个线程,线程可以看做是一个子程序。
比如一个程序由很多代码组成的,这些代码可以分成很多块放到不同的线程中分别执行。
在只有一个CPU的时候如何保证多个线程同时执行的呢?一个CPU可以分成很多时间片,时间片的时间可以非常短。比如有音乐播放器、代码编辑器、QQ三个软件同时运行,音乐播放器运行1ms,然后把CPU的使用权转给代码编辑器,代码编辑器运行1ms再把CPU的使用权转给QQ。那么这些程序就轮流在很短的时间使用CPU,对于CPU来说这些软件是轮流运行的,但是由于运行的时间间隔非常短,作为我们使用者来说感觉不到变化,我们就认为这些软件是同时运行的。
通过对CPU 时间的轮转来达到同时运行这样的效果。
Thread是一个线程类,位于java.lang包下
| 构造方法 | 说明 |
|---|---|
| Thread() | 创建一个线程对象 |
| Thread(String name) | 创建一个具体指定名称的线程对象 |
| Thread(Runnable target) | 创建一个基于Runnable接口实现类的线程对象 |
| Thread(Runnable target,String name) | 创建一个基于Runnable接口实现类,并且具有指定名称的线程对象 |
Thread类的常用方法
| 方法 | 说明 |
|---|---|
| public void run() | 线程相关的代码写在该方法中,一般需要重写 |
| public void start() | 启动线程的方法 |
| public static void sleep(long m) | 线程休眠m毫秒的方法 |
| public void join() | 优先执行调用join()方法的线程 |
public class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println(getName()+":该线程正在执行!");
}
}
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主线程1");
MyThread myThread = new MyThread();
myThread.start();//启动线程
System.out.println("主线程2");
}
}
注意:

public class ThreadTest {
public static void main(String[] args) {
System.out.println("主线程1");
MyThread myThread = new MyThread();
myThread.start();//启动线程
myThread.start();
System.out.println("主线程2");
}
}
一个线程不能多次启动
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主线程1");
MyThread myThread = new MyThread();
myThread.start();//启动线程
myThread.start();
System.out.println("主线程2");
}
}
并没有编译错误,此时运行会抛出异常,不合逻辑的线程状态,线程不能被多次启动

新建MyThread2.java
public class MyThread2 extends Thread {
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
for(int i=1;i<=10;i++){
System.out.println(getName()+"正在运行"+i);
}
}
}
ThreadTest类中创建两个线程,分别启动
public class ThreadTest {
public static void main(String[] args) {
MyThread2 myThread1 = new MyThread2("线程1");
MyThread2 myThread2 = new MyThread2("线程2");
myThread1.start();
myThread2.start();
}
}



由运行结果可以看出,每次运行顺序是随机的
为什么要实现Runnable接口?
新建PrintRunnable.java
public class PrintRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在运行!");
}
}
public class Test {
public static void main(String[] args) {
PrintRunnable pr1 = new PrintRunnable();
Thread t1 = new Thread(pr1);
t1.start();
PrintRunnable pr2 = new PrintRunnable();
Thread t2 = new Thread(pr2);
t2.start();
}
}


public class PrintRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10 ; i++) {
System.out.println(Thread.currentThread().getName()+"正在运行!"+i);
}
}
}

运行结果说明程序的运行具有随机性
修改PrintRunnable中i为成员变量,Test中调用同一个PrintRunnable对象
public class PrintRunnable implements Runnable {
int i = 1;
@Override
public void run() {
while (i <= 10) {
System.out.println(Thread.currentThread().getName()+"正在运行!"+i++);
}
}
}
public class Test {
public static void main(String[] args) {
PrintRunnable pr1 = new PrintRunnable();
Thread t1 = new Thread(pr1);
t1.start();
// PrintRunnable pr2 = new PrintRunnable();
Thread t2 = new Thread(pr1);
t2.start();
}
}


运行结果可以看出程序执行的过程也是随机的。Runnable run()方法中的代码可以被多个线程共享,适用于多个线程处理同一个资源的情况

public static void sleep(long millis)
创建MyThread.java
public class MyThread implements Runnable{
@Override
public void run() {
for (int i=1;i<=15;i++){
System.out.println(Thread.currentThread().getName()+"执行第"+i+"次!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SleepDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t = new Thread(myThread);
t.start();
}
}
运行程序,在不加sleep()之前程序很快就执行完了,在run()方法中加入Thread.sleep()方法后程序每1秒才执行一次
应用场景:
需要说明的是,到底线程什么时候执行不仅受到sleep()的影响,调用sleep()方法休眠1000ms,当时间终止这个线程并不能马上进入正在运行的状态,而是进入可运行状态。可运行状态的线程只有获取到CPU的使用权以后才能变成运行状态去执行线程,所以实际的时间可能比1000ms要多一些。如果使用这种方式去写时钟类的程序会有误差。
public class SleepDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t = new Thread(myThread);
t.start();
Thread t1 = new Thread(myThread);
t1.start();
}
}
运行结果发现,基本上是按照一个线程执行完再执行另外一个线程这样交替执行。执行结果也是具有随机性的,只不过通过sleep()方法去把之前的状态适当改变一下。出现这种交替执行的情况,是因为一个线程执行完一条输出语句就调用sleep()方法休眠了,在它休眠的这段时间里另外一个线程获得CPU使用权的概率就更大一些,所以就会执行Thread-1,Thread-1执行完也会调用sleep()方法休眠,这时候Thread-0的休眠时间就到了,Thread-0就会执行,这样依次类推,就产生了当前的结果。

public final void join()
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println(getName()+"正在执行!"+i+"次!");
}
}
}
public class JoinDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=1;i<=5; i++) {
System.out.println("主线程运行第"+i+"次!");
}
System.out.println("主线程运行结束!");
}
}
运行结果可以,Thread-0也就是调用了join()方法的线程先执行了10也就是先执行完,然后才是主线程的循环以及它最后的输出语句再执行。看出join()方法是抢占资源,调用这个方法的资源先执行,这个线程执行完毕其他资源才能执行。

public final void join(long millis)
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <=2000 ; i++) {
System.out.println(getName()+"正在执行!"+i+"次!");
}
}
}
public class JoinDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
myThread.join(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=1;i<=20; i++) {
System.out.println("主线程运行第"+i+"次!");
}
System.out.println("主线程运行结束!");
}
}
运行结果可以看出当它执行到第811次之后执行主线程,然后再执行Thread-0,调用join()方法的线程执行1ms以后就会把主动权让出来。如果join()方法不指定时间其他线程就会一直等待,调用join()方法的线程执行完毕才会执行其他线程。

Java为线程类提供了10个优先级
优先级可以用整数1-10表示,超过范围会抛出异常
主线程默认优先级为5
优先级常量
-MAX_PRIORITY:线程的最高优先级10
-MIN_PRIORITY:线程的最低优先级1
-NORM_PRIORITY:线程的默认优先级5
| 方法 | 说明 |
|---|---|
| public final int getPriority() | 获取线程优先级的方法 |
| public final void setPriority(int newPriority) | 设置线程优先级的方法 |
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i=1;i<=10;i++) {
System.out.println("线程"+name+"正在运行"+i);
}
}
}
public class PriorityDemo {
public static void main(String[] args) {
//获取主线程的优先级
int mainPriority = Thread.currentThread().getPriority();
System.out.println("主线程的优先级为:"+mainPriority);
MyThread myThread1 = new MyThread("线程1");
// myThread1.setPriority(10);
myThread1.setPriority(Thread.MAX_PRIORITY);
myThread1.start();
System.out.println("线程1的优先级为:"+myThread1.getPriority());
}
}
运行结果

这个运行过程也是具有随机性的,线程优先级的设置跟操作系统的环境以及CPU的调度方式都是有关系的,所以不能完全保证优先级高的线程一定能先执行,所以要根据每一个具体的环境考虑这个问题。
下面定义两个线程分别设置优先级
public class PriorityDemo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
myThread1.setPriority(Thread.MAX_PRIORITY);
myThread2.setPriority(Thread.MIN_PRIORITY);
myThread1.start();
myThread2.start();
}
}
以下运行结果可以看出线程的运行也是具有随机性的

public static void main(String[] args) {
MyThread myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
myThread1.setPriority(Thread.MAX_PRIORITY);
myThread2.setPriority(Thread.MIN_PRIORITY);
myThread2.start();
myThread1.start();
}
由运行结果可以看出,虽然myThread2优先级低但是由于myThread2先启动,也有可能会出现myThread2先运行的情况。

演示代码
Bank.java银行类,里面有存款和取款操作的方法
@Data
@AllArgsConstructor
public class Bank {
private String account; //账号
private int balance; //账户余额
//存款
public void saveAccount(){
//可以在不同的位置处添加sleep方法
//获取当前的账号余额
int balance = getBalance();
//修改余额,存100元
balance += 100;
//修改账户余额
setBalance(balance);
//输出存款后的账户余额
System.out.println("存款后的账户余额为:"+balance);
}
//取款
public void drawAccount(){
//可以在不同的位置处添加sleep方法
//获取当前的账号余额
int balance = getBalance();
//修改余额,存100元
balance = balance - 200;
//修改账户余额
setBalance(balance);
//输出存款后的账户余额
System.out.println("取款后的账户余额为:"+balance);
}
}
SaveAccount.java存款操作与线程相关
//存款
public class SaveAccount implements Runnable{
Bank bank;
public SaveAccount(Bank bank){
this.bank = bank;
}
@Override
public void run(){
bank.saveAccount();
}
}
DrawAccount.java取款操作与线程相关
//取款
public class DrawAccount implements Runnable{
Bank bank;
public DrawAccount(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.drawAccount();
}
}
Test.java测试类
public class Test {
public static void main(String[] args) {
//创建账户,给定余额为1000
Bank bank = new Bank("1001",1000);
//创建线程对象
SaveAccount sa = new SaveAccount(bank);
DrawAccount da = new DrawAccount(bank);
Thread save = new Thread(sa);
Thread draw = new Thread(da);
save.start();
draw.start();
try {
save.join();
draw.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(bank);
}
}
修改代码
try {
save.join();
draw.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

运行结果都是先存款后取款,可以看出线程的执行过程是随机的。在实际运行时银行账户会同时有很多存取款的线程,当有很多线程同时执行存取款操作时我们能保证存取款操作能正常执行吗?
在Bank.java中的存取款方法中添加sleep()方法模拟代码在执行一部分的时候暂停。现实情况下,到底执行到哪条语句线程终止了是不能确定的。
@Data
@AllArgsConstructor
public class Bank {
private String account; //账号
private int balance; //账户余额
//存款
public void saveAccount(){
//可以在不同的位置处添加sleep方法
//获取当前的账号余额
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改余额,存100元
balance += 100;
//修改账户余额
setBalance(balance);
//输出存款后的账户余额
System.out.println("存款后的账户余额为:"+balance);
}
//取款
public void drawAccount(){
//可以在不同的位置处添加sleep方法
//获取当前的账号余额
int balance = getBalance();
//修改余额,存100元
balance = balance - 200;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改账户余额
setBalance(balance);
//输出存款后的账户余额
System.out.println("取款后的账户余额为:"+balance);
}
}

运行结果看出,取款后存款账户余额变为1100了,这个问题如果发生在银行系统中是一个非常非常严重的问题。
造成这种现象的原因,取款的线程draw执行完1000-200修改余额然后线程休眠并没有执行更新Bank的balance。然后运行存款线程save,在取得账户余额的时候还是之前的1000并没有被修改,然后继续执行取款线程draw,更新balance并打印出结果800。此时继续执行存款线程save,balance=1000+100,更新Bank的balance为1100
public synchronized void saveAccount(){}
public static synchronized void saveAccount(){}
synchronized(obj){……}
修改Bank中的saveAccount和drawAccount
//存款
public synchronized void saveAccount(){
//可以在不同的位置处添加sleep方法
//获取当前的账号余额
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改余额,存100元
balance += 100;
//修改账户余额
setBalance(balance);
//输出存款后的账户余额
System.out.println("存款后的账户余额为:"+balance);
}
//取款
public void drawAccount(){
synchronized (this){
//可以在不同的位置处添加sleep方法
//获取当前的账号余额
int balance = getBalance();
//修改余额,存100元
balance = balance - 200;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改账户余额
setBalance(balance);
//输出存款后的账户余额
System.out.println("取款后的账户余额为:"+balance);
}
}
再次运行发现加入同步块synchronized就解决了这个问题,保证每个方法中的代码在执行过程中是不被打断的,保证了执行完整性。


package com.zl.hello.thread.queue;
public class Queue {
private int n;
public synchronized int getN() {
System.out.println("消费:"+n);
return n;
}
public synchronized void setN(int n) {
System.out.println("生产:"+n);
this.n = n;
}
}
package com.zl.hello.thread.queue;
public class Producer implements Runnable {
Queue queue;
public Producer(Queue queue) {
this.queue = queue;
}
@Override
public void run() {
int i=0;
while (true){
queue.setN(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.zl.hello.thread.queue;
public class Consumer implements Runnable {
Queue queue;
public Consumer(Queue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
queue.getN();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.zl.hello.thread.queue;
public class Test {
public static void main(String[] args) {
Queue queue = new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}

由以上运行结果可以看出不是期望的一生产一消费,生产完3没有被消费。我们可以在Queue中添加一个标记flag。当flag为false的时候表示容器中没有数据,这时候就需要我们调用set()方法生产数据,当生产数据完毕以后flag的值就变为true了。这时候消费者程序get()就可以获取数据了。

改写代码
package com.zl.hello.thread.queue;
public class Queue {
private int n;
boolean flag = false;
public synchronized int getN() {
if(!flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费:"+n);
flag = false; //消费完毕,容器中没有数据
notifyAll();
return n;
}
public synchronized void setN(int n) {
if (flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产:"+n);
this.n = n;
flag = true; //生产完毕,容器中已经有数据
notifyAll();
}
}
