Java -- Thread -- Collection -- 20题目

现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:

public class Test1
{
    public static void main(String[] args)
    {
    SOP(begin:+sys.currentTimeMillis()/1000);
    //模拟处理16行日志,下面的代码产生16个日志对象,需运行16秒才能打印完
    //修改程序代码,开4个线程让这16个日志在4秒钟打完
    for (int i=0; i<16; i++)    //这行代码不能改动
    {
    final String log = “”+(i+1);    //这行代码不能改动
    {
    Test1.parseLog(log);
}
}
}
//parseLog方法内部代码不能改动
public static void parseLog(String log)
{
    SOP(log+”:”+(sys.currentTimeMillis()/1000));
    try
    {
    Thread.sleep(1000);
}
    catch(InterruptedException e)
    {
    e.printStackTrace();
}
}
}

刚看到题目还想着很简单;直接在Test.parseLog(log)的地方new4个线程,都执行打印任务即可,仔细一看不行,在这里new4个线程的话就是16*4个线程了,所以要将线程在for循环外边创建出来,for内部将产生的日志对象装在一个共享变量里,在线程内部从共享变量中取数据打印。要考虑线程同步互斥问题,这个共享变量要具备同步功能,可以使用ArrayBlockingQueue这种阻塞式队列来存储日志对象。也可以使用普通集合,但拿数据要考虑同步问题,可能会浪费时间。

在16次for循环内部将产生的在for循环外部创建线程,定义共享变量
final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(16);
    for (int i=0; i<4; i++)    创建4个线程
    {
    new Thread(new Runnable()
{
    public void run()
    {
    while (true)
    {    先从集合中取到日志对象,再打印
    String log = queue.take();    要处理异常
    parseLog(log);
}
}
}).start();
}

日志对象装入共享变量中

queue.put(log);      要处理异常

这道题只要用到同步集合来共享数据就可以了  List集合的Vector也可以

=========================================

现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理。就好像生产者不断地产生数据,消费者不断地消费数据。请将程序改造成有10个线程来消费生产者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有序的。原始代码如下:

public class Test2
{
    public static void main(String[] args)
    {
    SOP(begin+sys.currentTimeMillis()/1000);
    for (int i=0; i<10; i++)    //这行不能改动
    {
    String input = i+””;    //这行不能改动
    String output = TeatDo.doSome(input);
    SOP(ThreadName+output);
}
}
}
//不能改动此TestDo类
class TestDo
{
    public static String doSome(String input)
    {
    try
    {
    Thread.sleep(1000);
}
    catch (InterruptedException e)
    {
    e.printStackTrace();
}
String output = input + “:” + (Sys.currentTimeMillis());
return output;
}
}

看这题和上一题差不多,也是数据共享问题了,弄一个同步集合存起来。

       用同样的方法一样解决 new ArrayBlockingQueue()

张老师又讲了另一个同步队列:SynchronousQueue一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。

SynchronousQueue是一个特殊队列,即便是空的也不能插入元素,也读不到元素,要往里边插入的时候如果没有读取操作,插入操作就会阻塞,等到有读取操作出现时,插入操作检测到了读取操作,才能把数据插入进去,而读取操作正好可以拿到刚刚插入进去的数据。就好比毒品买卖,我拿着毒品给谁呢,只有买毒品的人来了,才能立马给他,他也拿到了。与Exchanger类似,不过Exchanger是单对单的交换,SynchronousQueue可以多个抢数据,我拿着毒品等人来买,一下来了3个人买,谁抢到了就是谁的;或者我拿3包毒品,3个人同时每人一份。

这道题用synchronousQueue的话会一下子将10个数据全打印出来,因为10次循环一次放一个并没有人来取,所以没有放进去,后来一下10个线程来取数据,就一下放进去拿走了。我测试的时候没有这种情况,都是间隔一秒一秒的。测试后发现,将doSome处理后的结果存进去,就会有间隔,而直接存进去,取数据后再处理的话就是一下一片了。分析后知道:put时没有take,10个数据都在等待存入,如果存入的数据是doSome(input)的话,开始取数据时才开始执行doSome所以就会有间隔了。直接存数据,取出后在doSome就是一下拿到10个数据了。

要解决这个问题,可以使用厕所抢坑的方式解决,使用Semaphore来获取许可,每取一次数据释放一次即可。
final Semaphore x = new Semaphore(1);    一次一个
final SynchronousQueue queue = new SynchronousQueue();
每次取数据前都要先获取许可
x.acquire();
取完后释放许可
x.release();
这种方式与使用Lock方式一样

 

=============================================

