一,教材学习内容(12多线程机制)
1,进程与进程:线程不是进程,但线程的行为很像进程,线程是比进程更小的执行单位,一个进程在执行过程中,可以产生多个线程每个线程也有它自身产生,存在和消亡的过程。与进程不同的是,线程的中断和恢复可以更加节省系统的开销。
需要注意的是:没有进程就没有线程!
2,
Java的线程具有五中基本状态
新建状态(新):当线程对象对创建后,即进入了新建状态,如:线程t = new MyThread();
就绪状态(可运行): 。当调用线程对象的开始()方法(t.start();),线程即进入就绪状态处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(运行):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
有四种原因可能引起中断:
1,JVM(Java虚拟机)将CPU资源从当前线程切换到其他线程,使本线程让出的使用权的CPU而处于阻塞状态。
2,线程使用CPU期间,使用睡眠()方法使当前线程处于休眠状态。
3,线程使用CPU期间,执行了等待()方法,使得当前想线程进入等待状态。等待状态的线程不会主动进入线程队列中排队等待CPU的资源,必须由其它线程调用通知()方法通知它,使它重新进到线程队列中排队等待CPU资源。
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行等待()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 - 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 - 通过调用线程的sleep()或join()或发出了I / O请求时,线程会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或者超时,或者I / O处理完毕时,线程重新转入就绪状态。
死亡状态(死):线程执行完了或者因异常退出了的run()方法,该线程结束生命周期。
3,线程具有优先级
每一个Java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java线程的优先级是一个整数,其取值范围是1(Thread.MIN_PRIORITY) - 10(Thread.MAX_PRIORITY)。
默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。
4,主题类与线程的创建
1)使用螺纹类或子类创建线程对象。
2)在编写线程类的子类时,需要重写父类的run()的方法,以规定线程的具体操作。
3)使用螺纹子类创建现场的优点是:可以在子类中增加新的成员变量,使线程具有某种属性,也可以在子类中新增加方法,使线程具有新的功能。
线程的创建:
1)使用Thread类进行创建
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread myThread1 = new MyThread(); // 创建一个新的线程 myThread1 此线程进入新建状态
Thread myThread2 = new MyThread(); // 创建一个新的线程 myThread2 此线程进入新建状态
myThread1.start(); // 调用start()方法使得线程进入就绪状态
myThread2.start(); // 调用start()方法使得线程进入就绪状态
}
}
}
}
2)实现可运行接口,并重写该接口的run()的方法,该运行()方法同样是线程执行体,创建可运行实现类的实例,并以此实例作为线程类的参数来创建主题对象,该线程对象才是真正的线程对象。
使用Runnable接口实现线程(Thread(Runnable target))调用run()方法的步骤次序与直接使用Thread类是一样的。这里的Thread对象类似于一个启动器。
class MyRunnable implements Runnable {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
Thread thread2 = new Thread(myRunnable);
thread1.start(); // 调用start()方法使得线程进入就绪状态
thread2.start();
}
}
}
}
需要注意的是:不能对同一线程对象多次调用星()方法。
5,目标对象与线程的关系
1)目标对象与线程完全解耦
这里的完全解耦指的是目标对象不包括线程对象的引用
2)目标对象组合线程(弱耦合)
这里的弱耦合指的是线程对象组合目标对象。
6,线程常用的几个方法
1)星号()方法:启动线程,使之从新建状态进入就绪队列中排队,注意,每一个线程对象只能使用一次星()方法,不能多次调用
2)run()方法:用来定义线程对象被调度之后所执行的操作,需要用户重写。
3)sleep(int time)方法:线程休眠time毫秒,此方法可以完成一些特定的功能或者使高级别的线程休眠。
4)inAlive()方法:用于线程调用,返回boolean值,用以判断线程状态。
5)currentThread()方法:Thread类的类方法,可以返回当前正在使用cpu资源的线程。
6)interrupt()方法:用来吵醒被sleep()方法休眠的线程,使其重新排队等候cpu资源。
7、线程同步: java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。
保证线程运行顺序的正确性和安全性。
线程同步就是若干个线程都需要使用一个synchronize(同步)修饰的方法,即程序中若干个线程都需要使用一个方法,而这个方法用synchronize给予修饰。多个线程调用synchronize方法必须遵守同步机制。
同步机制:当一个线程A使用synchronize方法时,其他线程想使用这个synchronize方法时就必须等待,直到线程A使用完synchronize方法。
8、协调同步线程
当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用wait()方法。其他线程如果在使用这个同步方法时不需要等待,那么它使用完这个同步方法的同时,应该用notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待,曾中断的线程就会从刚才中断处继续执行这个同步方法,并遵循“先中断先进行”的原则。如果使用notify()方法,那么只是通知处于等待中的一个线程等待结束。
wait()、notify()、notifyAll()方法都是Object类中的final方法,被所有类继承且不允许被重写。
特别注意:不可以在非同步方法中使用等待(),通知()和notifyAll的()方法。
9,线程联合
一个线程在占有CPU资源期间,可以让其他线程调用join()方法和本线程联合。
一旦线程甲联合线程B,那么甲线程将立刻中断执行,一直等到乙线程执行完毕,A线程再重新排队等待,以便恢复执行。如果一个准备联合的乙线程已经结束,那么B.join()将不会产生任何效果。