JAVA多线程和并发

1. 进程和线程之间有什么不同?
一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。
而线程是在进程中执行的一个任务。

2. 多线程编程的好处是什么?
多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。

3. 用户线程和守护线程有什么区别?
当我们在Java程序中创建一个线程,它就被称为用户线程。
当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。

4. 我们如何创建一个线程?
有两种创建线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;
二是直接继承Thread类。

5. 有哪些不同的线程生命周期?
当我们在Java程序中新建一个线程时,它的状态是New。当我们调用线程的start()方法时,状态被改变
为Runnable。线程调度器会为Runnable线程池中的线程分配CPU时间并且讲它们的状态改变为Running。
其他的线程状态还有Waiting,Blocked 和Dead。

6. 可以直接调用Thread类的run()方法么?
当然可以,但是如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,为了在新的线程中执行
我们的代码,必须使用Thread.start()方法。

7. 如何让正在运行的线程暂停一段时间?
我们可以使用Thread类的Sleep()方法让线程暂停一段时间。
需要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为Runnable,
并且根据线程调度,它将得到执行。

8. 你对线程优先级的理解是什么?
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现
线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。

9. 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。
时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级
或者线程等待的时间。

10. 在多线程中,什么是上下文切换(context-switching)?
上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。

11. 你如何确保main()方法所在的线程是Java程序最后结束的线程?
我们可以使用Thread类的joint()方法来确保所有程序创建的线程在main()方法退出前结束。

12.线程之间是如何通信的?
当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。
Object类中wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。

13.为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?
在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,
这样Java的每一个类都有用于线程间通信的基本方法

14. 为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?
由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法
或者同步块中被调用。

15. 为什么Thread类的sleep()和yield()方法是静态的?
Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用
这些方法是没有意义的。

16.如何确保线程安全?
在Java中可以有很多方法来保证线程安全——同步

17. volatile关键字在Java中有什么作用?
当我们使用volatile关键字去修饰变量的时候,所以线程都会直接读取该变量并且不缓存它。
这就确保了线程读取到的变量是同内存中是一致的。

18. 同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个
对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。

19.如何创建守护线程?
使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在
调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。

20. 什么是ThreadLocal?
ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量
不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程
内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。

21. 什么是Thread Group?为什么不建议使用它?
ThreadGroup是一个类,它的目的是提供关于线程组的信息。
ThreadGroup API比较薄弱,它并没有比Thread提供了更多的功能。它有两个主要的功能:
一是获取线程组中处于活跃状态线程的列表;二是设置为线程设置未捕获异常处理器
(ncaught exception handler)。

22. 什么是死锁(Deadlock)?如何分析和避免死锁?
死锁是指两个以上的线程永远阻塞的情况,这种情况产生至少需要两个以上的线程和两个以上的资源。

23. 什么是Java Timer类?如何创建一个有特定时间间隔的任务?
java.util.Timer是一个工具类,可以用于安排一个线程在未来的某个特定时间执行。Timer类可以
用安排一次性任务或者周期任务。
java.util.TimerTask是一个实现了Runnable接口的抽象类,我们需要去继承这个类来创建我们自己的
定时任务并使用Timer去安排它的执行。

25. 什么是线程池?如何创建一个Java线程池?
一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。

java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于
创建线程池。

1. 什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。
int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,
这就会引发错误。
为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。

2. Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有
完全不同的性质,并且可以支持多个相关类的条件对象。

3. 什么是Executors框架?
Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。Executor框架是一个根据
一组执行策略调用,调度,执行和控制的异步任务的框架。

无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制
线程的数量并且可以回收再利用这些线程。

4. 什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
java.util.concurrent.BlockingQueue的特性是:当队列是空的时,从队列中获取或删除元素的操作将会
被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。

BlockingQueue 接口是java collections框架的一部分,它主要用于实现生产者-消费者问题。

5. 什么是Callable和Future?
Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,
但它可以返回一个对象或者抛出一个异常。

Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。


关于synchronized关键字的认识

1、Java线程生命周期
Java线程生命周期从大的方面划分,可以分为四个状态,即新建、就绪、阻塞和死亡。

新建(new):分配了必须的系统资源,并执行了初始化。 
新建状态下的线程表示可以获得CPU时间片,调度器可以将其转化为runnable或者blocked状态。

