一、理解程序、进程、多任务、线程的概念
程序(program):是对数据描述与操作的代码的集合,是应用程序执行的脚本。
进程(process):是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序即是 一 个进程从创建、运行到消亡的过程。
多任务(multi task):在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。
线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。线程是一个独立的执行流, 是进程内部的一个独立执行单元,相当于一个子程序。
二、创建多线程
方法一:创建 java.lang.Thread 类的子类,重写该类的 run方法 。
//创建一个线程类,打印 0-99之间的整数,实现双线程同时打印.
public class CountNumber {
int i = 0;
public static void main(String args[]) {
CountNumber countNumber = new CountNumber();
Thread thread1 = new CountThread("线程1", countNumber);
Thread thread2 = new CountThread("线程2", countNumber);
thread1.start();//启动线程
thread2.start();
}
}
//thread类
class CountThread extends Thread {
CountNumber countNumber;
public CountThread(String threadName, CountNumber countNumber) {
super(threadName);
this.countNumber = countNumber;
}
@Override//重写run方法
public void run() {
for (countNumber.i=0; countNumber.i < 100; countNumber.i++) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":" + countNumber.i);
}
}
}
方法二:创建 java.lang.Runnable接 口的实现类,实现接口中的 run 方法。
public class MyRunnable implements Runnable {//使用Runnable接口的方式打印
int i = 0;
@Override
public void run() {
for (; i < 20; i++) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.start();
thread2.start();
}
}
Runnable 接口与Thread 类之间的区别
1. Runnable 接口必须实现 run 方法,而 Thread 类中的run 方法是一个空方法,可以不重写
2. Runnable 接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行 run 方法,必须依靠 Thread 类
3. Runnable 接口适合于资源的共享
4.由于java单继承,如果一个类继承了其它类就不能在继承Thread类了,而Runnable接口却没有限制。
线程的生命周期: 指线程从创建到启动,直至运行结束。
线程的运行状态:
• 新建(New):当创建了一个Thread对象时, 该对象就处于“新建状态”,没有启动,就无法运行。
• 可执行(Runnable):新建状态下的线程调用了start()方法,对象被转换为可执行状态,处在等待调度阶段。
• 运行(Running): 处在“可执行状态”的线程对象获得了 CPU 控制权,就会转换到“执行状态”。“执行状态”下的线程可以调用 yield 方法,主动出让 CPU 控制权。线程对象出让控制权后回到“可执行状态”,重新等待调度。
• 阻塞(Blocking): 线程在“执行状态”下由于受某种条件的影响会被迫出让CPU控制权,进入“阻塞状态”。 进入阻塞状态的三种情况
- 调用sleep方法:让当前线程暂时休眠一段时间一毫秒为单位.
- 调用join方法:加入某个线程,此时当前进入阻塞状态,直到被加入的线程执行完之后当前线程才回到运行状态.
- 执行I/O操作: 线程在执行过程中如果因为访问外部资源(等待用户键盘输入、访问网络)时发生了阻塞,也会导致当前线程进入“阻塞状态”。
• 死亡(Dead):处于“执行状态”的线程一旦从run方法返回(无论是正常退出还是抛出异常),就会进入“死亡状态”。
已经“死亡”的线程不能重新运行,否则会抛出IllegalThreadStateException.
线程的优先级:
线程类设置了10个优先级,分别使用1~10内的整数表示 ,整数值越大优先级越高。每个线程都有一个默认的优先级,主线程的默认优先级是5
getPriority:获取当前线程的优先级
setPriority:设置当前线程的优先级
java中线程优先级常量:
MAX_PRIORITY::代表了最高优先级10
MIN_PRIORITY::代表了最低优先级1
NORM_PRIORITY::代表了正常优先级5
注意:setPriority 不一定起作用,在不同的操作系统、不同的JVM 上,效果也可能不同。操作系统也不能保证设置了优先级的线程就一定会先运行或得到更多的CPU时间。在实际使用中,不建议使用该方法。
线程同步问题(线程安全):
我们运行前面的代码可以发现 ,两个线程同时打印0到100的数字可能会出现0 ,0,1,2……等不符合我们要求的结果。
造成这种结果的原因是:多线程应用程序访问共享对象时,由于线程相互抢占cpu控制权,造成一个线程夹在另一个线程的执行过程中执行,所以可能导致错误的执行结果。
解决方法:Synchronized关键字
Synchronized 关键字:为了防止共享对象在并发访问时出现错误,确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念。
//定义同步方法
public synchronized void methd(){
//方法实现
}
注意:当一个线程访问对象的同步方法时,被访问对象就处于“锁定”状态,访问该方法的其他线程只能等待,对象中的其他同步方法也不能访问,但非同步方法则可以访问。
使用”synchronized” 关键字:修饰部分代码,如果只希望同步部分代码行,可以使用“同步块。同步块的作用与同步方法一样,只是控制范围有所区别 。
//同步块
synchronized(obj){
//被同步的代码块
}
wait()方法: 中断方法的执行,使本线程等待,暂时让出 cpu 的使用权,并允许其他线程使用这个同步方法。
notify()方法:唤醒等待的某个线程。
notifyall()方法:唤醒所等待的线程。
一个基于线程安全的练习:
import java.util.Random;
public class BookTickets implements Runnable {
// 共有50张票
private int tickets = 30;
// 控制是否运行的标志
private boolean isRun = true;
public synchronized boolean getTickets() throws InterruptedException {
// 如果票数小于等于0,直接提示没票,并停止运行
if (tickets <= 0) {
System.out.println("对不起,票已售完");
return false;
} else {
// 随机生成要买票的数目,在1-3之间
int buytickets = new Random().nextInt(3) + 1;
// 如果剩余票数大于要购买的票数则正常购买
if (tickets >= buytickets) {
tickets = tickets - buytickets;
Thread.sleep(500);// 睡眠0.5秒
System.out.println(Thread.currentThread().getName() + "购买了"
+ buytickets + "张票,还剩" + tickets + "张");
} else {// 剩余票数不够本次购买提示用户,并让下一个顾客(进程)购买
System.out.println(Thread.currentThread().getName() + "对不起!只剩"
+ tickets + "张票,你需要" + buytickets + "张票,本次购买失败");
wait();// 挂起当前进程
}
notify();//唤醒被挂起的线程
}
return true;
}
public static void main(String[] args) {
Runnable runnable = new BookTickets();
Thread[] td = new Thread[5];
for (int i = 0; i < td.length; i++) {
td[i] = new Thread(runnable, "customer" + (i + 1));
td[i].start();
}
}
@Override
public void run() {
while (isRun) {
try {
isRun = getTickets();
} catch (InterruptedException e) {
e.printStackTrace();
}// 有票返回true,继续执行,无票返回flase停止运行
}
}
}
customer1购买了3张票,还剩27张
customer1购买了2张票,还剩25张
customer5购买了1张票,还剩24张
customer5购买了3张票,还剩21张
customer5购买了1张票,还剩20张
customer4购买了2张票,还剩18张
customer4购买了3张票,还剩15张
customer4购买了2张票,还剩13张
customer4购买了2张票,还剩11张
customer3购买了2张票,还剩9张
customer2购买了1张票,还剩8张
customer3购买了3张票,还剩5张
customer3购买了1张票,还剩4张
customer3购买了2张票,还剩2张
customer3购买了1张票,还剩1张
customer3对不起!只剩1张票,你需要2张票,本次购买失败
customer4对不起!只剩1张票,你需要2张票,本次购买失败
customer5对不起!只剩1张票,你需要3张票,本次购买失败
customer1对不起!只剩1张票,你需要3张票,本次购买失败
customer2购买了1张票,还剩0张
对不起,票已售完