文章目录
前言
跟着导师学Java,今天无意中发现了java多进程和多线程的语法语义,来做一个多线程带锁的图书馆流程小程序
1多进程和多线程的概念
1.1多进程
进程是程序在计算机上的一次执行活动。当运行一个程序,就启动了一个进程。凡是用于完成操作系统的各种功能的进程就是系统进程,而所有非系统启动的进程都是用户进程。
而进程A和进程B的内存独立不共享,比如:魔兽游戏是一个进程,网易云音乐是一个进程
这两个进程是独立的,不共享资源。
1.2多线程
进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是独立运行于进程之中的子任务。是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。
进程和线程之间的关系:一个进程下包含 N 个线程。
举例说明:玩英雄联盟的时候,打开客户端便启动了许多个线程:排队队列线程、好友聊天线程、正在支付线程。在英雄联盟这一个进程之下便启动了 N 个线程。
2.Runnable任务类和Thread线程类
刚才我们知道线程是运算调度时最小的单位,而在Java中,Thread类就是线程类,它的作用是可以将调动Runnable类的实例对象,任务就是对象。为了创建任务,必须首先为任务定义一个实现 Runnable 接口的类Runnable接口非常简单,它只包含一个run方法。需要实现这个方法来告诉系统线程将如何运行。如图:
一旦定义了一个 PrintStrinng,就可以用它的构造方法创建一个任务。例如
Runnable printgreet = new PrintString("你好啊",50);
任务必须在线程中执行。Thread类包括创建线程的构造方法以及控制线程的方法。使用下面的语句创建任务的线程:调用start(方法告诉Java 虚拟机该线程准备运行,如下所示,会打印50次传入的字符串:
Thread thread = new Thread(printsee);
thread.start();
注意:每个线程可以处于5种状态之一:新建、就绪、阻塞、运行、结束
3.线程池
Thread为每个任务开始一个新线程可能会限制吞吐量并且造成性能降低。可以通过线程池是管理并发执行任务个数。Java提供Executor接口来执行线程池中的任务,提供 ExecutorService接口来管理和控制任务。
使用线程池来运行任务对象时,可以制定要运行的线程数量,并且可以运行完后关闭线程池:
ExecutorService executor = Executors.newFixedThreadPool(2); //指定线程池数量
executor.execute(new returnTask()); //添加线程
executor.execute(new boorroTask()); // 添加线程
//将线程池结束掉,释放资源
executor.shutdown();
4.线程同步
4.1异步编程和同步编程
异步编程模型:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁。异步就是并发。
那什么时候数据在多线程并发的环境下会存在安全问题呢?
满足三个条件:
-
条件1:多线程并发。
-
条件2:有共享数据。
-
条件3:共享数据有修改的行为。
比如存钱程序,用线程池创建1000个存钱的线程,逻辑如下:直接运行所有存钱线程,线程的代码是获取账户上的余额,然后把余额加1,但是所有线程同时并发运行,导致大家同时读到余额初始值并加1,线程池结束后发现余额的钱是2,相当于只运行了一次,任务2覆盖了任务1的结果。任务1和任务2以一种会引起冲突的方式访问一个公共资源,称为竞争状态(race condition) 。如果一个类的对象在多线程序中没有导致竞争状态,则称这样的类为线程安全的((thread-safe)。所以图中Account类不是线程安全的。
public class AccountWithoutSync {
private static Account account = new Account();
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
// 创建100个存钱线程
for (int i = 0; i < 100; i++) {
executor.execute(new AddAPennyTask());
}
executor.shutdown();
// 等待线程完成
while (!executor.isTerminated()) {
}
System.out.println("现在余额: " + account.getBalance());
}
// 建一个存钱任务类,调用存钱类Account,每次放1块钱
private static class AddAPennyTask implements Runnable {
public void run() {
account.deposit(1);
}
}
// 存钱类Account,每次放1块钱
private static class Account {
private int balance = 0;
public int getBalance() {
return balance;
}
public void deposit(int amount) {
int newBalance = balance + amount;
try {
Thread.sleep(5);
}
catch (InterruptedException ex) {
}
balance = newBalance;
}
}
}
5.显性同步锁
java中有几种方法,但是我用到的是显性同步锁,使用方法为:先创建一把锁,然后设置开锁条件,在业务类运行其中一个同步方法时先上锁,运行结束就解锁,其他同步方法在此期间不能进行操作,具体细节后续会再度补充,就先大概了解到这吧。
private static Lock lock = new ReentrantLock(); //创建一把锁
private static Condition newDeposit = lock.newCondition(); //创建解锁条件
6.完整代码
public class TestThread {
private static library library = new library();
public static void main(String[] args) {
System.out.println("-------------------");
System.out.println("| 图书馆借书还书流程 |");
System.out.println("-------------------");
//新建线程池,把还书任务和借书任务放进去
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new returnTask());
executor.execute(new boorroTask());
//将线程池结束掉,释放资源
executor.shutdown();
}
//新建任务类returnTask,此任务的run方法可执行静态类图书馆的还书方法,每次随机还1-5本书,运行100次
public static class returnTask implements Runnable{
@Override
public void run() {
int j = 0;
try{
do {
library.returnBook((int)(Math.random()*5)+1); //每次随机还书1-5本
Thread.sleep(1000); //每次还书休眠1秒钟再继续
j++;
//限定每天只有一百人来借书或者还书
}
while(j<100);
}
catch(InterruptedException ex){
ex.printStackTrace(); //有线程阻塞异常时不会中断程序
}
}
}
//新建任务类boorroTask,此任务的run方法可执行静态类图书馆的借书方法,每次随机借1-5本书,运行100次
public static class boorroTask implements Runnable{
public void run(){
int i=0;
try {
do {
library.borrowBooks((int)(Math.random()*5)+1);
Thread.sleep(1000); //每次借完书休眠1秒钟
i++;
//限定每天只有一百人来借书或者还书
}
while (i<100);
}
catch(InterruptedException ex){
ex.printStackTrace(); //有线程阻塞异常时不会中断程序
}
}
}
//定义静态类:图书馆类library,里面有变量margin表示书籍余量,一个借书方法,一个还书方法
public static class library{
private static Lock lock = new ReentrantLock(); //创建一把锁
private static Condition newDeposit = lock.newCondition(); //创建解锁条件
private int margin; //书籍余量
public int getMargin() {
return margin;
} //获取书籍余量
//定义borrowBooks方法,此方法用于借书
public void borrowBooks(int num){
lock.lock(); //先上一把锁,防止同时借书的事情发生
try {
while (margin < num) { //如果书籍余量小于要借书的余量就执行下面代码
System.out.println();
System.out.println(" ");
System.out.println("\t\t\t 什么?你想要借"+num+"本!!!"); //打印出来,表示无能为力,先睡会觉先
System.out.println("\t\t\t\t\t\t没有这么多书,先等一会吧~");
System.out.println(" ");
System.out.println();
newDeposit.await(); //把当前线程阻塞掉,不借那么快了
}
margin -= num; //如果余量大于要借测书,就把书借出去,余量减去借书数量
System.out.println();
System.out.println("****************************************************************");
System.out.println("\t\t\t 借走了"+num+"本书"+"\t\t\t 还剩下"
+margin+"本书");
System.out.println("****************************************************************");
System.out.println();
}
catch (InterruptedException ex){
ex.printStackTrace(); //有线程阻塞异常时不会中断程序
}
finally {
lock.unlock(); //无论有没有借书成功,都把锁打开
}
}
//定义returnBook方法,此方法用于还书
public void returnBook(int num){
lock.lock(); //还书前先上锁
try {
margin += num; //书籍余量加上还书数量
System.out.println();
System.out.println("----------------------------------------------------------------");
System.out.println("还书成功,数量为:"+num+"\t\t\t 当前书籍余量为:"
+margin);
System.out.println("----------------------------------------------------------------");
System.out.println();
newDeposit.signalAll(); //叫醒所有睡觉的借书进程,我还了几本书,你们可以借书了没,不够就继续碎觉吧
}
finally {
lock.unlock(); //无论结果如何,开锁
}
}
}
}
小结
今天先写到这里,后面有时间再整理细致发出来。
Java路漫漫,虽道阻且长,但行则必至!