就绪(runnable): 就绪状态表示线程处于随时可运行的状态,也包括了正在运行的状态
当线程在就绪状态时,只要调度器把时间片分配给线程,线程就能运行。

阻塞(blocked):当线程处于阻塞状态时,调度器将会忽略线程,不会分配给线程任何CPU
时间 也就是说阻塞和就绪两状态区别在于,调度器可以给就绪状态线程分配CPU时间片,
而无法给阻塞状态线程分配CPU时间片,只有当线程变成了runnable状态,它才有可能被执行。

结束(terminated):处于结束状态的线程是不可调度的,并且再也不会得到CPU时间片 
这个结束状态比较好理解,不过需要提的是一般而言,线程是在run方法运行完成后,
就进入了结束状态。

这四个状态新建(new)和结束(terminated)没啥好说的,平时在线程运行过程中,我们一般关注的都是
就绪(runnable)以及阻塞(blocked)两个状态,而且这两个状态在一定条件下可以发生转化

2、wait 和 notify
控制线程的就绪(runnable)和阻塞(blockded)两状态的方式之一。
Java的所有对象就继承自Object类,该类中定义了wait和notify方法,而且都是final的native方法

3、synchronized关键字使用
终于讲到synchronized关键字了,synchronized关键字有三种使用方式
修饰实例方法:作用于当前实例加锁,进入同步方法代码前要获得当前实例的锁
修饰静态方法:作用于当前类对象加锁,进入同步方法代码前要获得当前类对象的锁
修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁

4、说说wait和sleep
wait和sleep都可以使得当前线程处于阻塞状态,失去CPU时间

但是相似之外最大的还是区别
1、wait是针对线程锁而言的,wait使得当前线程失去线程锁并且没有被notify时,将没有机会再获取
线程锁,而sleep没有线程锁的概念,只是单纯的让线程失去CPU时间,一旦sleep时间已过,线程便处于
就绪(runnable)状态,随时可以获取CPU时间。
2、wait是Object实例方法,而sleep是Thread的静态方法;

java中synchronized关键字的用法

在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法。

java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块
或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入
这个锁的保护的同步代码块或方法。

java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内
置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是
有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类
的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象
实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的
东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

synchronized的用法:synchronized修饰方法和synchronized修饰代码块。

