meidi

最近觉得某些公司的选择题也是很基础,非常值得总结回味。今天做了美的的笔试,20道选择题(单选14+6多选)。特此记录如下(部分忘了烦请见谅):

 

1. 是我昨晚刚刚总结的List,Set,Map的区别;

需要注意的是Map不是继承了Collection接口;

 

2. 关于并发编程的问题;

感觉对并发这一块不是特别熟,题目上描述了缓存导致可见性问题,线程切换带来的原子性问题;编译优化带来的有序性问题等等,不过自己仔细想想也还是能够想出来的。

关于这个问题,大家可以参考以下三篇文档:

 

3.Spring事务的默认传播方式与隔离级别

Spring事务传播方式:
主要控制当前的事务如何传播到另外的事务中

  • NESTED

如果当前存在事务,则在嵌套事务内执行。
如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。
外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚

  • NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

  • NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

  • REQUIRED

支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择(默认)

  • REQUIREDS_NEW

新建事务,如果当前存在事务,把当前事务挂起。

  • SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

  • MANDATORY

支持当前事务,如果当前没有事务,就抛出异常。

 

Spring事务隔离级别:
主要定义事务与事务之间在数据库读写方面的控制范围
主要解决脏读、不可重复读、虚读三个问题

ISOLATION_DEFAULT 默认级别

ISOLATION_READ_UNCOMMITED
事务最低的隔离级别,充许别外一个事务可以看到这个事务未提交的数据,
会产生脏读,不可重复读和幻像读

ISOLATION_COMMITED
保证一个事务修改的数据提交后才能被另外一个事务读取,可以避免脏读出现,
解决了脏读问题,但是可能会出现不可重复读和幻像读

ISOLATION_REPEATABLE_READ
保证一个事务不能读取另一个事务未提交的数据外可以防止脏读,不可重复读
但是可能出现幻像读

ISOLATION_SERIALIZABLE
花费最高代价但是最可靠的事务隔离级别。
事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读

参考链接:事务的传播方式

4. yieid()与sleep()的区别

在这里我拓展一下:看看sleep()、wait()、yield()、join()各方法的解析。

1  sleep()  

使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会执行, 同时sleep函数不会释放锁资源

sleep没有优先级的区别,即:可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会。

2  yield() 

只是使当前线程重新回到可执行状态,所以执行yield()线程有可能在进入到可执行状态后马上又被执行。

只能使同优先级的线程有执行的机会。同样, yield()也不会释放锁资源

 

sleep和yield的联系与区别:

联系:yield()方法和sleep()方法类似,也不会释放“锁标志”。

区别:

  1. 执行sleep()方法后,线程转入阻塞(blocked)状态,而yield()方法只是使当前线程重新回到可执行状态即(就绪状态),所以执行yield()的线程有可能在进入到可执行状态后马上又被执行;
  2. sleep可以使优先级低的线程得到执行的机会,  而yield只能使同优先级或者更高优先级的线程有执行的机会;
  3. sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
  4. sleep()方法比yield()具有更好的可移植性

参见代码:

 1    public class ThreadYieldOne implements Runnable {
 2      public String name;
 3      public void run() {
 4         for (int i = 0; i < 10; i++) {
 5            Thread.yield();    // (2)  Thread.sleep(100);
 6            System.out.println(name + " :" + i);
 7         }
 8      }
 9   }
10 
11   public static void main(String[] args) {
12       ThreadYieldOne one = new ThreadYieldOne();
13       ThreadYieldOne two = new ThreadYieldOne();
14       one.name = "one";
15       two.name = "two";
16       Thread t1 = new Thread(one);
17       Thread t2 = new Thread(two);
18       t1.setPriority(Thread.MAX_PRIORITY);
19       t2.setPriority(Thread.MIN_PRIORITY);   // (1) t2.setPriority(Thread.MAX_PRIORITY);   
20       t1.start();
21       t2.start();
22    }

执行结果:

one :0 one :1 one :2 one :3 one :4 one :5 one :6 one :7 one :8 one :9
two :0 two :1 two :2 two :3 two :4 two :5 two :6 two :7 two :8 two :9

从上可看出:当使用yield()是优先级高的先执行,执行完毕后再开始执行优先级低的;

注:

  •  (1) 处 如果放开注释掉t2.setPriority(Thread.MIN_PRIORITY);   , 则执行结果将会改变: 如下:  one和two 交替打印。
one :0 one :1 one :2 one :two :0 two :1 two :2 two :3 one :4 one :5 one :3 two :4 two :5 two :6 two :6 one :7 one :8 one :9
7 two :8 two :9
  • (2)处 如果放开并注释掉Thread.yield(); , 则执行结果也是one 和two 交替打印, 并且, 它不受(1)处的影响。

