- 本周学习总结
1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容。
- 书面作业
本次PTA作业题集多线程
1.互斥访问与同步访问
完成题集4-4(互斥访问)与4-5(同步访问)
1.1 除了使用synchronized修饰方法实现互斥同步访问,还有什么办法实现互斥同步访问(请出现相关代码)?
还可以使用Lock对象和Condition对象实现互斥同步访问。
public void deposit(int money) {
lock.lock();
try {
this.balance = getBalance() + money;
} finally {
lock.unlock();
}
}
public synchronized void withdraw(int money) {
lock.lock();
this.balance = getBalance() - money;
try {
try {
if (balance < 0) {
condition.await();
}
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}
if (balance < 0) {
throw new IllegalStateException(balance + "");
}
} finally {
lock.unlock();
}
}
1.2 同步代码块与同步方法有何区别?
主要是格式上的不同,同步代码块主要是在方法中使用synchronized关键字实现互斥public void 方法名{ synchronized(类对象名T) {//同步代码块} };同步方法是在定义方法时,加在void前,public synchronized void 方法名{//同步代码块}
1.3 实现互斥访问的原理是什么?请使用对象锁概念并结合相应的代码块进行说明。当程序执行synchronized同步代码块或者同步方法时,线程的状态是怎么变化的?
①需要实现互斥访问的原因是有时两个或两个以上的线程需要同时对同一资源存取,而线程之间如果不加以控制,会产生一种情况-竞争;线程对该资源进行操作时即上锁,这样其他同步线程就无法获取资源,等执行中的线程结束之后,释放资源,让其他线程继续争取对资源的使用权。
②关于对象锁:每个对象都有一把锁,如果未获得对象锁,下面的代码就无法执行,必须等待。从而通过对象锁实现了互斥访问。
class MyCounter{
private int i = 0;
public void increment(){
synchronized (this) {//获得this这个对象上的内部锁
i++;
}
}
}
③假设线程A与线程B同时争取同一个资源,当A和B同时获取CPU进入Running之后,只能有一个线程(假设为A)获得资源的使用权,那么A线程就能进入LockPool获取同步锁继续执行,结束后将同步锁释放出来,而B就要进入WaitPool(通过wait();)等待同步锁被释放后进入LockPool(通过notify();/notifyAll();)重新争取同步锁。
如果有多个线程,则每次只有其中的一个线程获得对象锁,进入addId()、substractId()方法,执行id++、id--。直到该方法结束,同步锁被释放,下一个线程才能获得对象锁。
1.4 Java多线程中使用什么关键字实现线程之间的通信,进而实现线程的协同工作?为什么同步访问一般都要放到synchronized方法或者代码块中?
wait()和notify()/nofityAll()来实现线程之间的通信;存在多个线程同时运行时,没有互斥,将会使线程运行混乱,例如存钱与取钱,可能有多个人要进行,若是synchronized关键字,存钱时与取钱时,可能会造成A存钱时,B存钱也同时进行,而C取钱,余额在A,B,C的操作下同时进行,数值将会错误
2.交替执行
实验总结(不管有没有做出来)
实验总结:先是建立Repo类,存储字符串items.split(" ");;Worker1和Worker2类继承Runnable接口,若是要两个线程交替运行,需要使用wait()和notify()函数,需要在run函数加入synchronized关键字,run函数中循环while(),循环条件是this.repo.getSize()!=0,输出字符串数组,并将null该字符串数组,达到将任务删除,通过判断数组下标的奇偶数来决定接下来需要运行的线程,若是偶数,则运行Worker1,Worker2则wait();若是奇数则运行Worker2,Worker1则wait();以此来交替线程运行;题目的难点在于怎么样设计这个两个线程的交替实现,由于线程争用cpu的随机性,就需要Worker1和Worker2线程执行,在Worker1执行完一次进入等待之前唤醒Worker2,如此往复,主要要用上wait()和notify()了。
3.1
修改TestUnSynchronizedThread.java源代码使其可以同步访问。(关键代码截图,需出现学号)**
//201521123086
public static synchronized void addId() {
id++;
}
public static synchronized void subtractId() {
id--;
}
3.2
进一步使用执行器改进相应代码(关键代码截图,需出现学号)
//201521123086
List<Callable> task=new ArrayList<>();
ExecutorService exec=Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
task.add(Executors.callable(new Adder()));
}
for (int i = 0; i < 3; i++) {
task.add(Executors.callable(new Subtracter()));
}
exec.invokeAll(task);
System.out.println(Counter.getId());
System.out.println("main end")
4.线程间的合作:生产者消费者问题
4.1 运行MyProducerConsumerTest.java。正常运行结果应该是仓库还剩0个货物。多运行几次,观察结果,并回答:结果正常吗?哪里不正常?为什么?
并不是每一次的结果都是0,结果不正常,因为生产者的生产速度和消费者的取货速度不一致,二者没有合作,则可能可出现供给需求不一致的情况。
4.2 使用synchronized, wait, notify解决该问题(关键代码截图,需出现学号)
//201521123086
public synchronized void add(String t) {
while(repo.size() == capacity) {
System.out.println("仓库已满!无法添加货物。");
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
repo.add(t);
notifyAll();
}
public synchronized void remove() {
while (repo.size() == 0) {
System.out.println("仓库无货!无法从仓库取货");
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
5.查询资料回答:什么是线程安全?(用自己的话与代码总结,写自己看的懂的作业)
线程安全:如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,那么就是线程安全的。更专业一点的说法是:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
举个反例:
比如i++执行的时候可能会有两步来完成:1.x=i+1;2.i=x。在单线程运行的情况下,如果 i= 0,i++后,i=1;而如果是在多线程情况下,比如有两个线程,线程 A 取i得值为0,但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也取得i=0的值,因为此时i还是原来的值,并未被线程A改变,所以线程B也是i=0。然后线程A和线程B都继续运行,都执行i++,结果i都等于1。这就很明显,i通过线程AB进行两次自加,结果应该为2的,但是却等于1,这就是"线程不安全"了。
- 码云上代码提交记录
题目集:多线程(4-4到4-10)
3.1. 码云代码提交记录
在码云的项目中,依次选择“统计-Commits历史-设置时间段”, 然后搜索并截图
3.2 截图多线程PTA提交列表