首页 > 其他分享 >JTCR-多线程-09

JTCR-多线程-09

时间:2024-04-18 15:34:27浏览次数:30  
标签:JTCR Thread void 09 System 线程 多线程 方法 out

基于进程和线程的多任务,其最小的调度单位分别是进程和线程。

在基于线程的环境中,单个进程可以同时处理不同的任务,每个线程共享地址空间。

基于线程的多任务和基于进程的相比,开销小。相互间的通信和上下文切换开销不同。

Java 的线程模型

Java 的运行时系统使用多线程,当某一个线程阻塞时,不影响其他正在执行的线程。如果是单核处理器,线程轮流使用 CPU 时间片;如果是多核处理器,多个线程可以同时处理。

线程有 running、ready、run、suspended、resumed、blocked、terminated 这些状态。

Java 中线程优先级由一个整数指定,某个线程的优先级是相对于其他线程而言的。优先级在上下文切换时使用。上下文切换的情形:

  • 一个线程自愿放弃 CPU 的使用
  • 高优先级的线程抢占低优先级线程的 CPU 时间。

如果至少有两个相同优先级线程竞争 CPU 时间片,此时根据 OS 的不同有不同的行为。对于部分 OS,相同优先级的线程以 CPU 时间片轮转的方式调度线程;对于另一部分 OS,当某个线程不主动放弃 CPU 的使用,其他同优先级的线程无法运行。

monitor 是一种控制机制,可以将其看作一个小盒子,任一时刻仅允许一个线程位于其中。当一个线程在里面时,所有其他线程必须等待,直到这个线程退出盒子。用来保证共享资源在任何时刻都由一个线程使用。

Java 中,每一个对象隐含有 monitor 机制。某个线程调用一个对象的同步方法时,该对象的其他所有同步方法都不能被其他线程调用。

Java 提供了一种清晰、低成本的方式用于线程间通信。消息系统允许一个线程进入一个对象的同步方法,等待直到另一个线程通知它退出。

Java 的多线程有 Thread 类和 Runnable 接口,Thread 封装了执行的线程。为创建一个线程,要么继承 Thread 类要么实现 Runnable 接口。

主线程

Java 程序启动时,有一个线程立即执行,这个线程称为主线程。所有其他线程都由主线程产生;主线程是最后一个结束执行的线程,因为它需要执行各种终止操作。

public static Thread currentThread() 方法返回当前线程的引用,可以获得主线程的引用。

打印输出线程时,依次打印线程名、优先级和线程组名。线程组是控制作为一个整体的多个线程状态的数据结构。

创建线程

实现 Runnable 接口来新建线程的方式为:首先定义一个实现 Runnable 接口的类,该类必须实现 Runnable 接口中的 public void run() 方法,在 run() 方法里面,定义的是新线程的执行逻辑,可以看作是另一个程序的执行逻辑。在这个方法里面,允许定义类、变量和调用方法等操作。在这个类里面,需要实例化一个 Thread 类对象,构造器之一为 Thread(Runnable threadObj, String threadName)。新线程创建后,并不会执行,需要调用 Thread 类中的 start() 方法才会开始执行。start() 方法本质上调用了 run() 方法。

// 定义实现了 Runnable 接口的类 A,实现该接口的 run 方法
// run 方法中的内容即为子线程的执行内容
// 在 A 中实例化 Thread 类
// 以上创建完成子线程

// 使用子线程
// 实例化类 A
// 调用类 A 中 Thread 类实例的 start 方法

class A implements Runnable {
  Thread t;
  
  A() {
    t = new Thread(this, "ThreadName");
  }
  
  public void run() {
    try {
	  for (int i = 5; i >= 1; i--) {
	    System.out.println("child: " + i);
		Thread.sleep(500);
	  }
	} catch (InterruptedException e) {
	  System.out.println("child interrupted");
	}
	System.out.println("exit child thread");
  }
}

public class T {
  public static void main(String[] args) {
    A a = new A();
	a.t.start();
	
	try {
	  for (int i = 5; i >= 1; i--) {
	    System.out.println("main: " + i);
	    Thread.sleep(1000);
	  }
	} catch (InterruptedException e) {
	  System.out.println("main interrupted");
	}
	System.out.println("exit main thread");
  }
}

创建线程的另一种方法是定义 Thread 类的子类,需要实现 Thread 中的 run() 方法。调用该类的 start() 方法就可以启动新线程。

class CustomThread extends Thread {
  CustomThread() {
    super("ThreadName");
  }
  
  public void run() {
    try {
	  for (int i = 5; i >= 1; i--) {
	    System.out.println("child: " + i);
	    Thread.sleep(500);
	  }
	} catch (InterruptedException e) {
	  System.out.println("child interrupted");
	}
	System.out.println("exit child thread");
  }
}

class B {
  public static void main(String[] args) {
    CustomThread c = new CustomThread();
    c.start();

    try {
      for (int i = 5; i >= 1; i--) {
        System.out.println("main: " + i);
        Thread.sleep(1000);
      }
    } catch (InterruptedException e) {
      System.out.println("main interrupted");
    }
    System.out.println("exit main thread");
  }
}

