`

面试总结----多线程

    博客分类:
  • java
 
阅读更多
面试过程中,多线程被问到的概率非常大,差不多都会问的。

下面从线程安全概念、什么情况下会产生多线程安全问题、多线程问题的原因、多线程问题的解决方法、java对多线程的支持、例子等方面来总结多线程。

1、线程安全概念
   线程运行过程中,对共享对象的访问和预期或理论上的值有差别,这时就产生了线程安全问题,下面的例子就是三个线程对变量num产生了并发访问。
package com.yangjianzhou.multiThread.test;

public class ThreadUnsafeTest {

	public static int num = 0;

	public static void main(String[] args) {
		Thread t1 = new Thread(new SubRunnable());
		Thread t2 = new Thread(new SubRunnable());
		t1.start();
		t2.start();
		System.out.println(num);

	}

	static class SubRunnable implements Runnable {
		@Override
		public void run() {
			for (int i = 0; i < 10; i++) {
				num++;
			}

		}
	}
}


输出结果:有时为0,有时为10,有时为20.
输出结果的期望值是10,这时就产生了多线程的安全问题,这里有三个线程:t1、t2、main线程同时对num进行访问(读取和设值),这个变量num在这个多线程环境下就是不安全的。

2.什么情况下会产生多线程安全问题
  上面我们已经通过一个例子展示了多线程的安全问题了,那么什么情况下产生多线程的安全问题呢?
   多个线程对同一个对象的并发访问(读取和设值),这时就有可能产生多线程安全问题。可能是一个线程在读取共享变量值的时候,另外一个线程已经改变了这个变量的值了,这时候读取的值就不是预期的值了,这就产生了多线程安全问题了。

3.多线程问题的原因
   多线程产生的原因看似很简单,但是要弄懂还得从线程执行原理上分析。
  

  上图中展示了两个线程对一个共享变量的存储访问。线程在执行时,首先会将主内存区的共享变量拷贝一个副本到自己的工作线程区,执行的过程中,只会对自己工作线程区中的副本进行操作,什么时候讲共享变量的副本同步到主内存区是不确定的,拿1中的例子来说吧,线程t1执行完后(循环执行完)把num副本同步到主内存,在这之前,其他线程(t2和main线程都没同步主内存num的值),这时,主内存的值就是10,由于线程执行的不确定性,假设线程t1和t2开始时都把num的值读取到自己的工作区,然后t1执行,直到结束,把工作内存的num值同步到主内存,这时线程t2的工作区num副本的值为0,然后t2执行,直到完成,然后同步主内存num的值,这时num的值被覆盖为10,而不是期望的20.这就是多线程并发存储对象产生线程安全问题的实质。

4.多线程问题的解决方法
  多线程安全问题产生了,就需要找到解决方案,我们已经知道了多线程安全问题的实质,线程读取了共享变量错误的值,或者线程设置了共享变量的值而没有及时写入主内存。这里有对象锁、可重入锁、读写锁、信号量等措施来防止多线程安全问题。

5.java对多线程的支持
 
  • java中有关键字final、synchronized、java.util.concurrent包及其子包下面的类都对多线程提供了支持。
  •   
  • volatile不能保证线程安全,这个关键字只能保证变量的可见性和防止指令重排序。
  •   
  • final关键字修饰的变量表示该变量的值不能被改变,当然,但这个关键之被用来修饰一个独享变量的时候,只能保证对象变量在内存中的地址不可以变,而无法保证这个对象中包含的值的被改变。
  •   
  • synchronized 这个关键字只能用来修饰方法,由于每个对象都有一个内部锁,当一个线程进入一个对象的一个synchronized方法中后,就获得了这个对象的内部锁,就去主内存读取共享变量的值(synchronized关键字语法要求),其他线程就无法获取这个对象的内部锁了,只能处于等待状态,当获得内部锁的线程执行完这个方法后,退出方法时,就会讲共享变量值同步到主内存,这时synchronized关键字强制执行的,这时就会释放掉内部锁,其他线程处于等待的线程就会获取这个对象内部锁,进行方法执行,这时该线程工作区的变量值已经是最新的了,因此线程可以安全的执行了。
  •   
  • java.util.concurrent下面的类用到了可重入锁、读写锁和CAS(compare and  swap,这连个操作被设计成一条指令,用到了乐观锁机制)执行。

  • 6.生产者消费者例子
    队列大小为2,两个生产者,一个消费者,因此生产者一般都处于阻塞状态,队列一般都处于满的状态。
    
    生产者:
    package com.yangjianzhou.multiThread.test;
    
    import java.util.concurrent.BlockingQueue;
    
    public class Producer implements Runnable {
    
    	private BlockingQueue<String> queue;
    
    	public Producer(BlockingQueue<String> queue) {
    		this.queue = queue;
    	}
    
    	@Override
    	public void run() {
    		try {
    			for (int i = 0; i < 10; i++) {
    				System.out.println("Producer "+Thread.currentThread().getName()+" , queue.size = "+queue.size());
    				queue.put("break" + Thread.currentThread().getName() + i);
    			}
    		} catch (Exception e) {
    
    		}
    	}
    
    }
    
    消费者:
    package com.yangjianzhou.multiThread.test;
    
    import java.util.concurrent.BlockingQueue;
    
    public class Customer implements Runnable {
    
    	private BlockingQueue<String> queue;
    
    	public Customer(BlockingQueue<String> queue) {
    		this.queue = queue;
    	}
    
    	@Override
    	public void run() {
    		try {
    			for (int i = 0; i < 10; i++) {
    				System.out.println("customer "+Thread.currentThread().getName()+" , queue.size = "+queue.size());
    				queue.take();
    			}
    		} catch (Exception e) {
    
    		}
    	}
    
    }
    
    测试程序:
    package com.yangjianzhou.multiThread.test;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    public class Test {
    
    	public static void main(String[] args) {
    		BlockingQueue<String> queue = new ArrayBlockingQueue<String>(2);
    		Thread customer = new Thread(new Customer(queue));
    		Thread producer1 = new Thread(new Producer(queue));
    		Thread producer2 = new Thread(new Producer(queue));
    		customer.start();
    		producer1.start();
    		producer2.start();
    	}
    
    }
    
    运行结果:
    customer Thread-0 , queue.size = 0
    Producer Thread-1 , queue.size = 0
    Producer Thread-2 , queue.size = 1
    Producer Thread-2 , queue.size = 2
    Producer Thread-1 , queue.size = 2
    Producer Thread-2 , queue.size = 2
    customer Thread-0 , queue.size = 1
    customer Thread-0 , queue.size = 2
    Producer Thread-1 , queue.size = 2
    Producer Thread-2 , queue.size = 2
    customer Thread-0 , queue.size = 1
    Producer Thread-1 , queue.size = 2
    customer Thread-0 , queue.size = 2
    customer Thread-0 , queue.size = 2
    Producer Thread-2 , queue.size = 2
    customer Thread-0 , queue.size = 2
    Producer Thread-1 , queue.size = 2
    customer Thread-0 , queue.size = 1
    Producer Thread-2 , queue.size = 2
    Producer Thread-1 , queue.size = 2
    customer Thread-0 , queue.size = 2
    customer Thread-0 , queue.size = 1
    Producer Thread-2 , queue.size = 2
    Producer Thread-1 , queue.size = 2
    
    

    • 大小: 12.3 KB
    分享到:
    评论

    相关推荐

    Global site tag (gtag.js) - Google Analytics