java终章

十一、java多线程机制

Java提供了内置的多线程支持。一条线程指的是进程中一个单一顺序的控制流一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

在Java中,有四种主要的实现多线程的方式:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。

生命周期:

Java线程的生命周期包括五个状态:新建状态 (New)、就绪状态 (Runnable)、运行状态 (Running)、阻塞状态 (Blocked)和死亡状态 (Terminated)。

- **新建状态**:当程序使用new关键字创建了一个线程之后,该线程就处于一个新建状态。
- **就绪状态**:当线程对象调用了start()方法之后,该线程处于就绪状态。处于这个状态的线程并没有开始运行,它只是表示该线程可以运行了。
- **运行状态**:如果就绪状态的线程获取CPU资源,它就可以执行run()方法,此时线程便处于运行状态。
- **阻塞状态**:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
- **死亡状态**:线程会以以下三种方式之一结束,结束后就处于死亡状态: run()方法执行完成,线程正常结束;线程抛出一个未捕获的Exception或Error;直接调用该线程的stop()方法来结束该线程。

下面是一个简单的例子,它演示了一个线程在其生命周期中经历的不同状态:
public class ThreadStateExample {
    public static void main(String[] args) throws InterruptedException {
        // 新建状态
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 阻塞状态
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println("新建状态: " + thread.getState());

        // 就绪状态
        thread.start();
        System.out.println("就绪状态: " + thread.getState());

        // 运行状态
        Thread.sleep(100);
        System.out.println("运行状态: " + thread.getState());

        // 阻塞状态
        Thread.sleep(1000);
        System.out.println("阻塞状态: " + thread.getState());

        // 死亡状态
        Thread.sleep(3000);
        System.out.println("死亡状态: " + thread.getState());
    }
}

这个程序创建了一个新线程,该线程在其run()方法中调用了Thread.sleep()方法,使其进入阻塞状态。在主线程中,我们使用Thread.sleep()方法来暂停主线程的执行,以便能够在不同时间点观察新线程的状态。

当你运行这个程序时,你会看到类似下面的输出:


新建状态: NEW
就绪状态: RUNNABLE
运行状态: TIMED_WAITING
阻塞状态: TIMED_WAITING
死亡状态: TERMINATED

`Thread t = Thread.currentThread()` 这行代码的作用是获取当前正在执行的线程对象,并将其赋值给变量`t`。

`Thread.currentThread()` 是一个静态方法,它返回对当前正在执行的线程对象的引用。这个方法常用于获取当前线程的状态或者名称等信息。

例如,下面的代码演示了如何使用 `currentThread()` 方法来获取当前线程的名称:
public class CurrentThreadExample {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        System.out.println("当前线程: " + t.getName());
    }
}

当你运行这个程序时,它会输出类似下面的内容:
当前线程: main

垃圾实体:

Thread thread = new Thread(target);

thread.start();

然后再一次创建thread = new Thread(target);那么上面那个就会变成垃圾实体。又因为垃圾实体还在运行状态,它仍会继续工作。

线程同步:

线程同步是指在多线程环境下,对共享资源的访问进行协调,以防止多个线程同时访问共享资源导致数据不一致的问题。Java提供了多种同步机制来实现线程同步,包括`synchronized`关键字、`Lock`接口及其实现类、`Semaphore`类等。

下面是一个简单的例子,它演示了如何使用`synchronized`关键字来实现线程同步:
 

public class Bank implements Runnable {

	int money = 200;

	public static void main(String args[]) {
		Bank bank = new Bank();
		bank.setMoney(200);
		Thread accountant, // 会计
		cashier; // 出纳
		accountant = new Thread(bank);
		cashier = new Thread(bank);
		accountant.setName("会计");
		cashier.setName("出纳");
		accountant.start();
		cashier.start();
	}

	public void setMoney(int n) {
		money = n;
	}

	public void run() {
		if (Thread.currentThread().getName().equals("会计"))
			saveOrTake(300);
		else if (Thread.currentThread().getName().equals("出纳"))
			saveOrTake(150);
		;
	}

//synchronized 这个同步类型不能少,因为运行有两个进程,如果没有同步机制,就会造成抢占。
	public  synchronized void saveOrTake(int amount) { // 存取方法
		if (Thread.currentThread().getName().equals("会计")) {
			for (int i = 1; i <= 3; i++) {
				money = money + amount / 3; // 每存入amount/3,稍歇一下
				System.out.println(Thread.currentThread().getName() + "存入"
						+ amount / 3 + ",帐上有" + money + "万,休息一会再存");
				try {
					Thread.sleep(1000); // 这时出纳仍不能使用saveOrTake方法
				} catch (InterruptedException e) {
				}
			}
		} else if (Thread.currentThread().getName().equals("出纳")) {
			for (int i = 1; i <= 3; i++) { // 出纳使用存取方法取出60
				money = money - amount / 3; // 每取出amount/3,稍歇一下
				System.out.println(Thread.currentThread().getName() + "取出"
						+ amount / 3 + "帐上有" + money + "万,休息一会再取");
				try {
					Thread.sleep(1000); // 这时会计仍不能使用saveOrTake方法
				} catch (InterruptedException e) {
				}
			}
		}
	}

}

 等待wait(),只存在同步机制里。