具体使用上述两种方法中的哪一种取决于实际情况。实现 Runnable 接口则可以继承其他类;如果不重写 Thread 类中的其他方法,继承 Thread 的方式更简单。

创建多线程

和上一部分一样,只不过调用时创建了一个类的多个实例,分别调用每个实例的 start() 方法。

使用 isAlive() 和 join()

调用 Thread 类的 final boolean isAlive() 方法的线程如果正在执行则返回 true,否则返回 false。

如果在线程 A 中,在线程 B 上调用了 final void join() 方法,则 A 线程会等待,直到线程 B 执行完成后,A 线程恢复执行。

线程优先级

当线程调度器决定运行哪个线程时,会选择优先级高的线程运行。
final void setPriority(int level) 方法设置线程的优先级,可选值范围从 MIN_PRIORITY(1) 到 MAX_PRIORITY(10),默认的优先级为 NORM_PRIORITY(5)。
final int getPriority() 方法获得线程的优先级。

同步

当一个资源被多个线程同时访问时,需保证在任何时候,只有一个线程在使用该资源,这种机制称为同步。monitor 是作为互斥锁的对象,当某个线程在某一时刻获得了锁,,则称该线程进入了 monitor。此时,其他尝试进入 monitor 的线程在已经进入 monitor 的线程退出之前状态变为 suspended,也称等待 monitor。进入 monitor 的线程可以再次进入 monitor。

在 Java 中,使用同步比较简单。每个对象都有与之关联的隐式 monitor。当一个对象的 synchronized 方法被调用时,当前线程进入 monitor,任何调用该对象 synchronized 方法的其他线程必须等待,直到当前线程从 synchronized 方法返回。

class Nchr {
  synchronized void call(String msg) {
    System.out.print("a-" + msg);
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      System.out.println("Interrupted");
    }
    System.out.println("-end");
  }
}

class Ca implements Runnable {
  Nchr obj;
  String msg;
  Thread t;
  
  public Ca(Nchr n, String m) {
    obj = n;
    msg = m;
    t = new Thread(this);
  }
  
  public void run() {
    obj.call(msg);
  }
}

public class Out {
  public static void main(String[] args) {
    Nchr n = new Nchr();
    Ca one = new Ca(n, "one");
    Ca two = new Ca(n, "two");
    Ca three = new Ca(n, "three");

    one.t.start();
    two.t.start();
    three.t.start();
    /* 输出
            a-one-end
            a-two-end
            a-three-end
    */
    try {
      one.t.join();
      two.t.join();
      three.t.join();
    } catch (InterruptedException e) {
      System.out.println("Interrupted");
    }
  }
}

如果想要调用的方法是非 synchronzied 方法,但又想使用同步,可以使用同步块。如下:

// 同步块括号中的引用表示需要同步的对象
// 上一个代码段中 Ca 类的 run 方法改写的等价形式
public void run() {
  synchronized(t) {
    t.call(msg)
  }
}

线程间通信

在 Java 中,Object 类中的 wait()notify()notifyAll() 方法用于线程间通信。这三个方法必须在 synchronized 上下文中使用。

  • final void wait():让调用该方法的线程放弃 monitor 进入休眠状态,等待直到另一个线程进入这个 monitor 并调用 notify()notifyAll() 方法。
  • final void notify():唤醒在同一个对象上调用了 wait() 方法的一个线程。
  • final void notifyAll():唤醒所有在同一个对象上调用了 wait() 方法的线程,给予其中某个线程访问权限。

使用 wait() 方法的线程有可能不是通过调用 notify()notifyAll() 方法唤醒的。wait() 方法应该在检查条件的循环中使用。

class Q {
	int value;
	boolean isSet = false;
	
    // synchronized 方法必须执行完,其他方法才能执行
	synchronized void put(int value) throws InterruptedException {
        // 值已经设置了,则挂起
		while (isSet) {
			wait();
		}
		this.value = value;
		System.out.println("put: " + value);
		isSet = true;
		notify();
	}
	
	synchronized void get() throws InterruptedException {
		while (!isSet) {
			wait();
		}
		int value = this.value;
		System.out.println("get: " + value);
		isSet = false;
		notify();
	}
}

class Producer1 implements Runnable {

	Thread thread;
	private Q q;
	
	Producer1(Q q) {
		thread = new Thread(this);
		this.q = q;
	}
	
