JAVA中线程的相关小结

334 阅读8分钟

·什么是线程

线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程。

多线程:一个进程中包含有多个线程,但CPU在同一时间只允许一个线程的进行。所以有多个线程的运行是根据CPU切换完成,如何切换由CPU决定,因此多线程运行具有不确定性。

·线程的创建方法

1.继承Thread类

即创建一个thread类,或者创建一个thread子类的对象

1>Thread类的介绍

  • Thread类是一个线程类,位于java.lang包下

  • Thread的主要构造方法:

    1.1 thread() 创建一个线程对象

    1.2 thread(String name) 创建一个具有指定名称的线程对象

    1.3 thread(Runnable target) 创建一个基于runnable接口实现类的线程对象

    1.4 thread(Runnable target,string name) 创建一个基于Runnable接口实现类,并且具有指定名称的线程对象

  • 常用方法: 1.1 public void run() 线程相关的代码写在该方法中,一般需要重写

    1.2 public void start() 启动线程

    1.3 public static void sleep(long m) 线程休眠m毫秒

    1.4 public final void join() 优先执行调用join方法的线程

2>具体的代码实现

package _Thread;

class mythread extends Thread{
//重写Run方法
	public void run() {
		System.out.println(getName()+"线程");
	}
}

public class _threadTest {
	public static void main(String[] args) {
		mythread sr=new mythread();
		sr.start;//启动线程
	}
}

实现结果

然后为了将CPU时间片的轮转体现出来,将加入2个循环如下:

package _Thread;

class mythread extends Thread{
	public void run() {
		for(int i=1;i<=10;i++)
		System.out.println(getName()+"线程"+i+"次");
	}
}

public class _threadTest {
	public static void main(String[] args) {
		mythread sr=new mythread();
		sr.start();
		for(int i=1;i<=10;i++)
			System.out.println("主线程"+i+"次");
	}
}

两次不同的运行结果:

这说明线程的运行是有cpu随机切换完成的,具有不确定性。

tips:

线程只能启动一次

主方法也是一个线程

2.实现Runnable接口

1>Runnable类的介绍

由于JAVA是不支持多继承的,所以如果一个类已经有一个父类,那么我们就无法通过继承Threadl类来创建线程,此时我们就可以通过实现Runnable接口来完成。Runnable是java中实现线程的接口,它有且只有一个方法Run(),并且任何实现线程功能的类必须实现该接口。

2>具体的代码实现

package _Thread;

class mythread implements Runnable{
	public void run() {
		System.out.println(Thread.currentThread().getName()+"线程");
		/*
		由于这里没有继承Thread类中的方法,
		所以这里调用Thread中的currentThread()的getName()来获取当前线程的名字
		*/
	}
}

public class _threadTest {
	public static void main(String[] args) {
		mythread sr=new mythread();
		Thread pr=new Thread(sr);//通过调用Thread类中的含参构造方法来创建一个线程对象
		pr.start();
	}
}

tips:一个实例可以被多个线程所共享,一般用于多个线程处理同一个资源的情况。

·线程的状态和生命周期

1.线程的几种状态

    1.1新建(NEW)状态

当程序使用new关键字创建了一个线程之后,该线程就处于新建状态.

    1.2可运行(Runnable)状态

该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

·关于直接调用run()方法

如果直接调用线程对象的run方法,也就是相当于启动线程,并直接执行run方法,而且再run方法结束之前无法运行其他线程。

    1.3正在运行(Runnming)状态

当CPU开始调度处于可运行状态的线程时,此时线程获得了CPU的时间片才得以真正开始执行run()方法的线程执行体,则该线程处于可运行状态。

    1.4阻塞(Blocked)状态

处于运行状态的线程在某些情况下,让出CPU并暂时停止该线程的运行,也就是进入了阻塞状态。

    1.5终止(Dead)状态

线程的run()或call()方法执行完成,线程会正常结束;

线程抛出了一个未捕获的Exception或Errorl异常,线程会意外终止;

或者直接调用该线程stop()方法来结束该线程,但可能会导致线程的死锁;

2.线程的生命周期

