进程
指内存中运行的一个程序,每个进程都有一个独立的内存空间;
一个应用程序可以同时运行多个进程;
进程是程序的一次执行过程,是系统运行程序的一个基本单位;
系统运行一个程序即一个进程从创建、运行到消亡的过程;
线程
线程是进程的一个执行单元,负责当前进程中程序的执行;
一个进程至少有一个线程;
一个进程可以有多个线程;
线程的调度:
分时调度:所有线程轮流使用CPU,平均分配每个线程占用CPU的时间;
抢占式调度:优先级高的线程优先使用CPU;
public class Demo1 {
public static void main(String[] args) {
MyThread1 myThread = new MyThread1();
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main thread: " + i);
}
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("new thread: " + i);
}
}
}
public class Demo2 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
Thread thread = new Thread(myThread2);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main thread: " + i);
}
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("new thread: " + i);
}
}
}
实现Runnable接口比继承Thread类的优势:
适合多个相同程序代码的线程去共享一个资源(如抢票功能的票数):
Runnable比Thread共享一个资源要方便一些(如下段代码所示);
可以避免java中的单继承的局限性:
java是单继承多实现的,继承了一个类后,就不能再继承其他类,有较大的局限性;
实现解耦操作,代码可以被多个线程共享,代码和线程独立:
一段代码可以被多个线程同时使用,每新启动一个线程,都会在JVM里新开一个栈空间,但是每个栈的执行体,所执行的都是同一段代码;
暂时抛开线程安全问题执行下面两段代码后,会发现继承Thread的线程所操作的ticket票数属性,是每个售票口10张票,开的两个线程之间资源没有共享(如果给ticket加上static,也是可以共享的),而实现Runnable的线程,则是所有售票口共同销售10张票;
Thread:
public class Demo3 {
public static void main(String[] args) {
MyThread3 thread1 = new MyThread3("售票口1");
thread1.start();
MyThread3 thread2 = new MyThread3("售票口2");
thread2.start();
}
}
class MyThread3 extends Thread {
private Integer ticket = 10;
public MyThread3(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + ticket);
ticket--;
}
}
}
Runnable:
public class Demo4 {
public static void main(String[] args) {
MyThread4 runnable = new MyThread4();
Thread thread1 = new Thread(runnable, "售票口1");
thread1.start();
Thread thread2 = new Thread(runnable, "售票口2");
thread2.start();
}
}
class MyThread4 implements Runnable {
private Integer ticket = 10;
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + ticket);
ticket--;
}
}
}
方法名 | 说明 |
---|---|
void setPriority(int) | 线程优先级为1-10,默认5,值越大,获取CPU机会越高 |
static void sleep(long millis) | 当前线程主动休眠millis毫秒 |
static void yield() | 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片 |
final void join() | 允许其他线程加入到当前线程中 |
void setDaemon(boolean) | 设置为守护线程有两类:false用户线程(前台线程),true守护线程(后台线程),默认为用户线程 |
调整java线程的优先级后,优先级高的线程会获得较多的运行机会,只能反应线程的紧急程度,不表示优先级高的就一定先执行;优先级为1到10,默认为5,值越大,获取CPU机会越高;
通过setPriority(int)方法来设置线程的优先级别,代码如下:
public class Demo5 {
public static void main(String[] args) {
MyThread4 runnable = new MyThread4();
Thread thread1 = new Thread(runnable, "thread1");
thread1.setPriority(1);
Thread thread2 = new Thread(runnable, "thread2");
thread2.setPriority(10);
thread1.start();
thread2.start();
}
}
使用线程的sleep(long)可以使线程休眠指定的毫秒数,然后再继续执行执行线程;
代码示例:
当线程里for循环的i循环到5时休眠5秒。
public class SleepThread {
public static void main(String[] args) {
MyThread6 thread6 = new MyThread6();
thread6.start();
}
}
class MyThread6 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i);
}
}
}
Thread.yield()方法的作用是暂停当前正在执行的线程对象,放弃当前拥有的CPU资源,并执行其他线程,暂停的线程回到可运行状态,参与下一次的CPU资源竞争;
实际中无法保证yield()达到让步的目的,因为让步的线程可能在下一次竞争中再次被线程调度程序选中;
sleep()和yield()的区别:
sleep()是使当前的线程进入停滞状态,所以在指定时间内线程是肯定不会再继续执行的;
yield()是使当前线程回到可执行状态,进行下一次的资源竞争,是有可能在进入可执行状态后又获得了CPU的占有权,从而又被执行;
线程调用join()方法后,主线程的执行会被打断,知道加入的线程被执行完,主线程才会继续执行;
什么时候用join()方法
主线程启动了一个子线程,如果子线程的执行时间比较长,且主线程需要子线程的处理结果,也就是主线程需要等子线程执行完毕之后,主线程才能结束,这个时候就可以使用join()方法。
public class JoinThread {
public static void main(String[] args) throws InterruptedException {
System.out.println("----------main thread start----------");
Thread thread = new Thread(new MyThread7());
thread.start();
thread.join();
System.out.println("----------main thread end----------");
}
}
class MyThread7 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("child thread: " + i);
}
}
}
设置为守护线程之后,如果主线程执行完毕,则不管守护线程有没有执行完,都会立马结束;
示例代码如下:
MyThread8守护线程里的1000次循环还没执行完,就会随着主线程10次循环的执行完毕而结束;
public class DaemonThread {
public static void main(String[] args) {
MyThread8 thread = new MyThread8();
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main thread: " + i);
}
}
}
class MyThread8 extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("daemon thread: " + i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当线程被创建,并启动之后,它并不是立刻就进入了执行状态,也不是一直处于执行状态,而是进入了就绪状态(Runnable),等待CPU调度执行;
线程有5种状态:New、Runnable、Running、Blocked、Dead
如下图:
当线程对象创建后,即进入了新建状态,如:
Thread thread = new MyThread();
当调用了线程的start()方法之后,线程即进入了就绪状态,并不是说执行了thread.start(),thread线程就会立即执行,而是说明该线程做好了准备,随时等待CPU的调度执行;
当CPU调度处于就绪状态的线程时,该线程才会真正执行,即进入到运行状态;
线程想进入运行状态必须先进入就绪状态,就绪状态时进入运行状态的唯一入口;
处于运行状态中的线程由于某一原因,暂时放弃了对CPU的使用权,停止执行任务,就会进入到阻塞状态,直到该线程重新进入到就绪状态,才会有机会被CPU重新调用,从而再次进入运行状态;
阻塞状态可分为3种:
等待阻塞:运行状态的线程执行wait()方法,使本线程进入到等待阻塞状态;
同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程占用),会进入到同步阻塞状态;
其他阻塞:通过调用sleep()或join()或I/O请求时,线程会进入阻塞状态。当sleep状态超时,join等待线程终止或超时,或IO处理完毕,线程会重新进入就绪状态;
线程执行完成,或因为异常退出了run()方法,该线程就结束了生命周期。
保证多个线程协同有序的一起完成一件事情;
当A线程需要等待B线程完成一个任务,才会继续接着执行后面任务时,使用Object.wait()方法,让A线程进入等待,然后等B线程执行完这个任务后,使用Object.notify()方法,通知A线程,将A线程从WAITING状态中唤醒,继续执行A线程后面的任务;
方法 | 说明 |
---|---|
public final void wait() | 释放锁,进入等待队列 |
public final void wait(long timeout) | 释放锁,进入等待序列,但超过设定时间之后,会自动唤醒 |
public final void notify() | 随机唤醒,通知所有线程 |
public final void notifyAll() | 唤醒、通知所有线程 |
现有三个线程,一个老板,一个顾客,一起完成一个商品的交易;
交易过程如下(顺序不能乱):
1.顾客挑选货物,耗时2秒
2.顾客付钱
3.老板计算需要找零多少,耗时2秒
4.老板找零
5.顾客拿回零钱走人
public class Demo6 {
public static void main(String[] args) {
// 锁
Object lock1 = new Object(); // 顾客付钱通知老板的锁
Object lock2 = new Object(); // 老板找零通知顾客的锁
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "==>1. 挑选货物");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "==>2. 付钱");
synchronized (lock1) {
lock1.notify();
}
synchronized (lock2) {
try {
lock2.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "==>5. 拿回零钱走人");
}
}, "顾客").start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1) {
try {
lock1.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "==>3. 计算需要找零多少");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "==>4. 找零");
synchronized (lock2) {
// 通知顾客找零了,可以走人了
lock2.notify();
}
}
}, "老板").start();
}
}
睡眠(sleep)和等待(wait)的区别
wait:线程不再活动,不再参与调度,进入到wait set(锁池)中,会释放锁,也不会去竞争锁,因此不会浪费cpu的资源,此时线程的状态为WAITING,需要等其他线程执行“通知(notify)”,才能将在这个对象上等待的线程从wait set中释放出来,重新进入调度队列(ready queue);
sleep:不会释放锁,会一直占用cpu资源;
wait与notify是Object类的方法,所以任意对象都有这两个方法;
两个或多个线程同时被阻塞,都在等待某一个锁资源的释放,由于线程被无限期的阻塞,所以程序不可能正常终止;
如两个线程A和B,两个锁资源lock1和lock2,线程A已经持有lock1,线程B已经持有lock2,但是现在线程A需要获取lock2才能继续往下执行,而线程B需要获取lock1才能继续往下执行,所以两个线程都需要对方已经持有的锁资源,此时两个线程就会因为互相等待对方释放资源,而进入到死锁的状态;
代码示例:
public class Demo7 {
public static void main(String[] args) {
Object lock1 = new Object(); // 锁资源1
Object lock2 = new Object(); // 锁资源2
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "==>获取到锁1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "==>等待获取锁2");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "==>获取到锁2");
}
}
}
}, "线程A").start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "==>获取到锁2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "==>等待获取锁1");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "==>获取到锁1");
}
}
}
}, "线程B").start();
}
}