同步机制,也需要一个很重要的思想:等待wait()   和   通知等待结束notify()

public class TicketHouse implements Runnable {

	public static void main(String args[]) {
		TicketHouse officer = new TicketHouse();
		Thread zhangfei, likui;
		zhangfei = new Thread(officer);
		zhangfei.setName("张飞");
		likui = new Thread(officer);
		likui.setName("李逵");
		zhangfei.start();
		likui.start();
	}

	int fiveAmount = 2, tenAmount = 0, twentyAmount = 0;

	public void run() {
		if (Thread.currentThread().getName().equals("张飞")) {
			saleTicket(20);
		} else if (Thread.currentThread().getName().equals("李逵")) {
			saleTicket(5);
		}
	}

	private synchronized void saleTicket(int money) {
		if (money == 5) { // 如果使用该方法的线程传递的参数是5,就不用等待
			fiveAmount = fiveAmount + 1;
			System.out.println("给" + Thread.currentThread().getName() + "入场卷,"
					+ Thread.currentThread().getName() + "的钱正好");
		} else if (money == 20) {
			while (fiveAmount < 3) {
				try {
					System.out.println("\n" + Thread.currentThread().getName()
							+ "靠边等...");
					wait(); // 如果使用该方法的线程传递的参数是20须等待
					System.out.println("\n" + Thread.currentThread().getName()
							+ "继续买票");
				} catch (InterruptedException e) {
				}
			}
			fiveAmount = fiveAmount - 3;
			twentyAmount = twentyAmount + 1;
			System.out.println("给" + Thread.currentThread().getName() + "入场卷,"
					+ Thread.currentThread().getName() + "给20,找赎15元");
		}
		notifyAll();
	}
}

线程联合:join方法

大概就是有两个线程A,B,在A线程里面使用线程B对象的join方法,则会从这里转到线程B去运行线程B,而A等待线程B执行结束。

和wait有点像,但不一样。它不需要使用同步机制,就能实现等待。

GUI线程:

GUI线程(Graphical User Interface Thread)是指专门用来处理图形用户界面的线程。它负责监听用户在图形用户界面上的操作,并对这些操作进行响应。在许多图形用户界面应用程序中,GUI线程通常是主线程。

GUI线程负责维护消息队列接收和分发来自操作系统或其他应用程序的消息。例如,当用户点击鼠标或按下键盘上的某个键时,操作系统会将相应的消息发送到应用程序的消息队列中。GUI线程会从消息队列中取出这些消息,并将它们分发给相应的窗口或控件进行处理。

