在多线程的操作中,多个线程有可能同时处理同一资源,这就是多线程的共享数据。
如下程序:
public class ThreadDemo {
public static void main(String[] args) {
MyThread my = new MyThread();
Thread t1 = new Thread(my,"小白");
Thread t2 = new Thread(my,"小黑");
t1.start();
t2.start();
}
}
class MyThread implements Runnable{ // 共享数据
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在用打印机");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"用完打印机");
}
}
某次运行结果:
小白正在用打印机
小黑正在用打印机
小白用完打印机
小黑用完打印机
这个结果明显存在问题,对于打印机这个共享资源,一次只能有一个使用者使用而不能同时使用,这样就会引起共享数据的不安全问题。
为了解决上面的问题,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是代码块
线程同步:
解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可继续执行。
线程进行同步,有如下两种方式:
(1):同步代码块:
Synchronized (要同步的对象){
要同步的操作
}
上面语法格式中synchronized后括号里的obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得同步监视器的锁定。
注意: 任何时刻,只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。
虽然java程序允许任何对象作为同步监视器,但是监视器的目的是:阻止两个线程对同一资源进行并发访问,因此通常推荐使用可能 并发访问的资源充当同步器,
访问同步资源的过程为:加锁------修改----------释放锁。
如下:
class MyThread implements Runnable{
Object obj = new Object();// 同步的标记对象
@Override
public void run() {
// 同步代码块(同步标记对象,也可以同步this)
synchronized (obj) {
System.out.println(Thread.currentThread().getName()+"正在用打印机");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"用完打印机");
}
}
}
某次运行结果:
小白正在用打印机
小白用完打印机
小黑正在用打印机
小黑用完打印机
这样共享数据的使用就是安全的。
(2)同步方法:
Public synchronized void method (){
要同步的操作
}
同步方法就是使用synchronized关键字修饰某个方法 ,则该方法称为同步方法。对于synchronized修饰的方法无需显示的指出同步监视器,同步方法的监视器是this,也就是调用该方法的对象。
通过使用同步方法可以非常方便的实现多线程安全的类。
Synchronized 关键字可以修饰方法,可以修饰代码块,但不能修饰构造器,成员变量。
程序如下:
class MyThread implements Runnable{
Object obj = new Object();// 同步的标记对象
@Override
public void run() {
function ();
}
//同步方法(同步当前对象)
public synchronized void function (){
System.out.println(Thread.currentThread().getName()+"正在用打印机");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"用完打印机");
}
}
同步带来的问题:
同步是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响可采用如下措施:
1:不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步,
如果可变类有两种运行环境,单线程和多线程两种环境,则应该提供两种版本,即单线程不安全版和多线程安全版,在单线程中提供不安全版提高性能,在多线程环境下使用安全版。
温馨提示:
Jdk所提供的StringBuffer和StringBuilder就是为单线程环境和多线程提供环境的类,单线程环境使用StringBuilder提高性能,多线程环境下使用StringBuffer提高保证安全。
释放同步监视器的锁定:
任何代码进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,当修改完后就会释放锁,但是程序无法显示的释放监视器的锁定,线程会在下情况下释放锁:
(1):当前线程的同步方法,代码块结束,立即释放锁。
(2):当前线程在同步代码块,或方法中遇到 break或return方法
(3):当前线程在同步代码块,或方法中出现了未处理的error 或exceoption 导致了该代码块,该方法异常结束。
(4):当前线程在同步代码块,或方法时,程序执行了同步监视器对象的wait(),则当前线程暂停,并释放同步监视器。
如下情况线程不会释放同步监听器:
1:线程执行同步代码或同步方法的时候,程序调用Thread.sleep () or Thread.yield ()方法暂停当前程序,当前线程不会释放同步监视器。
2:线程执行同步代码块时,其他线程调用了该线程supspend()方法将该线程挂起。该线程不会释放同步监视器。
当编写synchronized块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能危险方面大有帮助。
使用同步有如下准则:
1:使代码块保持简短,把不随线程变化的预处理和后处理移出synchronized块
2:不要阻塞。
3:在持有锁的时候,不要对其它对象调用方法。(如果调用同步对象有可能出现死锁)