3 .  stop() 

这个方法可以中止一个正在运行的线程, 但这样的方法并不安全。 强列建议不要使用此函数来中断线程。

注:  stop方法是立即停止当前线程,  这样停止的后果是导致stop后的语句无法执行, 有可能资源未释放. 列或者在同步块中调用此方法会导致同步数据会不完整. 所以需禁用此方法.  由于stop方法的特释性, 将不给出示范代码。

 

4. Interrupt()

一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作。线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序。

Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

两个解释为什么会相冲突呢, 一个解释是可以中断一个线程, 一个解释说不会中断一个正在运行的线程。仔细看会发现其中的奥秒, interrupt方法不会中断一个正在运行的线程,就是指线程如果正在运行的过程中,去调用此方法是没有任何反应的。为什么呢, 因为这个方法只是提供给 被阻塞的线程, 即当线程调用了.Object.wait, Thread.join, Thread.sleep三种方法之一的时候, 再调用interrupt方法, 才可以中断刚才的阻塞而继续去执行线程。

          //  情况1.  不会中断线程.
      public class guoInterrupt extends Thread {
         public boolean stop = false;
         public static void main(String[] args) throws InterruptedException {
            guoInterrupt t1 = new guoInterrupt();
            System.out.println("app is starting");
            t1.start();
            Thread.sleep(3000);
            System.out.println("Interrupting t1....");
            t1.interrupt();
            Thread.sleep(3000);
            System.out.println("app is end");
            t1.stop = true;
            System.exit(0);
         }
         public void run() {
            while(!this.stop) {
               System.out.println("t1 running...........");
              long time = System.currentTimeMillis();
               while ( (System.currentTimeMillis()-time < 1000) && (!stop) ) {};
            }
            System.out.println("t1 is end");
         }
      }

输出结果:

app is starting
t1 running...........
t1 running...........
t1 running...........
t1 running...........
Interrupting t1....
t1 running...........
t1 running...........
t1 running...........
app is end
t1 is end

结果说明:当调用了interrupt时, t1线程仍在执行, 并没有中断线程. 直到main扫行结束后, 改t1的stop值时 t1线程才执行结束.
             //情况2:  interrupt中断线程.
             public class guoInterrupt extends Thread {
           public boolean stop = false;
           public static void main(String[] args) throws InterruptedException {
              guoInterrupt t1 = new guoInterrupt();
              System.out.println("app is starting");
              t1.start();
              Thread.sleep(3000);
              System.out.println("Interrupting t1....");
              t1.stop = true;  //就是这一句语句的位置的作用;
              t1.interrupt();
              Thread.sleep(3000);
              System.out.println("app is end");
              System.exit(0);
           }
           public void run() {
              while(!this.stop) {
                 System.out.println("t1 running...........");
                 try {
                    Thread.sleep(1000);
                 } catch (InterruptedException e) {
                    System.out.println("t1 is Interrupting......");
                 }
              }
              System.out.println("t1 is end");
           }
        }  

执行结果:

app is starting
t1 running...........
t1 running...........
t1 running...........
Interrupting t1....
t1 is Interrupting......
t1 is end
app is end

结果说明: 当执行了 t1.interrupt();方法时, 线程立即产生了一个.InterruptedException 异常。

 

5 .  join() 

当join(0)时等待一个线程直到它死亡(即等待线程执行完毕);

当join(1000)时等待一个线程1000纳秒(即执行线程并不一定执行完毕),后回到主线程继续执行。