public class TestSynchronized 
{  
    public void test1() 
    {  
         synchronized(this) 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
    public synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
    public static void main(String[] args) 
    {  
         final TestSynchronized myt2 = new TestSynchronized();  
         Thread test1 = new Thread(  new Runnable() {  public void run() {  
myt2.test1();  }  }, "test1"  );  
         Thread test2 = new Thread(  new Runnable() {  public void run() { 
myt2.test2();   }  }, "test2"  );  
         test1.start();;  
         test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();
    } 
}

test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0
test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0

因为第一个同步代码块传入的this,所以两个同步代码所需要获得的对象锁都是同一个对象锁,
下面main方法时分别开启两个线程,分别调用test1和test2方法,那么两个线程都需要获得该对象锁,
另一个线程必须等待。

运行的结果可以看到:直到test2线程执行完毕,释放掉锁,test1线程才开始执行。

可能这个结果有人会有疑问,代码里面明明是先开启test1线程,为什么先执行的是test2呢?这是因为java
编译器在编译成字节码的时候,会对代码进行一个重排序,也就是说,编译器会根据实际情况对代码进行一个
合理的排序,编译前代码写在前面,在编译后的字节码不一定排在前面,所以这种运行结果是正常的, 
这里是题外话,最主要是检验synchronized的用法的正确性

如果我们把test2方法的synchronized关键字去掉,执行结果会如何呢?

test1 : 4
test2 : 4
test2 : 3
test1 : 3
test1 : 2
test2 : 2
test2 : 1
test1 : 1
test2 : 0
test1 : 0
 
1.我们可以看到,结果输出是交替着进行输出的,这是因为,某个线程得到了对象锁,但是另一个线程还是
可以访问没有进行同步的方法或者代码。
2.进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了
同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。
3.当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,
只能阻止其他线程获得同一个锁。
4.之所以每个对象都有一个内置锁,是为了免去显式地创建锁对象。

所以synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就表明要获得
该内置锁才能执行,并不能阻止其他线程访问不需要获得该内置锁的方法。

类锁的修饰(静态)方法和代码块:

public class TestSynchronized 
{  
    public void test1() 
    {  
         synchronized(TestSynchronized.class) 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
    public static synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
    public static void main(String[] args) 
    {  
         final TestSynchronized myt2 = new TestSynchronized();  
         Thread test1 = new Thread(  new Runnable() {  public void run() {  
myt2.test1();  }  }, "test1"  );  
         Thread test2 = new Thread(  new Runnable() {  
public void run() { TestSynchronized.test2();   }  }
, "test2"  );  
         test1.start();  
         test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();
    } 
}
 

test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0
test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0

其实,类锁修饰方法和代码块的效果和对象锁是一样的,因为类锁只是一个抽象出来的概念,只是为了区别
静态方法的特点,因为静态方法是所有对象实例共用的,所以对应着synchronized修饰的静态方法的锁也是
唯一的,所以抽象出来个类锁。

下面这块代码,synchronized同时修饰静态和非静态方法
public class TestSynchronized 
{  
    public synchronized void test1() 
    {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
    }  
    public static synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
    public static void main(String[] args) 
    {  
         final TestSynchronized myt2 = new TestSynchronized();  
         Thread test1 = new Thread(  new Runnable() {  public void run() {  
myt2.test1();  }  }, "test1"  );  
         Thread test2 = new Thread(  new Runnable() {  
public void run() { TestSynchronized.test2();   }  }, 
"test2"  );  
         test1.start();  
         test2.start();  
//         TestRunnable tr=new TestRunnable();
//         Thread test3=new Thread(tr);
//         test3.start();
    } 
}
 

test1 : 4
test2 : 4
test1 : 3
test2 : 3
test2 : 2
test1 : 2
test2 : 1
test1 : 1
test1 : 0
test2 : 0

上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,这证明了类锁和对象锁
是两个不一样的锁,控制着不同的区域,它们是互不干扰的。
同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。

synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,
必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。
如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要
永远的等待。这是一个致命的问题。

这个类里面声明了一个对象实例,SynObject so=new SynObject();在某个方法里面调用了这个实例的
方法so.testsy();但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法。

这时如果直接用synchronized修饰调用了so.testsy();代码的方法,那么当某个线程进入了这个方法
之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程
会一直阻塞,影响到系统的性能。

如果这时用synchronized来修饰代码块:synchronized(so){so.testsy();},那么这个方法加锁的对象
是so这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时
没有影响的,因为他们持有的锁都完全不一样。

一个类的对象锁和另一个类的对象锁是没有关联的,当一个线程获得A类的对象锁时,它同时也可以获得
B类的对象锁。

打个比方:一个object就像一个大房子,大门永远打开。房子里有 很多房间(也就是方法)。

这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。

普通情况下钥匙的使用原则是:“随用随借,用完即还。”

这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。
但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,
就只能等了。

要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。象前面例子里那个想
连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能
再次拿到。 (JAVA规范在很多地方都明确说明不保证,像Thread.sleep()休息后多久会返回运行,相同
优先权的线程那个首先被执行,当要访问对象的锁被 释放后处于等待池的多个线程哪个会优先得到,等等。
我想最终的决定权是在JVM,之所以不保证,就是因为JVM在做出上述决定的时候,绝不是简简单单根据 一个
条件来做出判断,而是根据很多条。而由于判断条件太多,如果说出来可能会影响JAVA的推广,也可能是
因为知识产权保护的原因吧。SUN给了个不保证 就混过去了。无可厚非。但我相信这些不确定,并非完全
不确定。因为计算机这东西本身就是按指令运行的。即使看起来很随机的现象,其实都是有规律可寻。学过
计算机的都知道,计算机里随机数的学名是伪随机数,是人运用一定的方法写出来的,看上去随机罢了。
另外,或许是因为要想弄的确太费事,也没多大意义,所 以不确定就不确定了吧。


看看同步代码块。和同步方法有小小的不同。

1.从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风
隔开的空间。

2.同步代码块还可以人为的指定获得某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,
你可以用本房的钥匙;你也可以指定用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把
那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。

为什么要使用同步代码块呢?     
首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,再对这些变量做一些
操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响
范围

如何做?同步代码块。我们只把一个方法中该同 步的地方同步,比如运算。另外,同步代码块可以指定钥匙
这一特点有个额外的好处,是可以在一定时期内霸占某个对象的key。还记得前面说过普通情况下钥匙的使用
原则吗。现在不是普通情况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值