例如:打字母游戏,使用线程:计时操作。监听:输入字母。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class WindowTyped extends JFrame implements ActionListener, Runnable {

	public static void main(String args[]) {
		WindowTyped win = new WindowTyped();
		win.setTitle("打字母游戏");
		win.setSleepTime(3000);
	}

	JTextField inputLetter;
	Thread giveLetter; // 负责给出字母
	JLabel showLetter, showScore;
	int sleepTime, score;
	Color c;

	WindowTyped() {
		setLayout(new FlowLayout());
		giveLetter = new Thread(this);
		inputLetter = new JTextField(6);
		showLetter = new JLabel(" ", JLabel.CENTER);
		showScore = new JLabel("分数:");
		showLetter.setFont(new Font("Arial", Font.BOLD, 22));
		add(new JLabel("显示字母:"));
		add(showLetter);
		add(new JLabel("输入所显示的字母(回车)"));
		add(inputLetter);
		add(showScore);
		inputLetter.addActionListener(this);
		setBounds(100, 100, 400, 280);
		setVisible(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		giveLetter.start(); // 在AWT-Windows线程中启动giveLetter线程
	}

	public void run() {
		char c = 'a';
		while (true) {
			showLetter.setText("" + c + " ");
			validate();
			c = (char) (c + 1);
			if (c > 'z')
				c = 'a';
			try {
				Thread.sleep(sleepTime);
			} catch (InterruptedException e) {
			}
		}
	}

	public void setSleepTime(int n) {
		sleepTime = n;
	}

	public void actionPerformed(ActionEvent e) {
		String s = showLetter.getText().trim();
		String letter = inputLetter.getText().trim();
		if (s.equals(letter)) {
			score++;
			showScore.setText("得分" + score);
			inputLetter.setText(null);
			validate();
			//如果没有结束休眠操作,那么输出回车,字母不会发生改变
			giveLetter.interrupt(); // 吵醒休眠的线程,以便加快出字母的速度
		}
	}
}

计时器:Timer类  

应该不属于线程,只是有点像,

应用举例:记时答题小程序

`FileReader` 类是 Java 中用于读取字符文件的便捷类。它继承自 `InputStreamReader` 类,可以将文件中的字节转换为字符。`FileReader` 类的构造函数接受一个 `File` 对象或一个文件名字符串作为参数,用于指定要读取的文件。

`BufferedReader` 类是 Java 中用于读取文本的缓冲字符输入流。它继承自 `Reader` 类,可以将文本从字符输入流中高效地读取到字符数组中。`BufferedReader` 类的构造函数接受一个 `Reader` 对象作为参数,可以使用 `FileReader` 对象来创建 `BufferedReader` 对象。

下面是一个简单的例子,它演示了如何使用 `FileReader` 和 `BufferedReader` 类来读取文本文件中的内容:
import java.io.*;

public class FileReaderExample {
    public static void main(String[] args) {
        try {
            FileReader fileReader = new FileReader("example.txt");
            BufferedReader bufferedReader = new BufferedReader(fileReader);

            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }

            bufferedReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

这个程序使用 `FileReader` 类来打开名为 "example.txt" 的文本文件,并使用 `BufferedReader` 类来从文件中读取文本。当读取到文件末尾时,`readLine()` 方法会返回 `null`,循环结束。

解决文件中有中文,最后出现乱码的问题

乱码问题通常是由于编码不一致导致的。在你的例子中,`FileReader` 类使用默认的字符编码来读取文件,如果文件的编码与默认编码不同,就可能出现乱码问题。

要解决这个问题,你可以使用 `InputStreamReader` 类来代替 `FileReader` 类,并在创建 `InputStreamReader` 对象时指定文件的编码。例如,如果你的文件是以 "UTF-8" 编码保存的,你可以这样修改你的代码:
import java.io.*;

public class FileReaderExample {
    public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream("D:\\桌面\\例子代码\\chapter12\\例子14\\test.txt");     //输入流的类
            InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");          //缓冲流的类
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }

            bufferedReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个修改后的例子中,我们使用 `FileInputStream` 类来打开文件,并使用 `InputStreamReader` 类来读取文件中的字节并将它们转换为字符。在创建 `InputStreamReader` 对象时,我们指定了文件的编码为 "UTF-8"。

答题小程序代码:

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class StandardExamInTime extends JFrame implements ActionListener,
		ItemListener {

	public static void main(String args[]) {
		StandardExamInTime win = new StandardExamInTime();
		win.setTitle("限时回答问题");
		win.setTestFile(new java.io.File("D:\\桌面\\例子代码\\chapter12\\例子14\\test.txt"));
		win.setMAX(8);
	}

	File testFile;
	int MAX = 8;
	int maxTime = MAX, score = 0;
	javax.swing.Timer time; // 计时器
	JTextArea showQuesion; // 显示试题
	JCheckBox choiceA, choiceB, choiceC, choiceD;
	JLabel showScore, showTime;
	String correctAnswer; // 正确答案
	JButton reStart;
//	FileReader inOne;
	BufferedReader inTwo;

	StandardExamInTime() {
		time = new javax.swing.Timer(1000, this);
		showQuesion = new JTextArea(2, 16);
		setLayout(new FlowLayout());
		showScore = new JLabel("分数" + score);
		showTime = new JLabel(" ");
		add(showTime);
		add(new JLabel("问题:"));
		add(showQuesion);
		choiceA = new JCheckBox("A");
		choiceB = new JCheckBox("B");
		choiceC = new JCheckBox("C");
		choiceD = new JCheckBox("D");
		choiceA.addItemListener(this);
		choiceB.addItemListener(this);
		choiceC.addItemListener(this);
		choiceD.addItemListener(this);
		add(choiceA);
		add(choiceB);
		add(choiceC);
		add(choiceD);
		add(showScore);
		reStart = new JButton("再做一遍");
		reStart.setEnabled(false);
		add(reStart);
		reStart.addActionListener(this);
		setBounds(100, 100, 200, 200);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
	}

	public void setMAX(int n) {
		MAX = n;
	}

	public void setTestFile(File f) {
		testFile = f;
		score = 0;
		try {
//			inOne = new FileReader(testFile);
           //下面这两个代替一个是输入流,一个是缓存流。用来代替前面的FileReader,防止中文乱码的问题。
			FileInputStream file = new FileInputStream(testFile);   
			InputStreamReader inOne = new InputStreamReader(file,"UTF-8");

			inTwo = new BufferedReader(inOne);
			readOneQuesion();
			reStart.setEnabled(false);
		} catch (IOException exp) {
			showQuesion.setText("没有选题");
		}
	}

	public void readOneQuesion() {
		showQuesion.setText(null);
		try {
			String s = null;
			while ((s = inTwo.readLine()) != null) {
				if (!s.startsWith("-"))
					showQuesion.append("\n" + s);
				else {
					s = s.replaceAll("-", "");
					correctAnswer = s;
					break;
				}
			}
			time.start(); // 启动计时
			if (s == null) {
				inTwo.close();
				reStart.setEnabled(true);
				showQuesion.setText("题目完毕");
				time.stop();
			}
		} catch (IOException exp) {
		}
	}

	public void itemStateChanged(ItemEvent e) {
		JCheckBox box = (JCheckBox) e.getSource();
		String str = box.getText();
		boolean booOne = box.isSelected();
		boolean booTwo = str.compareToIgnoreCase(correctAnswer) == 0;
		if (booOne && booTwo) {
			score++;
			showScore.setText("分数:" + score);
			time.stop(); // 停止计时
			maxTime = MAX;
			readOneQuesion(); // 读入下一道题目
		}
		box.setSelected(false);
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == time) {
			showTime.setText("剩:" + maxTime + "秒");
			maxTime--;
			if (maxTime <= 0) {
				maxTime = MAX;
				readOneQuesion(); // 读入下一道题目
			}
		} else if (e.getSource() == reStart) {
			setTestFile(testFile);
		}
	}
}

十五、泛型与集合框架

泛型类就是<>里面的类。

泛型(Generics)是 Java 语言中一种非常重要的特性,它允许在定义类、接口和方法时使用类型参数(type parameter)。类型参数在实例化或调用时会被具体的类型替换,从而为我们提供了一种编写可重用代码的方式。

泛型的一个重要应用就是在 Java 集合框架(Collections Framework)中。集合框架为我们提供了一组用于存储和操作对象集合的类和接口。这些类和接口都支持泛型,允许我们指定集合中元素的类型。

下面是一个简单的例子,演示了如何使用泛型和集合框架来创建一个列表并向其中添加元素:
import java.util.ArrayList;
import java.util.List;

public class GenericsExample {
    public static void main(String[] args) {
        // 创建一个列表,用于存储字符串
        List<String> list = new ArrayList<>();

        // 向列表中添加元素
        list.add("apple");
        list.add("banana");
        list.add("cherry");

        // 遍历列表并打印元素
        for (String item : list) {
            System.out.println(item);
        }
    }
}

在这个例子中,我们使用 `ArrayList` 类来创建一个列表,并使用泛型语法 `<String>` 来指定列表中元素的类型为 `String`。这样,在向列表中添加元素时,编译器就会检查我们添加的元素是否为 `String` 类型,从而保证了类型安全

泛型类的使用方式:

一般格式:类<>  对象 = new 类<>();   //一般来说<>里面的类型就是泛型类型。

泛型类使用在链表中:

LinkedList<E> 泛型类。创建以链表结构存储数据。它创建的对象称为链表对象

LinkedList<String> mylist = new LinkedList<String>();

迭代器方法:Iterator<String>  iter = mylist.iterator();  mylist是一个泛型字符串。

它可以调用iter.hasNext()方法遍历mylist字符串。

泛型最大的优点就是不需要强行转换数据类型。而一般的链表,从链表提取get数据,都需要强制转换为String类型。定义泛型之后就不需要了。参考书籍P447.

排序链表:

public static sort(List<E> list)     //按升序排列

public binarySearch(List<T> list,T key,CompareTo<T> c)   //二分法的查找

堆栈:(后进先出,只在一端操作)

总是在顶端进行操作,最顶端的位置是1。Stack<>创建堆栈:

Stack<Interger> stack = new Stack<Interger>();

push()压栈:stack.push(new Integer(1));

pop()弹栈:Interger F1 = stack.pop()

empty()检查是否还有数据:

peek()获取顶端的数据,但不删除:

散列映射:

HashMap<K,V>泛型类创建的对象称作散列映射(也就是键值对)。

HashMap<String,Student> hashtable = HashMap<String,Student>();

hashtable是一个键值对数据。

例题:

英汉小字典GUI设计。

树集:

TreeSet<E>泛型类,它创建的对象叫做树集。

TreeSet<String> mytree = new TreeSet<String>();

把一个元素插入树集比插入数组或链表的效率更高。

树映射:

TreeMap<K,V>类,它的对象称为树映射。

参考书籍P457

应用举例:

使用对象流实现商品库存的录入与显示系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值