代码示例:

    public static void main(String[] args) {
        ThreadJoin t = new ThreadJoin();
        try {
            t.start();
            Thread.sleep(1000);
            System.out.println("main join start");
            t.join(0);  // (1)
            System.out.println("main join end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }             

 public class ThreadJoin extends Thread {
    public void run() {
            System.out.println("join start");
            try {
                Thread.sleep(9000);
                for (int i = 0; i < 5; i++) {
                    System.out.println("sub thread:" + i);
                }
            } catch (InterruptedException e) {
            }
            System.out.println("join end");
    }
}

输出结果:

  •  当(1)处 为t.join(0)时 等待t线程执行完之后回到main函数的主线程继续处理;

结果如下:

join start
main join start
sub thread:0
sub thread:1
sub thread:2
sub thread:3
sub thread:4
join end
main join end
  •  当(1)处 为t.join(1000)时,main函数的主线程等待t经程1000纳秒后继续执行;

结果如下:

join start
main join start
main join end
sub thread:0
sub thread:1
sub thread:2
sub thread:3
sub thread:4
join end

注:  join函数为线程安全函数, 即同步函数。

也就是说上面的例子, 当ThreadJoin类的run用synchronized锁住时, t.join方法将得不到锁资源而等待更长的时间。

 

【注意:以上都是thread类的方法!!!!】

 

6. wait()

在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。

 

wait()方法需要和notify()及notifyAll()两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象的锁。注意,这些是Object类的方法,而不是Thread类的方法。

wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。

 

 

多线程的同步:

  • synchronized;
  • wait;
  • notify;

线程的同步需要依靠上面两个函数和一个同步块实现。

在这里以读写为例:

 有一个缓冲区, 由两个线程进行操作, 一个写线程, 一个读线程. 写线程完成对缓冲区的写入, 读线程完成对线程的读取, 只有当缓冲区写入数据时, 读线程才可以读取, 同样. 只有当缘冲区的数据读出时,  写线程才可以向缘冲区写入数据。

代码如下:

public class ThreadWrite extends Thread {
    public StringBuffer buffer;
    public ThreadWrite(StringBuffer buffer) {
        this.buffer = buffer;
    }
    public void run() {
        synchronized (this.buffer) {
            for (int i = 0; i < 5; i++) {
                if (!"".equals(this.buffer.toString())) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } 
                System.out.println("Write start");
                this.buffer.append("123");
                this.buffer.notify();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("Write end");
            }
        }
    }
}

 

public class ThreadRead extends Thread {
    public StringBuffer buffer;
    public ThreadRead(StringBuffer buffer) {
        this.buffer = buffer;
    }
    public void run() {
        synchronized (buffer) {
            for (int i = 0; i < 5; i++) {
                if ("".equals(this.buffer.toString().trim())) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("read start");
                System.out.println(this.buffer.toString());
                buffer.delete(0, buffer.toString().length());
                buffer.notify();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("read end");
            }
        }
    }
}

public class GuoSynchronized {
    public static void main(String[] args) {
        StringBuffer bufer = new StringBuffer("");
        ThreadWrite write = new ThreadWrite(bufer);
        ThreadRead read = new ThreadRead(bufer);
        read.start();
        write.start();
    }
}
View Code

执行结果:

Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
View Code

结果说明: 这个结果不管执行多少次, 都会是相同的结果, 即多线程同步的特点。

注: synchronized  关键字的同步, 在两个线程之间必须是同一个对象, 即 对同一个对象进行同步访问。

 

  • 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
  • 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
  • waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

以上总结如下:

1、 sleep和yield的区别在于, sleep可以使优先级低的线程得到执行的机会,  而yield只能使同优先级的线程有执行的机会.
2、 interrupt方法不会中断一个正在运行的线程.就是指线程如果正在运行的过程中, 去调用此方法是没有任何反应的. 为什么呢, 因为这个方法只是提供给 被阻塞的线程, 即当线程调用了.Object.wait, Thread.join, Thread.sleep三种方法之一的时候, 再调用interrupt方法, 才可以中断刚才的阻塞而继续去执行线程.
3、 join()  当join(0)时等待一个线程执行直到它死亡返回主线程,  当join(1000)时主线程等待一个线程1000纳秒,后回到主线程继续执行.
4、 waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

 

参考:

1. sleep(),wait(),yield()和join()方法的区别

2. sleep()、wait()、yield()、join()方法 解析

 

5. 双重校验单例中volatile关键字的用处

volatile关键字具有两种特性:

  • 保证此变量对所有线程的可见性
  • 禁止指令重排序优化

而在双重校验单例中,起作用的是第二种。

场景:

1. 状态标记量使用volatile来修饰状态标记量,使得状态标记量对所有线程是实时可见的;

2. 双重检测机制实现单例:普通的双重检测机制在极端情况,由于指令重排序会出现问题,通过使用volatile来修饰instance,禁止指令重排序,从而可以正确的实现单例

 

6. 创建对象的个数以及判断是否相等

  • ==比较的是两个对象的内存地址;
  •  equals()比较的是两个对象的内容;
public static void main(String[] args) {
        String s1 = "mei";//pool中没有,新new一个String对象在堆中,再创建一个对象"mei",并把“mei”放入常量池中;
        String s2 = new String("mei");//pool中有,故只需要创建一个对象放在堆中即可;
        String s3 = "m" + "ei";
        System.out.println((s1 == s2) + " || " + s1.equals(s2));//false||true
        System.out.println(s1 == s3);//true
        System.out.println((s1 == s1.intern())+ " || " + (s1.equals(s1.intern())));//true||true
        System.out.println((s2 == s2.intern()) + " || " + (s2.equals(s2.intern())));//false||true
        /*
         * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
         */

    }

输出结果:

false || true
true
true || true
false || true

 

posted @ 2019-09-15 17:21  额是无名小卒儿  阅读(1056)  评论(0编辑  收藏  举报