Java PTA(11)——多线程

目录

九.多线程

1.线程的创建

2.线程的控制

3.线程的同步

4.并发API

5.流式操作及并行流

6.题目

1.单选题

2.程序填空题

3.函数题

4.编程题

4.1创建线程

4.2 创建一个倒数计数线程

4.3 试试多线程


九.多线程

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);

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

并发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) //存钱,在余额的基础上加上money
void withdraw(int money) //取钱,在余额的基础上减去money

注意:

  1. 取钱时如果balance<0的时候,会抛出异常。在多线程情况下,如只有一个存钱的线程,但是有多个取钱的线程,很可能会抛出异常。
  2. 需要编写完整的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);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值