1、线程同步的必要性
前面说的线程都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部资源或方法,也不必关心其它线程的状态或行为;但是经常有一些同时运行的线程需要共享数据,此时就需要考虑其他线程的状态和行为,否则就不能保证程序运行结果的正确性。
2、实现线程同步
(1)当两个线程或多个线程需要访问同一资源时,需要以某种顺序来确保该资源在某一时刻只能被一个线程使用的方式称为线程同步。
(2)采用同步来控制线程的执行有两种方式,即同步方法和同步代码块。这两种方式都使用synchronized关键字实现。
同步方法:
1)通过在方法声明中加入synchronized关键字来声明同步方法;
2)使用synchronized修饰的方法控制对类成员变量的访问。每个类实例对应一把锁,方法一旦执行,就独占该锁,直到该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态;
3)这种机制确保了同一时刻对应每一个实例,其所有声明为synchronized的方法只能有一个处于可执行状态,从而有效的避免了类成员变量的访问冲突;
4)同步方法的语法格式如下:
访问修饰符 synchronized 返回类型 方法名(){} 或者 synchronized 访问修饰符 返回类型 方法名(){}
1)synchronized是同步关键字;
2)访问修饰符是指public、private等。
同步代码块:
同步代码块的语法格式如下:
synchronized(){
//需要同步访问控制的代码
}
需要注意的是:
1)synchronized块中的代码必须获得对象syncObject的锁,具体实现机制与同步方法一样。
2)由于同步代码块可以针对任意代码块,且可任意指定上锁的对象,故灵活性比较高。
死锁:
多线程在使用同步机制时,存在“死锁”的潜在危险。如果多个线程都处于等待状态而无法唤醒时,就构成了死锁(DeadLock),此时处于等待状态的多个线程占用系统资源,但无法运行,因此不会释放自身的资源。
在编程时应注意死锁问题,避免死锁的有效方法是:
1)线程因某个条件未满足而受阻,不能让其继续占有资源;
2)如果有多个对象需要互斥访问,应确定线程获得锁的顺序,并保证整个程序以相反的
3、线程间通信的必要性
在前面的介绍中,了解了多线程编程中使用同步机制的重要性,并介绍了如何通过同步来正确地访问共享资源。这些线程之间是相互独立的,并不存在任何的依赖关系。
它们各自竞争CPU资源,互不相让,并且还无条件地阻止其他线程对共享资源的异步访问。然而,有很多现实问题要求不仅要同步地访问同一共享资源,而且线程间还被彼此牵制,相互通信。
4、实现线程间通信
(1)Java提供了如下3个方法实现线程之间的通信:
1)wait()方法:调用wait()方法会挂起当前线程,并释放共享资源的锁。;
2)notify()方法:调用任意对象的notify()方法会在因调用该对象的wait()而阻塞的线程中随机选择一个线程解除阻塞,但要等到获得锁后才真正执行;
3)notifyAll()方法:调用了notifyAll()方法会将因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞;
(2)wait()、notify()和notifyAll()这3个方法都是Object类中final方法,被所有的类继承且不允许重写。这3个方法只能在同步方法或者同步代码块中使用,否则会抛出异常。