java基础-多线程
一、什么是多线程
线程对象是可以产生线程的对象。比如在Java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线程的优势有:
(1)进程之间不能共享数据,线程可以;
(2)系统创建进程需要为该进程重新分配系统资源,故创建线程代价比较小;
(3)Java语言内置了多线程功能支持,简化了java多线程编程。
二、线程的状态
先上张网图,理解图了,很多知识就能连贯了。
1、新建状态
用new关键字,Thread类或者其子类创建了一个线程对象后,该对象就处于初始状态。通过调用Thread类的start方法,线程对象进入就绪状态(runnable)。
注意:对已经调用了start方法的Thread对象,不能再次调用。不然会报Java.lang.IllegalThreadStateException异常。
2、就绪状态
处于就绪状态的线程对象已经具备了运行的条件,但是没有分配到cpu。处于等待队列,(等待队列中的对象分配到cpu的机会是随机的。)等待进程挑选一个等待执行的Thread对象,被选中后,该对象就从就绪状态进入了执行状态。系统挑选的动作被称为cpu调度。一旦获得了cpu,线程就进入了运行状态并自动调用自己的run方法。
3、运行状态
运行状态的线程最为复杂。他可以变味就绪状态、死亡状态和堵塞状态。
线程一旦获得了cpu,就转为了运行状态。当thread对线调用Thread类的 sleep(线程休眠),join(线程加入)等方法与Object 的wait等方法使得运行状态的线程进入阻塞状态。使用Thread.yeild()方法,当前对象会放弃cpu进入就绪状态,与其他等待的线程一起竞争cpu。
如果线程run()方法执行完,或者被强制停止。如出现异常,调用stop方法,destroy方法等等,线程就会从运行态进入了死亡状态。
4、阻塞状态
线程由于某些原因失去了cpu资源,只有从新进入就绪状态,才有可能重新获得cup,进入运行状态。阻塞状态分为三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
4、死亡状态
当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
三、线程的创建
在java中创建线程有一下几个方式:
1、继承Thread类
实例:
package ThreadDemo.demo;
public class Demo1 extends Thread {
private String name ;
public Demo1(String name){
this.name = name;
}
public void run(){
for(int i = 0 ; i < 5 ; i++){
System.out.println(name + "运行" + i );
try{
sleep((int)Math.random() * 10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Demo1 thread1 = new Demo1("A");
Demo1 thread2 = new Demo1("B");
thread1.start();
// thread2 = thread1;
thread2.start();
}
}
2、实现Runnable接口
实例:
public class ThreadDemo2 implements Runnable {
private String name ;
public ThreadDemo2(String name){
this.name = name;
}
public void run() {
for(int i = 0; i < 20; i++){
System.out.println(name + "运行" + i);
}
try {
Thread.sleep((int)Math.random() * 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class test2 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "主线程开始执行");
Thread demo1 = new Thread(new ThreadDemo2("E"));
Thread demo2 = new Thread(new ThreadDemo2("C"));
Thread demo3 = new Thread(new ThreadDemo2("D"));
demo1.start();
demo2.start();
demo3.start();
try {
demo1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
demo2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
demo3.join(30000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "主线程结束");
}
}
3、通过Callable、Future、线程池创建
实例:
public interface Callable {
V call() throws Exception;
}
步骤1:创建实现Callable接口的类SomeCallable(略);
步骤2:创建一个类对象:
Callable oneCallable = new SomeCallable();
步骤3:由Callable创建一个FutureTask对象:
FutureTask oneTask = new FutureTask(oneCallable);
注释: FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了 Future和Runnable接口。
步骤4:由FutureTask创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
步骤5:启动线程:
oneThread.start();
总结:
如果一个类继承Thread类,则不适合资源共享,因为在java世界继承都是单继承的。而现实Runnable接口,共享资源就变得简单多了。
实现Runnable接口比继承thread类具有以下优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
四、线程的调度
1、调整线程的优先级
java线程具有优先级。优先级高的线程会获得较高的运行机会。
java中线程的优先级是用1-10的整数来表示。Thread类中用setPriority()与getPrioprity()来设置与获得线程的优先级。线程的优先级具有继承关系。比如在A线程中创建了B线程。那么B线程具有与A线程一样的优先级。
Thread类中定义了三个静态常量:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
每个线程都有默认的优先级,主线程的默认优先级为 NORM_PRIORITY。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
2、线程睡眠(Thread.sleep()) Thread.sleep(long millis)方法 让线程转入阻塞状态。millis为阻塞时长,单位为毫秒。当睡眠时间结束后,线程由阻塞状态转为就绪状态。sleep没有释放对象锁。
3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。