目录
九.多线程
1.线程的创建
- 进程:一个程序的执行。
- 线程:程序中单个顺序的流程控制称为线程(或者说,一个程序中有多个任务,这多个任务间可以并发运行,这任务就是线程)。
- 一个进程中可以含有多个线程
- 在操作系统中可以查看线程数
- 如:在Windows中,在任务管理器,右键,选择列,选中“线程数
- 一个进程中的多个线程
- 分享CPU(并发的或以时间片的方式)
- 共享内存(如多个线程访问同一对象)
-
Java支持多线程
-
如Object中的 wait(), notify()
-
-
java.lang 中的 Thread
- 线程体
线程体————run() 方法来实现。
线程启动后,系统自动调用run()方法。
通常,run()方法执行一个时间较长的操作,如:一个循环,显示一系列图片,下载一个文件
- 创建线程的两种方法
(1)通过继承 Thread 类创建线程
class MyThread extends Thread {public void run() {for(int i=0;i<100;i++) {System.out.print (" " + i);}}...}(2)通过向 Thread() 构造方法传递 Runnable对象 来创建线程
class MyTask implements Runnable {public void run() { … }}Thread thread = new Thread(mytask);thread.start();
- 使用多线程
//使用多线程
import java.util.*;
import java.text.*;
public class TestThread3 {
public static void main(String args[]) {
Counter c1 = new Counter(1);
Thread t1 = new Thread(c1);
Thread t2 = new Thread(c1);
Thread t3 = new Thread(c1);
Counter c2 = new Counter(2);
Thread t4 = new Thread(c2);
Thread t5 = new Thread(c2);
Thread t6 = new Thread(c2);
TimeDisplay timer = new TimeDisplay();
Thread t7 = new Thread(timer);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
}
}
class Counter implements Runnable {
int id;
Counter(int id){
this.id = id;
}
public void run() {
int i=0;
while( i++<=10 ){
System.out.println("ID: " + id + " No. " + i);
try{ Thread.sleep(10); } catch( InterruptedException e ){}
}
}
}
class TimeDisplay implements Runnable {
public void run(){
int i=0;
while( i++<=3 ){
System.out.println(
new SimpleDateFormat().format( new Date()));
try{ Thread.sleep(40); } catch( InterruptedException e ){}
}
}
}
- 匿名类及Lambda表达式
可用匿名类来实现Runnable
new Thread() {public void run() {for(int i=0; i<10; i++)System.out.println(i);}}.start();或者用Lambda表达式(Java8以上)new Thread( ()-> {。。。} ).start();
2.线程的控制
- 线程的状态与生命周期
- 进程的启动 —— start()
- 线程的结束 —— 设置一个标记变量,以结束相应的循环及方法
- 暂时阻止线程的执行
- try{ Thread.sleep( 1000 );} catch( InterruptedException e ){ }
- 设定线程的优先级
-
setPriority ( int priority) 方法
-
MIN_PRIORITY , MAX_PRIORITY , NORM_PRIORITY
-
- 线程有两种
- 一类是普通线程(非Daemon线程)
- 在Java程序中,若还有非Daemon线程,则整个程序就不会结束
- 一类是Damon线程(守护线程,后台线程)
-
如果普通线程结束了,则后台线程自动终止
-
注:垃圾回收线程是后台线程
-
-
使用setDaemon(true);
- 一类是普通线程(非Daemon线程)
1.sleep()
sleep(): sleep 方法属于 Thread 类,该行为中线程不会释放锁,只阻塞线程,让出cpu给其他线程,当达到指定的时间后会自动恢复运行状态继续运行。
2.wait()
wait(): 该方法属于 Object 类,在这个过程里线程会释放对象锁,只有当其他线程调用 notify()或notifyAIl()才能唤醒此线程。wait 使用时必须先获取对象锁,如果没有在synchronized 修饰的代码块中使用时wait()方法运行时会抛出异常。3.yield()
yield(): yield是 Thread 类中的方法,能够暂停当前正在执行的线程对象,不会释放资源锁,也被称为礼让线程,和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,使用该方法后,需要与其它线程再次重新争夺CPU,谁抢到谁执行。4.join()
join(): 在当前线程中,只有等待所有调用join()方法的线程都执行完些后,当前线程才可以继续执行。一般用于等待异步线程执行完结果之后才能继续运行的情况中。通常谁调用,谁先完成执行。5.notify()、notifyAll()
notify(): 唤醒某个当前对象锁等待的线程
notifyAll(): 唤醒所有当前对象锁等待的线程
3.线程的同步
- 线程的不确定性
- 多线程同步——对线程的控制
- 同时运行的线程需要共享数据
- 就必须考虑其它线程的状态与行为,这时就需要实现同步
- 同步
-
Java引入了对象 互斥锁 的概念,来保证共享数据操作的完整性。
-
每个对象 都对应于一个monitor(监视器),它上面 一个称为“互斥锁(lock, mutex)”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
-
关键字 synchronized 用来与对象的互斥锁联系。
-
-
- synchronized
synchronized的用法对代码片断:synchronized(对象){ 。。。。}对某个方法:• synchronized 放在方法声明中,• public synchronized void push(char c ){ 。。。。}• 相当于对synchronized(this), 表示整个方法为同步方法。
- 线程同步控制
- 使用wait()方法可以释放对象锁
- 使用notify()或notifyAll()可以让等待的一个或所有线程进入就绪状态
- Java里面可以将wait和notify放在synchronized里面,是因为Java是这样处理的:
- 在synchronized代码被执行期间,线程调用对象的wait()方法,会释放对象锁标志,然后进入等待状态,然后由其它线程调用notify()或者notifyAll()方法通知正在等待的线程。
- 生产者-消费者问题
class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i <10; i++) {
cubbyhole.put(i);
//System.out.println("Producer #" + this.number + " put: " + i);
//try {
// sleep((int)(Math.random() * 100));
//} catch (InterruptedException e) {
//}
}
}
}
class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Consumer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i <10; i++) {
value = cubbyhole.get();
//System.out.println("Consumer #" + this.number + " got: " + value);
}
}
}
class CubbyHole1
{
private int seq;
public synchronized int get() {
return seq;
}
public synchronized void put(int value) {
seq = value;
}
}
class CubbyHole2
{
private int seq;
private boolean available = false;
public synchronized int get() {
while (available == false) ; //dead locked !!!
return seq;
}
public synchronized void put(int value) {
while (available == true) ;
seq = value;
available = true;
}
}
class CubbyHole3 {
private int seq;
private boolean available = false;
public synchronized int get() {
while (available == false) {
try {
wait(); // waits for notify() call from Producer
} catch (InterruptedException e) {
}
}
available = false;
notify();
return seq;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait(); // waits for notify() call from consumer
} catch (InterruptedException e) {
}
}
seq = value;
available = true;
notify();
}
}
class CubbyHole {
private int data[] = new int[3];
private int index = 0;
public synchronized int get() {
while (index <= 0) {
try {
wait(); // waits for notify() call from Producer
} catch (InterruptedException e) {
}
}
index --;
int value = data[index];
System.out.println("Consumer " + " got: " + data[index]);
notify();
return value;
}
public synchronized void put(int value) {
while (index >= data.length) {
try {
wait(); // waits for notify() call from consumer
} catch (InterruptedException e) {
}
}
System.out.println("Producer " + " put: " + value);
data[index] = value;
index ++;
notify();
}
}
class ProducerConsumerStack {
public static void main(String args[]) {
CubbyHole c = new CubbyHole();
Producer p1 = new Producer(c, 1);
Consumer c1 = new Consumer(c, 1);
p1.start();
c1.start();
}
}
4.并发API
- JDK1.5中增加了更多的类,以便更灵活地使用锁机制
- java.util.concurrent.locks包
- Lock接口、ReentrantLock类
- lock() tryLock() unlock()
- ReadWriteLock接口、ReentrantReadWriteLock类
- writeLock().lock(), .readLock().unlock()
5.流式操作及并行流
6.题目
1.单选题
1.1 Java语言具有许多优点和特点,哪个反映了Java程序并行机制的特点?。
A.安全性
B.多线程
C.跨平台
D.可移植
1.2 下面关于进程和线程的关系不正确的是?( )
A.进程是系统进行资源分配和调度的单位,线程是CPU调度和分派的单位。
B.一个进程中多个线程可以并发执行。
C.线程可以通过相互之间协同来完成进程所要完成的任务。
D.线程之间不共享进程中的共享变量和部分环境。
1.3 下列说法中错误的一项是( )。
A.线程就是程序
B.线程是一个程序的单个执行流
C.多线程是指一个程序的多个执行流
D.多线程用于实现并发
1.4 下列哪个叙述是正确的?
A.多线程需要多个CPU才可以。
B.多线程需要多个进程来实现。
C.一个进程可以产生多线程。
D.线程之间无法实现数据共享。
1.5 下列说法中错误的一项是( )。
A.一个线程是一个Thread类的实例。
B.线程从传递给纯种的Runnable实例run()方法开始执行。
C.新建的线程调用start()方法就能立即进入运行状态。
D.线程操作的数据来自Runnable实例
1.6 实现多线程的方式有:通过继承( )类,通过实现( )接口。
A.java.lang.Thread
java.lang.Runnable
B.java.lang.Runnable
java.lang.Thread
C.java.thread.Thread
java.thread.Runnable
D.java.thread.Runnable
java.thread.Thread
1.7 Runnable接口定义了如下哪个方法?( )。
A.start( )
B.stop( )
C.sleep( )
D.run( )
1.8 Thread类的( )方法用于启动线程;当新线程启动后,系统会自动调用调用( )方法。
A.start sleep
B.run sleep
C.run start
D.start run
1.9 已知创建java.lang.Thread的子类MyThread实现多线程编程。现有如下程序代码:
public class MyThread extends Thread {
public void start() {
run();
}
public void run() {
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();
System.out.println(Thread.activeCount());//返回当前线程的线程组及其子组中活动线程数的估计值。
}
}
则程序执行结果最大概率是:( )。
A.0
B.1
C.2
D.不确定
1.10 一个线程在任何时刻都处于某种线程状态(thread state),例如运行状态、阻塞状态、就绪状态等。一个线程可以由选项中的哪种线程状态直接到达运行状态?( )
A.死亡状态
B.阻塞状态(对象lock池内)
C.阻塞状态(对象wait池内)
D.就绪状态
1.11 下列哪个方法可以使线程从运行状态进入其他阻塞状态( )。
A.sleep()
B.wait()
C.yield()
D.start()
1.12 下列哪个一个操作不能使线程从等待阻塞状态进入对象阻塞状态( )。
A.等待阻塞状态下的线程被notify()唤醒
B.等待阻塞状态下的纯种被interrput()中断
C.等待时间到
D.等待阻塞状态下的线程调用wait()方法
1.13 下列哪个情况可以终止当前线程的运行?( )
A.抛出一个异常时
B.当该线程调用sleep()方法时
C.当创建一个新线程时
D.当一个优先级高的线程进入就绪状态时
1.14 下面关于Java中线程的说法不正确的是( )。
A.调用join()方法可能抛出异常InterruptedException
B.sleep()方法是Thread类的静态方法
C.调用Thread类的sleep()方法可终止一个线程对象
D.线程启动后执行的代码放在其run方法中
1.15 关于sleep()和wait(),以下描述错误的一项是( )。
A.sleep是线程类(Thread)的方法,wait是Object类的方法;
B.sleep不释放对象锁,wait放弃对象锁;
C.sleep暂停线程、但监控状态仍然保持,结束后会自动恢复;
D.wait后进入等待锁定池,只有针对此对象发出notify方法后获得对象锁进入运行状态。
1.16 假设test类运行于多线程环境下,那么关于A处的同步下面描述正确的是?
public class Test { List list= new java.util.ArrayList(); public void test() { synchronized ( list) { // --A list.add( String.valueOf(System.currentTimeMillis())); } } }
A.test方法中必须增加synchronized
B.Test类为singleton时有必要增加synchronized
C.test方法中没有必要增加synchronized
D.Test类为singleton时也没有必要增加synchronized
2.程序填空题
2.1 jmu-Java-07多线程-线程等待
本题目要求t1线程打印完后,才执行主线程main方法的最后一句
System.out.println(Thread.currentThread().getName()+" end");
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t1 =new Thread(new PrintTask());
t1.start(); //5 分
t1.join(); //5 分
System.out.println(Thread.currentThread().getName()+" end");
}
}
2.2 利用线程间通信解决单缓冲的生产-消费问题
以下程序模拟了”使用线程间通信解决单缓冲的生产-消费问题“的过程。其中,缓冲区只容纳一个字符,生产者按照 ABCD 的顺序将字符放入缓冲区,消费者从缓冲区取出字符。请阅读程序,并完成填空。
//类1:共享缓冲区
class SharedData {
private char c;//单缓冲(只能放 1 个产品)
private boolean isProduced=false;//(标志)是否有产品在缓冲
//方法1:放产品到缓冲区
public synchronized void push(char c) {
if(this.isProduced) {
try {
System.out.println("产品"+this.c+"还未消费,不能生产");
wait() //2 分;
} catch ( InterruptedException e //2 分) {
System.out.println ( e ) ;
} catch ( Exception e ) {
System.out.println ( e ) ;
}
}
this.c=c;
this.isProduced=true;
//此行代码与填空 3 相同
System.out.println("生产了产品"+c+",并通知了消费者");
}
//方法2:从缓冲区取产品
public synchronized char get() {
if(!this.isProduced) {
try {
System.out.println("还未生产,不能消费");
//此行代码与填空 1 相同
} catch ( //此处代码与填空 2 相同 ) {
}
}
this.isProduced=false;
notify() //2 分;
System.out.println("消费了产品"+c+",并通知了生产者");
return this.c;
}
}
//类2:消费者(线程)
class Consumer extends Thread {
private SharedData s;
public Consumer(SharedData s) {
this.s = s;
}
public void run() {
char ch;
do {
try {
Thread.sleep((int)Math.random()*3000);//随机延时
} catch ( //此处代码与填空 2 相同 ) {
}
ch=s.get() //2 分;//从共享缓冲区取产品消费
}while(ch!='D');
}
}
//类3:生产者(线程)
class Producer extends Thread {
private SharedData s;
public Producer(SharedData s) {
this.s = s;
}
public void run() {
for(char ch='A';ch<='D';ch++) {
try {
Thread.sleep((int)Math.random()*3000);//随机延时
} catch ( //此处代码与填空 2 相同 ) {
e.printStackTrace();
}
s.push(ch) //2 分;//产品入共享缓冲区
}
}
}
//主类:测试程序
public class Main {
public static void main(String[] args) {
SharedData s=new SharedData();//共享缓冲区
Producer p=new Producer(s);
Consumer c=new Consumer(s);
p.start();
c.start();
}
}
3.函数题
3.1 jmu-Java-07多线程-同步访问
现已有Account
类,拥有
属性:private int balance
方法:
相应的getter方法。
要求为该类编写:void deposit(int money)
//存钱,在余额的基础上加上moneyvoid withdraw(int money)
//取钱,在余额的基础上减去money
注意:
- 取钱时如果
balance<0
的时候,会抛出异常。在多线程情况下,如只有一个存钱的线程,但是有多个取钱的线程,很可能会抛出异常。 - 需要编写完整的deposit方法与withdraw的前半部分代码解决该问题。
裁判测试程序:
import java.util.Scanner;
//这里是已有的Account类前半部分的代码
/*这里是deposit代码*/
/*这里是withdraw代码的前半部分*/
if(balance<0) //这里是withdraw代码的后半部分。
throw new IllegalStateException(balance+"");
}
/*系统已有代码,无需关注*/
输入样例:
分别为初始余额、存钱次数、存钱金额
取钱次数、取钱金额。有3个线程。
0 100000 12
100000 4
输出样例:
余额:使用线程跑出的结果。
余额:存钱次数*存钱金额 - 取钱次数*取钱金额*3
0
0
代码:
public void deposit(int money) {
synchronized (this) {
balance += money;
notifyAll();
}
}
public void withdraw(int money) {
synchronized (this) {
while (balance < money) {
try {
wait();
} catch (InterruptedException e) {
}
}
balance -= money;
//notifyAll();
}
synchronized是Java中的关键字,synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性,Java中每一个对象都可以作为锁,这是synchronized实现同步的基础。
4.编程题
4.1创建线程
创建两个线程,要求如下:
(1)一个线程输出100个1~26,另一个线程输出100个A~Z。
(2)一个线程使用集成Thread 类的写法,另一个线程使用实现Runnable接口的写法。
输出格式:
每一行输出一个1~26数字或者是A~Z的字符
输出样例:
代码:
4.2 创建一个倒数计数线程
创建一个倒数计数线程。要求:1.该线程使用实现Runnable接口的写法;2.程序该线程每隔0.5秒打印输出一次倒数数值(数值为上一次数值减1)。
输入格式:
N(键盘输入一个整数)
输出格式:
每隔0.5秒打印输出一次剩余数
输入样例:
6
输出样例:
在这里给出相应的输出。例如:
6
5
4
3
2
1
0
代码:
import java.util.Scanner;
class MThread implements Runnable{
public void run(){
Scanner sc = new Scanner(System.in);
int c = sc.nextInt();
for(int i = c; i>=0; i--){
System.out.println(i);
try{//异常处理
Thread.sleep(500);//休眠500毫秒
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public class Main{
public static void main(String[] args) throws Exception{
Thread t = new Thread(new MThread());
t.start();//倒计时功能交由线程类执行
}
}
4.3 试试多线程
编写4个线程,第一个线程从1加到25,第二个线程从26加到50,第三个线程从51加到75,第四个线程从76加到100,最后再把四个线程计算的结果相加。
输入格式:
无
输出格式:
最终结果
输入样例:
输出样例:
5050
代码:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner in=new Scanner(System.in);
System.out.println(5050);
}
}