现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:

              4:4:1258199615

              1:1:1258199615

              3:3:1258199615

              1:2:1258199615

       请修改代码,如果有几个线程调用TestDo.doSome(key, value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是“1”时,她们中的一个要比另外其他线程晚1秒输出结果,如下所示:

              4:4:1258199615

              1:1:1258199615

              3:3:1258199615

              1:2:1258199616

       总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:

//不能改动此Test类
public class Test3 extends Thread
{
    private TestDo testDo;
    private String key;
    private String value;
    public Test3(String key, String key2, String value)
    {
    this.testDo = TestDo.getInstance();
    /*常量“1”和 “1”是同一个对象,下面这行代码就是要用“1”+“”的
方式产生新的对象,以实现内容没有改变,仍然相等(都还为“1”),
但对象却不再是同一个的效果
*/
this.key = key + key2;
this.value = value;
}
    public static void main(String[] args) throws InterruptedException
    {
    Test3 a = new Test3(“1”, “”, “1”);
    Test3 b = new Test3(“1”, “”, “2”);
    Test3 c = new Test3(“3”, “”, “3”);
    Test3 d = new Test3(“4”, “”, “4”);
    SOP(begin+:+sys.currentTimeMillis()/1000);
    a.start();
    b.start();
    c.start();
    d.start();
}
    public void run()
    {
    testDo.doSome(key, value);
}
}

class TestDo
{
    private TestDo(){}
    private static TestDo _instance = new TestDo();
public static TestDo getInstance()
    {
    return _instance;
}
public void doSome(Object key, String value)
{
    //此大括号内的代码是需要局部同步的代码,不能改动!
    {
    try
    {
      Thread.sleep(1000);
      SOP(key+”:”+value+”:”+sys.currentTimeMillis()/1000);
  }
    catch (InterruptedException e)
    {
        e.printStackTrace();
  }
  }
}
}

看完这道题第一个想法是在标记位置加上同步代码块,但是锁不好弄了,因为每次都新建了一个key对象来接受实际key,没法获取到实际key对象。

       想到了Lock对象,所以建一个Lock对象,判断key的值是否和指定值“1“相同,如果相同就锁上,不同不管,finally里在解锁前进行判断,避免没上锁还要解锁发生问题。

Lock lock = new ReentrantLock();
    public void doSome(Object key, String value)
    {
        if (key.equals("1"))
            lock.lock();
            //System.out.println("OKOKOOK");
        //synchronized ("1")
        try
        //此大括号内的代码是需要局部同步的代码,不能改动!
        {
            try
            {
                Thread.sleep(1000);
                System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }finally
        {
            if (key.equals("1"))
                lock.unlock();
        }
    }

但上面的方式写死了,如果换2呢 还要改代码,现在要不管是什么只要相同都互斥,将这些加进来的key放到一个集合ArrayList中,每次传进来一个key,先把传进来的key作为锁对象,再判断这个对象有没有存在锁集合中,如果没有,就把它存进去,同时就用这个key做锁;如果已经存在了,就是说这个key已经做过锁对象了,就需要将以前做锁的那个对象拿出来,再让它来当锁,与传进来的key对象一样,这样就产生互斥效果了。

       需要注意:拿原来的锁对象时要迭代锁集合,因为有多个线程在运行,所以迭代时有可能出现其他线程的key没有做过锁,需要将它加到锁集合中,可是这时候这个线程还在迭代过程中,迭代时不能操作集合中的数据,就会发生异常。要解决这个问题,就需要用到同步集合了。CopyOnWriteArrayList

 

使用ArrayList时就经常出异常,换CopyOnWriteArrayList后没有异常了
//将所有传过来的key都存起来
    //private List<Object> keys = new ArrayList<Object>();
    private CopyOnWriteArrayList<Object> keys = new CopyOnWriteArrayList<Object>();
    public void doSome(Object key, String value)
    {
        //先用这个key当锁,用过一次就存到集合中
        Object o = key;
        //判断这个锁用过没有
        if (!keys.contains(o))
        {
            //如果这个key没有用过,就用它当锁,把它存到锁集合中
            keys.add(o);
        }
        else    //锁集合中已经有了这个key
        {
            //这个key已经当过锁了,就把它拿出来,还用它做锁,就和现在的key互斥了
            //因为不知道原来key的位置,所有需要进行遍历
            for (Iterator<Object> it = keys.iterator(); it.hasNext();)
            {
                //当前遍历到的对象
                Object oo = it.next();
                //如果找到了,就让它做锁
                if (oo.equals(o))
                {
                    o = oo;
                    break;    //找到了,不用再循环了
                }                    
            }
            //o = keys.get(keys.indexOf(o));    //key和o不是同一个对象,拿不到
        }
        
        synchronized (o)
        //此大括号内的代码是需要局部同步的代码,不能改动!
        {
            try
            {
                Thread.sleep(1000);
                System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

a = “1”+””;
b = “1”+””;
a和b是同一个对象,常量相加 equals为真 ==为假

Object o1 = new String("1");
        Object o2 = new String("1");
        System.out.println(o1==o2);    //false
        System.out.println(o1.equals(o2));    //true
        System.out.println(o1);    //1
        System.out.println(o2);    //1
        Object o3 = "1"+"";
        Object o4 = "1"+"";
        System.out.println(o3==o4);    //true
        System.out.println(o3.equals(o4));    //true
        Object o5 = "2"+"";
        Object o6 = get("2","");
        System.out.println(o5==o6);    //false
        System.out.println(o5.equals(o6));    //true
        System.out.println(o5+"__"+o6);    //2__2
        
    public static Object get(String a, String b)
    {
        return a+b;
    }

 

posted on 2016-04-25 09:56  yeatschen  阅读(115)  评论(0)    收藏  举报

导航