1. 本周学习总结
1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容。
- 注意: notify()/notifyAll()方法和wait()方法都只能在被声明为synchronized的方法或代码段中调用。
2. 书面作业:本次PTA作业题集多线程
互斥访问与同步访问:完成题集4-4(互斥访问)与4-5(同步访问)
1.1 除了使用synchronized修饰方法实现互斥同步访问,还有什么办法实现互斥同步访问(请出现相关代码)?
答:
还有lock(ReentrantLock(可重入锁),lock,unlock方法)和使用条件对象(条件变量—信号量,Condition)
private Lock lock = new ReentrantLock();``private Condition condition = lock.newCondition();
通过以上两句定义一个lock对象和一个condition对象来实现互斥同步访问。
- 使用条件对象(条件变量—信号量,Condition)
- Condition对象需要搭配Lock对象来使用
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
- 实现同步的方法
- condition.await() //类似object的wait
- condition.signal() //类似object的notify
- condition.signalAll() //类似object的notifyAll()
关键代码如下截图:
1.2 同步代码块与同步方法有何区别?
答:
①在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为synchronized方法。简单说就是有synchronized关键字修饰的方法
需要注意的是synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
例如:
public static synchronized void addId() {
id++;
}
②synchronized代码块即有synchronized关键字修饰的语句块。
例如:
public static void addId() {
synchronized(Counter.class){//代表Counter类型的对象
id++;
}
}
③除了定义和格式不一样之外,同步代码块与同步方法的区别还在于同步代码块锁住的是语句而同步方法锁住的是整个方法。
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();
)重新争取同步锁。
1.4 Java多线程中使用什么关键字实现线程之间的通信,进而实现线程的协同工作?为什么同步访问一般都要放到synchronized方法或者代码块中?
答:
①Java多线程中使用synchronized
关键字以及wait();``notify();``notifyAll();
实现线程之间的通信,进而实现线程的协同工作。
②同步访问一般都要放到synchronized方法或者代码块中就是将方法或代码块上锁的过程,这样多个线程有冲突时,才能通过‘执行获得同步锁的线程’这样的原则来解决多个线程之间的竞争问题,才不会导致代码运行结果出错。
交替执行
实验总结(不管有没有做出来)
答:首先通过task = items.split(" ");
将传递进来的字符串以空格区分分解为多个不同的任务;task.length - i;
Repo包含的任务数量(完成任务的时候,需要将任务删除->i);设一个布尔型的flag来判断任务1和2是否执行,当flag!=true
时,任务1wait();
(eclipse会提示你要放在try-catch里面的),否则如果Repo里还有任务时就执行下一个并输出结果,此时,完成的任务个数i就会改变task[i++]
,任务2类似;接下来定义Worker1与Worker2类,从Repo对象中获取任务run1与run2;
看来我对pta的中文识别能力一无所知,一直编译错误,后来才想起来pta不喜欢中文,赶紧把代码的中文提示删掉,果然。
互斥访问
3.1 修改TestUnSynchronizedThread.java源代码使其可以同步访问。(关键代码截图,需出现学号)
答:修改后的关键代码及结果截图如下:
3.2 进一步使用执行器改进相应代码(关键代码截图,需出现学号)
参考资料:Java多线程之Executor、ExecutorService、Executors、Callable、Future与FutureTask
答:对Executor还不是太了解,虽然改进后的代码能运行,但是结果却不是0,不知道是哪里错了
线程间的合作:生产者消费者问题
4.1 运行MyProducerConsumerTest.java。正常运行结果应该是仓库还剩0个货物。多运行几次,观察结果,并回答:结果正常吗?哪里不正常?为什么?
答:结果不正常,运行十次发现不知出现“仓库还剩0个货物”还出现“仓库还剩10个货物”。
不正常:
public synchronized void add(String t) {
if (repo.size() >= capacity) {
System.out.println("仓库已满!无法添加货物。");//那货物怎么办呢?
} else {
repo.add(t);
}
}
原因在于当生产者向仓库添加货物而此时仓库已满,代码中仅仅输出仓满,而没有对这些货物作出处理(比如扔掉或者等消费者取走等量货物后再放入)。
4.2 使用synchronized, wait, notify解决该问题(关键代码截图,需出现学号)
答:截图如下
4.3 选做:使用Lock与Condition对象解决该问题
答:结合书面作业1.1,截图如下
查询资料回答
什么是线程安全?(用自己的话与代码总结,写自己看的懂的作业)
答:
线程安全:如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,那么就是线程安全的。更专业一点的说法是:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
举个反例:
比如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,这就是"线程不安全"了。
选做:实验总结
6.1 4-8(CountDownLatch)实验总结
答:看到题目发现不认识,吓得我赶紧查了一下,‘CountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行;CountDownLatch如其所写,是一个倒计数的锁存器,当计数减至0时触发特定的事件。利用这种特性,可以让主线程等待子线程的结束。’,创建计数器和执行器不难,跟书面作业3.2截图里的做法差不多,只是换了Executors.newFixedThreadPool();
然后for循环exec.execute(new MyTask(latch));
.
6.2 4-9(集合同步问题)实验总结
答:Collections.synchronizedList(new ArrayList());
参考链接
6.3 较难:4-10(Callable),并回答为什么有Runnable了还需要Callable?实验总结。
选做:使用其他方法解决题目4的生产者消费者问题。
7.1 使用BlockingQueue解决生产者消费者问题关键代码截图
7.2 说明为什么不需要显示的使用wait、notify就可以解决同步问题。这样解决相比较wait、notify有什么优点吗?
答:如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒.同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作.
[参考博客:线程----BlockingQueue](http://www.cnblogs.com/likwo/archive/2010/07/01/1769199.html)
7.3 使用Condition解决生产者、消费者问题。
答:结合书面作业1.1(Lock和Condition),截图如下