线程的生命周期实际上是指线程的几种状态的之间的切换。

生命周期图如下:

这些状态的切换往往是通过调用Thread类中的方法来实现的,下面介绍几种常用的方法:

    2.1  public static native void sleep(long millis)
  • 作用:使当前正在运行的线程休眠millis毫秒
  • 由于该线程是从运行状态到阻塞状态,再到可运行状态,并不是直接到可运行状态。所以中间会产生一定的时间误差,使其略长于所设参数。
  • 该方法是在Thread类中的一个静态方法。
    2.2  object.wait()
  • 作用:暂停该线程,释放对象锁
  • 该方法是在object类中的方法
  • 在调用前需要先拥有某对象的锁,所以一般在 synchronized 同步块中使用

·对象锁简单的理解就是在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段 于之对应的还有类锁,指在代码中的方法上加了static和synchronized的锁(在下面还会详细涉及到)。

    2.3 Thread.yield()
  • 作用:表示暂停当前线程,让出 CPU给优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程。
  • 需要注意的是这和 sleep() 方法不同的是,它不会进入到阻塞状态,而是直接进入到可运行状态。
    2.4 Thread.join()
  • 会优先将该线程运行完毕,再去调用其他线程,也就是等待该线程的终止

  • 其重载方法还有

    public final void join(long millis)等待该线程终止的时间最长为 millis 毫秒

    public final void join(long millis, int nanos)等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

·线程的优先级

Java 把线程优先级分成10个级别(依次为1到10,数字越大线程的优先级越高),线程被创建时如果没有明确声明则使用默认优先级(5)。虽然优先级越高越先被执行,但我们不能用线程优先级来规定线程的执行顺序。因为线程的执行顺序还受很多其他因素影响,例如操作系统和JVM的调度等等,所以我们只能说线程优先级越大,越先被执行的可能性越高。这里也体现了CPU调度的随机性。

1.三个优先级的常量

最小优先级(1)=Thread.MIN_PRIORITY

默认优先级(5)=Thread.NORM_PRIORITY

最大优先级(10)=Thread.MAX_PRIORITY

2.优先级的相关方法

   2.1 public int getPriority()
  • 作用:取得线程的优先级
   2.2 public void setPriority(int newPriority)
  • 作用:设置线程的优先级

·线程的同步

由于线程被执行的时间、占用cpu的时间都是不确定的,所以在有多个线程要被执行的情况下,会产生许多问题。例,在有两个线程(线程1和线程2)对同一个类的属性进行操作时,如果线程1对该属性进行“加”的操作前,线程2突然抢占了CPU来对该属性进行了“减”的操作,对该属性进行修改,那么就可能出现类似于1+1=1的结果。

为了避免上述结果的出现,这里需要调用一个synchronized关键字(同步),来对该共享对象进行上锁,即让该共享对象在同一个时刻,只能被一个线程访问。

synchronized关键字的使用

一般加在线程前,表明该线程不允许被打断, 可以加在静态方法/方法/语句块前。 例:

public synchronized void  方法名(){}
public static synchronized void  方法名(){}
synchronized(对象名){}

线程间的通信

我们先引进三个方法

wait()中断方法的执行,使线程等待
notify()随机唤醒等待的某一个线程
notifyall()唤醒所有处于等待的线程

对多线程的处理,下面以对2个线程的处理为例

一般是将公共对象当作一个容器queue,并在该容器内设置一个Boolean类型的值,在将线程1和2设置为synchronized类型后,在线程1中添加判断语句,如果该Boolean型值为ture(也可以是flase)才能执行该线程的操作语句,否则进入等待(在这里调用wait()使其暂停运行,记得要在线程最后改变Boolean类型值的值),对线程2的操作也类似,这样就能解决对线程调度顺序问题。

但这样问题并不是完全被解决,如果线程1和2都进入休眠状态,那么锁死,即两个线程都不进行。所以我们还需要在线程1和线程2的最后加上唤醒醒另一个线程的语句(这里调用notifyall(),之所以不调用notify()是因为它是随机唤醒某一个线程,如果在程序中还有线程3、线程4···,那我们想唤醒的线程有可能不被唤醒)