	@Override
	public void run() {
		int i = 0;
		while (i <= 20) {
			try {
				q.put(i++);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}

class Consumer1 implements Runnable {
	Thread thread;
	private Q q;
	
	Consumer1(Q q) {
		thread = new Thread(this);
		this.q = q;
	}

	@Override
	public void run() {
		while (true) {
			try {
				q.get();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class PCFixed {
	public static void main(String[] args) {
		Q q = new Q();
		Producer1 p = new Producer1(q);
		Consumer1 c = new Consumer1(q);
		p.thread.start();
		c.thread.start();
	}
}

一个线程 a 进入对象 X 的 monitor 中,同时另一个线程 b 进入对象 Y 的 monitor 中。然后, a 调用 Y 的 synchronized 方法, b 调用 X 的 synchronized 方法。此时,两个线程都需无限等待,发生死锁。

挂起、恢复和停止

线程执行状态的改变通过设置一个状态变量决定。在 run() 方法中根据状态变量使用 wait() 方法挂起,在其他方法中设置状态变量的值,有时需要调用 notify() 方法唤醒。

获得线程状态

Thread.State getState() 方法返回调用该方法时线程所处的状态。由于该方法返回结果之后,线程的状态可能已发生变化,所以不能用于线程同步。Thread.State 的取值有:

说明
BLOCKED 因等待锁挂起
NEW 没有开始执行
RUNNABLE 正在执行或者正等待CPU时间将执行
TERMINATED 完成执行
TIMED_WAITING 挂起一段时间,如 sleep
WAITING 因等待事件发生挂起

使用工厂方法创建和启动线程

如果需要使用一条语句创建并启动线程,而不是分两步进行,可以使用工厂方法。工厂方法是一个静态方法,在这个方法里面,创建类的实例并启动线程,然后返回对实例的引用。

class NewThread {
  public static NewThread createAndStart() {
    var t = new NewThread();
    t.start();
    return t;
  }
}

var n = NewThread.createAndStart();

参考

[1] Herbert Schildt, Java The Complete Reference 11th, 2019.

标签:JTCR,Thread,void,09,System,线程,多线程,方法,out
From: https://www.cnblogs.com/xdreamc/p/16364778.html

相关文章

  • JTCR-异常处理-08
    异常处理基础try{//可能产生异常的代码块}catch(ExceptionTypeex){//处理异常}catch(ExceptionTypeex){//处理异常}...finally{//无论是否发生异常,必须执行的代码块}异常类型所有异常类型的超类是Throwable,该类有两个直接子类,一个是Excepti......
  • JTCR-包和接口-07
    包包用于划分类的命名空间,使得不同包中的同名类不会冲突。Java使用文件夹存储包,文件夹名和包名一致。Java运行时系统从当前目录中、CLASSPATH变量定义的值、-classpath指定的值这三种途径寻找包。包和成员访问可访问性private无修饰符protectedpublic同一个......
  • JTCR-深入了解方法和类-05
    方法重载一个类中存在多个同名方法,这些方法的形参类型或数量不同的现象称为方法重载。同名方法的返回值类型在判断是否构成重载时不予考虑。方法重载是支持多态的方式之一。当调用同名方法时,Java根据传入方法的实参类型、数量和顺序确定某个唯一精确匹配的方法,然后调用该方法;如......
  • JTCR-继承-06
    继承基础classA{inti;voidm(){//body}}classBextendsA{intk;voidn(){//body}}没有类可以成为其自身的超类(superclass)。子类不能访问超类中的private成员。超类类型变量可以引用派生自该超类的子类对象,但是使用该变量只......
  • JTCR-介绍类-04
    类基础类表示一个新的数据类型。简单类的一般形式:classClassName{typeinstanceVariable;//...typemethod(parameterList){//方法体}//...}有些Java应用不需要main方法。定义对象new操作符在运行时动态地给一个对象分配内存并返回指向该......
  • JTCR-数据类型、变量和数组-01
    原始类型Java是强类型语言,在编译时会检查所有变量、表达式的类型是否兼容。Java为数据定义了8种原始类型(primitivetype),分为4组:整型:byte、short、int、long,表示整数。浮点数:float、double,表示小数。字符:char,表示字符集中的元素。Boolean:boolean,表示true/false值。......
  • JTCR-运算符-02
    算术运算符算术操作符不能对boolean类型使用,可以对char类型使用,因为char类型是int类型的子集。除操作符对整数使用,结果为整数。取模运算符对整数和浮点数使用,都返回余数。在某些情况下,复合赋值操作符比它的等价形式更有效率。位运算符运算符运算结果~按位......
  • POI2009SLO-Elephants
    #POI#Year2009#贪心#数学建图,对于每个环,有两种可行的方案,是这个环内部操作,需要的代价为\(mi\times(cnt-2)\),\(mi\)为这个环中的最小值,\(cnt\)为这个环的长度还可以用环外的一个点,需要的代价为\(mn\times(cnt+1)+mi\)直接贪心即可//Author:xiaruizeconsti......
  • POI2009LYZ-Ice Skates
    POI#Year2009#线段树#Hall定理考虑实际上是一个二分图匹配问题,那么这个二分图存在匹配当且仅对于\(L\)的任何子集右侧的度数和\(\geq\)左侧的然后线段树维护左侧的区间最大和//Author:xiaruizeconstintN=2e5+10;intn,m,k,d;structsegment_tree{#de......
  • POI2009GAS-Fire Extinguishers
    POI#Year2009#贪心贪心的把灭火器放到深度较小的点上,对于每个点,维护两个数组,记录距离当前点为\(x\)没有覆盖的点有\(a_x\)个,距离当前点\(y\)的灭火器有\(b_y\)个然后在每个点上,合并长度为\(len\)或者\(len-1\)的路径,因为这些路径不能延伸到父节点,所以要在这个点解决......