重点
说明:其中Running表示运行状态,Runnable表示就绪状态(万事俱备,只欠CPU),Blocked表示阻塞状态,阻塞状态又有多种情况,可能是因为调用wait()方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用了sleep()方法或join()方法等待休眠或其他线程结束,或是因为发生了I/O中断。
每日要点
线程的三种状态(五种状态)
- 运行态(Running) ---> yield() ---> 就绪态
- 就绪态(Ready/Runnable)
- 阻塞态(Blocked)
- sleep()/IO中断/join()
- wait() ---> 等待池 ---> notify()/notifyAll() --->等锁池
- synchronized ---> 等锁池 --->抢到锁 ---> 就绪态
例子1:线程上传和下载 两种
class DownloadHandler implements Runnable {
@Override
public void run() {
Test01.download();
}
}
class Test01 {
public static void main(String[] args) {
Thread t1 = new Thread(new DownloadHandler());
// t1.setPriority(Thread.MAX_PRIORITY);
// t1.setDaemon(true);
t1.start();
Thread t2 = new Thread(() -> {
upload();
});
// t2.setPriority(Thread.MIN_PRIORITY);
t2.start();
for (int i = 1; i <= 1000; i++) {
System.out.println("hello");
}
}
public static void download() {
try {
Thread.sleep(6000);
System.out.println("下载完成!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void upload() {
try {
Thread.sleep(3000);
System.out.println("上传完成!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程池
创建和释放一个线程都会造成很大的系统开销
所以手动创建Thread对象并调用start方法并不适合服务器编程
通常做多线程的编程可以使用线程池技术来减少频繁创建和释放线程带来的开销
线程池是典型的用空间换时间的策略
线程池
private ExecutorService service = Executors.newFixedThreadPool(5);
线程同步
synchronized有两种用法
- 放在方法前面该方法只允许抢占到对象锁的线程进行执行
- 构造一个同步代码块只允许抢占到对象锁的线程进行执行
没有抢占到对象锁的线程在系统自动维护的等锁池中等待
如果抢到锁的线程释放了对象锁那么这些等锁的线程就抢锁
谁抢到锁谁就进入同步代码块中执行没有抢到锁的继续等待
基于synchronized关键字的锁机制是可重入的锁机制
使用Java5以后的锁机制来解决同步问题
Lock - ReentrantLock
例子:银行案例 100个人往一个账号存钱
class Account {
private double balance;
private Lock lock = new ReentrantLock();
public double getBalance() {
return balance;
}
// 使用Java5以后的锁机制来解决同步问题 - Lock - ReentrantLock
public /*synchronized*/ void deposit(double money) {
lock.lock();
// System.out.println(Thread.currentThread().getName());
double newBalance = balance + money;
// 用休眠来模拟受理存款业务需要一段时间
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
balance = newBalance ;
lock.unlock();
}
}
class AddMoneyHandler implements Runnable {
private Account account;
private double money;
public AddMoneyHandler(Account account, double money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
// synchronized有两种用法
// - 放在方法前面该方法只允许抢占到对象锁的线程进行执行
// - 构造一个同步代码块只允许抢占到对象锁的线程进行执行
// 没有抢占到对象锁的线程在系统自动维护的等锁池中等待
// 如果抢到锁的线程释放了对象锁那么这些等锁的线程就抢锁
// 谁抢到锁谁就进入同步代码块中执行没有抢到锁的继续等待
// 基于synchronized关键字的锁机制是可重入的锁机制
// synchronized (account) {
account.deposit(money);
// }
}
}
class Test03 {
public static void main(String[] args) throws Exception{
// List<Thread> list = new ArrayList<>();
ExecutorService service = Executors.newFixedThreadPool(10);
Account mainAccount = new Account();
for (int i = 0; i < 100; i++) {
service.execute(new AddMoneyHandler(mainAccount, 100));
/* Thread t = new Thread(new AddMoneyHandler(mainAccount, 100));
// t.setName("存" + i);
list.add(t);
t.start();*/
}
service.shutdown();
while (! service.isTerminated()) {
}
/* for (Thread thread : list) {
// 线程对象的join()方法是一个阻塞式的方法表示等待线程结束
thread.join();
}*/
System.out.println("账户余额: ¥" + mainAccount.getBalance());
}
}
注意:
- 线程对象的join()方法是一个阻塞式的方法表示等待线程结束
thread.join();
- 如果可能的话尽量不要使用隐式的(synchronized关键字)或者显式的(Lock接口)的锁机制
因为它们会将并行(并发)的程序变成串行执行的程序 这样会让程序的性能受到严重的影响 - 一种常见的解决方案是为每个线程创建资源的副本 每个线程操作自己的副本
- 二不是同步操作同一个资源
这又是典型的使用空间置换时间的策略
例子:
class Test04 {
// 如果可能的话尽量不要使用隐式的(synchronized关键字)或者显式的(Lock接口)的锁机制
// 因为它们会将并行(并发)的程序变成串行执行的程序 这样会让程序的性能受到严重的影响
// 一种常见的解决方案是为每个线程创建资源的副本 每个线程操作自己的副本 二不是同步操作同一个资源
// 这又是典型的使用空间置换时间的策略
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(5);
// ArrayList的线程是不安全的 (因为它的方法没有加synchronized保护)
// List<String> list = new ArrayList<>();
// 装潢模式的应用 - 将线程不安全的容器包装成线程安全的容器
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 5; i++) {
service.execute(() -> {
for (int j = 0; j < 100000; j++) {
list.add("apple");
}
});
}
service.shutdown();
while (! service.isTerminated()) {
}
System.out.println(list.size());
}
}
例子2:按钮
Thread类有两个静态方法可以让正在执行的线程放弃对CPU的占用
- sleep(mills)
- yield
public class MyFrame extends JFrame implements ActionListener {
private static final long serialVersionUID = -8705543850112480600L;
private JButton uploadButton, downloadButton, calcButton, aboutButton;
private Thread downloadThread, uploadThread;
public MyFrame() {
this.setTitle("测试");
this.setSize(520, 300);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLayout(null);
uploadButton = new JButton("上传");
uploadButton.setBounds(40, 150, 80, 40);
uploadButton.addActionListener(this);
this.add(uploadButton);
downloadButton = new JButton("下载");
downloadButton.setBounds(160, 150, 80, 40);
downloadButton.addActionListener(this);
this.add(downloadButton);
calcButton = new JButton("计算");
calcButton.setBounds(280, 150, 80, 40);
calcButton.addActionListener(this);
// 一开始禁用"计算"按钮
calcButton.setEnabled(false);
this.add(calcButton);
aboutButton = new JButton("关于");
aboutButton.setBounds(400, 150, 80, 40);
aboutButton.addActionListener(this);
this.add(aboutButton);
}
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == aboutButton) {
JOptionPane.showMessageDialog(null, "欢迎使用Sabc软件\n作者: Kygo");
}
else if (source == uploadButton) {
uploadButton.setEnabled(false);
uploadButton.setText("上传中");
uploadThread = new Thread(() -> {
uploadDate();
uploadButton.setText("上传");
uploadButton.setEnabled(true);
});
uploadThread.start();
new Thread(() -> {
tryToEnableCalcButton();
}).start();
}
else if (source == downloadButton) {
downloadButton.setEnabled(false);
downloadButton.setText("下载中");
downloadThread = new Thread(() -> {
downloadData();
downloadButton.setText("下载");
downloadButton.setEnabled(true);
});
downloadThread.start();
new Thread(() -> {
tryToEnableCalcButton();
}).start();
}
else if (source == calcButton) {
}
}
private void tryToEnableCalcButton() {
if (uploadThread != null && downloadThread != null) {
try {
downloadThread.join();
uploadThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
calcButton.setEnabled(true);
}
}
private void uploadDate() {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void downloadData() {
try {
// Thread类有两个静态方法可以让正在执行的线程放弃对CPU的占用
// - sleep(mills)
// - yield
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new MyFrame().setVisible(true);
}
}
昨日作业讲解
作业:做一个窗口,实现5辆车多线程跑
class Car {
public static final int W = 70;
public static final int H = 30;
private int x;
private int y;
private Color color;
public Car(int x, int y) {
this.x = x;
this.y = y;
this.color = randomColor();
}
public int getX() {
return x;
}
public void run() {
int currentSpeed = (int) (Math.random() * 9 + 2);
x += currentSpeed;
}
private Color randomColor() {
return new Color((int) (Math.random() * 256),
(int) (Math.random() * 256), (int) (Math.random() * 256));
}
public void draw(Graphics g) {
g.setColor(color);
g.fillRect(x, y, W, H);
}
}
class CarRacingFrame extends JFrame {
private static final long serialVersionUID = -8091607300191490412L;
private static final int START = 100;
private static final int END = 1150;
private BufferedImage image = new BufferedImage(1200, 600, 1);
private List<Car> carsList = new ArrayList<>();
// 线程池
private ExecutorService service = Executors.newFixedThreadPool(5);
public CarRacingFrame() {
this.setTitle("赛车游戏");
this.setSize(1200, 600);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
for (int i = 0; i < 5; i++) {
Car car = new Car(START - Car.W, 50 + 120 * i);
carsList.add(car);
/* new Thread(() -> {
while (car.getX() + Car.W < END) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
car.run();
repaint();
}
}).start();*/
// 创建和释放一个线程都会造成很大的系统开销
// 所以手动创建Thread对象并调用start方法并不适合服务器编程
// 通常做多线程的编程可以使用线程池技术来减少频繁创建和释放线程带来的开销
// 线程池是典型的用空间换时间的策略
service.execute(() -> {
while (car.getX() + Car.W < END) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
car.run();
repaint();
}
});
}
service.shutdown();
}
@Override
public void paint(Graphics g) {
Graphics otherGraphics = image.getGraphics();
super.paint(otherGraphics);
((Graphics2D) otherGraphics).setStroke(new BasicStroke(4));
otherGraphics.setColor(Color.BLACK);
otherGraphics.drawLine(START, 0, START, 600);
otherGraphics.drawLine(END, 0, END, 600);
for (Car car : carsList) {
car.draw(otherGraphics);
}
g.drawImage(image, 0, 0, null);
}
}
class Test02 {
public static void main(String[] args) {
new CarRacingFrame().setVisible(true);
}
}