1. 本周学习总结

2. 书面作业

本次PTA作业题集多线程

互斥访问与同步访问

完成题集4-4(互斥访问)与4-5(同步访问)

1.1 除了使用synchronized修饰方法实现互斥同步访问,还有什么办法实现互斥同步访问(请出现相关代码)?

答:还可以使用lock实现互斥同步访问,相关代码如下:
	void deposit(int money){
		lock.lock();//上锁
		try{
			balance=balance+money;
			notify();
		}finally{
			lock.unlock();//释放锁
		}	
	}
	void withdraw(int money){
		lock.lock();//上锁
		try{
			if(balance>money)
				balance=balance-money;
			else
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
		}finally{
			lock.unlock();//释放锁
		}
	}

1.2 同步代码块与同步方法有何区别?

答:首先是同步代码块是在方法内部使用大括号使得一个代码块得到同步,而同步方法是在方法前加关键字synchronized,然后被同步的方法一次只能有一个线程进入,其他线程等待;其次是同步代码块的锁可以是任意对象,然而同步方法的普通方法的锁是this,同步方法的静态方法的锁是当前类的字节码文件对象。

1.3 实现互斥访问的原理是什么?请使用对象锁概念并结合相应的代码块进行说明。当程序执行synchronized同步代码块或者同步方法时,线程的状态是怎么变化的?

答:每个对象都会有个内部锁定,或者称为监控锁定,被标识为synchronized的区块就会被监控,任何线程要执行synchronized代码块都必须取得指定的对象锁定。例如下面的这个代码片段:
public void add(Object o){
   synchronized(this){
        if(next==s.length){
            s=Arrays.copyOf(s,s.length*2);
        }
        s[next++]=o;
    }
}
在线程要执行synchronized代码块时必须要取得括号中指定的对象锁定
程序执行synchronized同步代码块或者同步方法时,线程的状态是在取得锁定之后,会先回到Runable状态,等待CPU排班器排入Running状态。

1.4 Java多线程中使用什么关键字实现线程之间的通信,进而实现线程的协同工作?为什么同步访问一般都要放到synchronized方法或者代码块中?

答:使用wait和notify/notifyAll来实现线程之间的通信,因为如果同步访问不放在synchronized方法或者代码块中,就很有可能由于多线程同时访问一个临界资源而导致混乱。

交替执行

实验总结(不管有没有做出来)

总结:首先要把输入进来的带空格的字符串分割然后存储起来,然后还要使用wait()和notify()来进行线程之间的合作,交替执行的总体思想就是第一个线程执行完进入等待之前要唤醒第二个线程,第二个线程执行完进入等待之前要唤醒第一个线程,如此反复即可,所以就需要使用flag。

互斥访问

3.1 修改TestUnSynchronizedThread.java源代码使其可以同步访问。(关键代码截图,需出现学号)

运行结果如下:

3.2 进一步使用执行器改进相应代码(关键代码截图,需出现学号)

使用ExecutorService改进,最后需要加上awaitTermination,否则任务提交通道就会被关闭,也就是shutdown()后,不能再提交新的任务进去,但是awaitTermination()后,可以继续提交。

运行结果如下:

线程间的合作:生产者消费者问题

4.1 运行MyProducerConsumerTest.java。正常运行结果应该是仓库还剩0个货物。多运行几次,观察结果,并回答:结果正常吗?哪里不正常?为什么?

答:不正常,运行结果是随机的,剩余货物是0-10的随机数,而且add()有时会被运行,有时候不会被运行,因为生产者和消费者的存取速度是随机的,不一定相同。

4.2 使用synchronized, wait, notify解决该问题(关键代码截图,需出现学号)

运行结果如下:

4.3 选做:使用Lock与Condition对象解决该问题。

private Lock lock=new ReentrantLock();
	private Condition condition = lock.newCondition();

	public synchronized void add(String t) {
		lock.lock();
		try {
			while (repo.size() == capacity) {
				try {					
					condition.await();
					System.out.println("仓库已满!无法添加货物。");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			repo.add(t);
			condition.signal();
		} finally {
			lock.unlock();
		}
	}
	public synchronized void remove() {
		lock.lock();
		try{			
			while (repo.size() == 0) {
				try{					
					condition.await();
					System.out.println("仓库无货!无法从仓库取货");
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
			repo.remove(0);
			condition.signal();
		}finally{
			lock.unlock();
		}		
	}//201521123039

查询资料回答:什么是线程安全?(用自己的话与代码总结,写自己看的懂的作业)

答:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,比如一个程序所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码,如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,那么这样就是线程安全的。比如看下面的代码:
public Double pi() {   
    int a = 5;   
    int b = 2;   
    return new Double(a / b);  //不同线程执行这段代码时,会有不同的a/b变量,由于没有数据共享,所以这里是线程安全的
  }  
  private static ThreadLocal pi = new ThreadLocal(); //ThreadLocal类封装了任何类型对象,并把它绑定到当前线程  
  public Double pi() {   
    if (pi.get() == null) {   
      pi.set(new Double(5 / 2));   
    }     
    return (Double)pi.get(); //线程执行pi()方法的时候,实例pi返回的是当前线程的对象,这样的调用也是线程安全的  
  }  

线程安全的理解

选做:实验总结

6.1 4-8(CountDownLatch)实验总结

总结:CountDownLatch 的作用和 Thread.join() 方法类似,可用于一组线程和另外一组线程的协作,例如,主线程在做一项工作之前需要一系列的准备工作,只有这些准备工作都完成,主线程才能继续它的工作

6.2 4-9(集合同步问题)实验总结

总结:ArrayList不是线程安全的,所以要通过集合Collections.synchronizedList将其转换为一个线程安全的类

6.3 较难:4-10(Callable),并回答为什么有Runnable了还需要Callable?实验总结。

选做:使用其他方法解决题目4的生产者消费者问题。

7.1 使用BlockingQueue解决生产者消费者问题关键代码截图

7.2 说明为什么不需要显示的使用wait、notify就可以解决同步问题。这样解决相比较wait、notify有什么优点吗?

7.3 使用Condition解决生产者、消费者问题。

3.1. 码云代码提交记录

3.2 截图多线程PTA提交列表