1.进程与线程
(1).什么是进程?
进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
(2).什么是线程?
线程:是一个执行路径,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
2.线程的生命周期及五种基本状态
Java线程具有五种基本状态:
(1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
(2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
(3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
①等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
②同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
③其他阻塞 : 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
图中的方法解析如下:
Thread.sleep():在指定时间内让当前正在执行的线程暂停执行,但不会释放"锁标志"。不推荐使用。
Thread.sleep(long):使当前线程进入阻塞状态,在指定时间内不会执行。
Object.wait()和Object.wait(long):在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的"锁标志",从而使别的线程有机会抢占该锁。 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常,waite()和notify()必须在synchronized函数或synchronized中进行调用。如果在non-synchronized函数或non-synchronized中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
Object.notifyAll():则从对象等待池中唤醒所有等待等待线程
Object.notify():则从对象等待池中唤醒其中一个线程。
Thread.yield()方法 暂停当前正在执行的线程对象,yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会。
Thread.Join():把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
3.Java多线程的实现
在Java中,如果要实现多线程的程序,那么必须依靠一个线程的主体类(好比主类的概念一样,表示一个线程的主类),但是这个线程的主体类在定义的时候需要有一些特殊的要求,这个类可以继承Thread类或实现Runnable接口来完成定义。
(1)继承Thread类实现多线程
java.lang.Thread是一个负责线程操作的类,任何的类继承了Thread类就可以成为一个线程的主类。既然是主类,必须有它的使用方法,而线程启动的方法需要覆写Thread类中的run()方法才可以。
定义一个线程的主体类:
class TestThread extends Thread{
String name;
public TestThread(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++){
System.out.println(this.name+i);
}
}
}
现在已经有了线程类,并且里面也存在了相应的操作方法,那么就应该产生对象并调用里面的方法,于是编写出了以下的程序:
public class MyClass {
public static void main(String[] args){
TestThread t1 = new TestThread("A");
TestThread t2 = new TestThread("B");
TestThread t3 = new TestThread("C");
t1.run();
t2.run();
t3.run();
}
}
运行结果:
A1
A2
A3
A4
A5
B1
B2
B3
B4
B5
C1
C2
C3
C4
C5
我们发现:以上的操作并没有真正的启动多线程,因为多个线程彼此之间的执行一定是交替的方式运行,而此时是顺序执行,每一个对象的代码块执行完之后才向下继续执行。
如果想要在程序之中真正的启动多线程,必须依靠Thread类的一个方法:public void start(),表示真正启动多线程,调用此方法后会间接调用run()方法:
public class MyClass {
public static void main(String[] args){
TestThread t1 = new TestThread("A");
TestThread t2 = new TestThread("B");
TestThread t3 = new TestThread("C");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
A1
B1
C1
B2
A2
B3
C2
B4
B5
A3
A4
A5
C3
C4
C5
此时可以发现:多个线程之间彼此交替执行,但每次的执行结果是不一样。通过以上的代码就可以得出结论:要想启动线程必须依靠Thread类的start()方法执行,线程启动之后会默认调用了run()方法。
在调用start()方法之后,发生了一系列复杂的事情:
(1)启动新的执行线程(具有新的调用栈);
(2)该线程从新状态转移到可运行状态;
(3)当该线程获得机会执行时, 其目标run()方法将运行。
注意:对java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法时合法的,但并不启动新的线程。
(2)Runnable接口实现多线程
使用Thread类的确时可以方便的进行多线程的实现,但是这种方式最大的缺点就是单继承的问题。为此,在java之中也可以利用Runnable接口来实现多线程。这个接口的定义如下:
public interface Runnable{
public void run();
}
通过Runnbale接口实现多线程:
class TestThread implements Runnable{
String name;
public TestThread(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(name+i);
}
}
}
这和之前继承Thread类的方式区别不大,但是有一个优点就是避免了单继承局限。
不过问题来了。之前说过,如果要启动多线程,需要依靠Thread类的start()方法完成,之前继承Thread类的时候可以将此方法直接继承过来使用,但现在实现的是Runnable接口,没有这个方法可以继承了,怎么办?
需要解决这个问题,还是需要依靠Thread类完成。在Thread类中定义了一个构造方法,接收Runnable接口对象:
public Thread (Runnable target);
利用Thread类启动多线程:
public class MyClass {
public static void main(String[] args){
TestThread t1 = new TestThread("A");
TestThread t2 = new TestThread("B");
TestThread t3 = new TestThread("C");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
运行结果:
B1
B2
A1
C1
A2
B3
A3
C2
A4
B4
A5
C3
B5
C4
C5
此时,不但实现了多线程的启动,而且没有了单继承局限。
4.Thread类和Runnable接口实现多线程两种方式的区别
Thread类和Runnable接口都可以为同一功能的方式来实现多线程,那么从java的实际开发而言,肯定优先考虑使用Runnable接口,因为可以有效的避免单继承的局限,除了这个,这两种方式是否还有其他联系呢?
我们先来看Thread类的定义结构:
public class Thread extends Object implements Runnable
发现Thread类也是Runnable接口的子类,这样的话,Runnable接口实现多线程序的结构就有了以下形式:
除了以上的联系之外,对于Runnable和Thread类还有一个区别:使用Runnable接口可以更加方便的表示出数据共享的概念。(这里的数据共享是指多个线程访问同一个资源的操作)
5.多线程的常用操作方法
(1)currentThread()方法:返回代码段正在被哪个线程调用的信息。
public static Thread currentThread()
实例如下:
public class MyClass {
public static void main(String[] args){
System.out.println(Thread.currentThread());
TestThread t1 = new TestThread();
new Thread(t1).start();
new Thread(t1).start();
new Thread(t1).start();
}
}
class TestThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread());
}
}
运行结果:
Thread[main,5,main]
Thread[Thread-0,5,main]
Thread[Thread-1,5,main]
Thread[Thread-2,5,main]
(2)setName()/getName()和getId()方法:
setName()/getName():设置/取得线程的名称:
public final void setName(Sring name)
public final void getName(String name)
getId():取得线程的唯一标识
public final void setName(String name)
实例如下:
public class MyClass {
public static void main(String[] args){
TestThread testThread = new TestThread();
Thread thread = new Thread(testThread);
thread.setName("线程");
System.out.println(thread.getName()+" "+thread.getId());
}
}
class TestThread implements Runnable{
@Override
public void run() {
System.out.println();
}
}
运行结果:
线程 12
————————————————
借鉴:https://blog.csdn.net/wei_zhi/article/details/52787749