看一个打印车票的例子
/** * 此程序存在线程的安全问题:打印车票时,会出现重票、错票 */ class Window1 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--); } else { break; } } } } public class TestWindow1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }经过多次测试,在打印台上能看到票号为0或者为负数的情况。
上述代码有安全问题,需要在代码中添加同步机制
使用synchronized来解决同步问题,有以下两种方式:
同步代码块: synchronized (对象){ // 需要被同步的代码 } synchronized还可以放在方法声明中,表示整个方法为同步方法。 public synchronized void show (String name){ // 需要被同步的代码 }要想使用好同步机制,我们需要进一步理解同步机制中的锁是什么?
同步锁机制: 在《Thinking in Java》中有一段解释:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁 之时,另一个任务就可以锁定并使用它了。
synchronized的锁是什么?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。同步方法的锁:静态方法(类名.class)、非静态方法(this)同步代码块:自己指定,很多时候也是指定为this或类名.class我们需要注意是:
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),在同步代码块中指定锁需谨慎,以免有安全问题。合理使用同步锁机制: 范围太小:没锁住所有有安全问题的代码 范围太大:没发挥多线程的功能
下面我们用同步锁机制来解决上述 打印车票的安全问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁; 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决方案:
专门的算法、原则尽量减少同步资源的定义尽量避免嵌套同步看一个死锁的例子
//死锁的演示 class A { public synchronized void foo(B b) { //同步监视器:A类的对象:a System.out.println("当前线程名: " + Thread.currentThread().getName() + " 进入了A实例的foo方法"); // ① // try { // Thread.sleep(200); // } catch (InterruptedException ex) { // ex.printStackTrace(); // } System.out.println("当前线程名: " + Thread.currentThread().getName() + " 企图调用B实例的last方法"); // ③ b.last(); } public synchronized void last() {//同步监视器:A类的对象:a System.out.println("进入了A类的last方法内部"); } } class B { public synchronized void bar(A a) {//同步监视器:b System.out.println("当前线程名: " + Thread.currentThread().getName() + " 进入了B实例的bar方法"); // ② // try { // Thread.sleep(200); // } catch (InterruptedException ex) { // ex.printStackTrace(); // } System.out.println("当前线程名: " + Thread.currentThread().getName() + " 企图调用A实例的last方法"); // ④ a.last(); } public synchronized void last() {//同步监视器:b System.out.println("进入了B类的last方法内部"); } } public class DeadLock implements Runnable { A a = new A(); B b = new B(); public void init() { Thread.currentThread().setName("主线程"); // 调用a对象的foo方法 a.foo(b); System.out.println("进入了主线程之后"); } @Override public void run() { Thread.currentThread().setName("副线程"); // 调用b对象的bar方法 b.bar(a); System.out.println("进入了副线程之后"); } public static void main(String[] args) { DeadLock dl = new DeadLock(); new Thread(dl).start(); dl.init(); } }测试结果:
当前线程名: 主线程 进入了A实例的foo方法 当前线程名: 副线程 进入了B实例的bar方法 当前线程名: 主线程 企图调用B实例的last方法 当前线程名: 副线程 企图调用A实例的last方法看测试结果我们发现,A和B类的last()方法内部的打印都没有打印输出,可以看出死锁了。
代码实现
import java.util.concurrent.locks.ReentrantLock; /** * 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增 * <p> * 1. 面试题:synchronized 与 Lock的异同? * 相同:二者都可以解决线程安全问题 * 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器 * Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()) * <p> * 2.优先使用顺序: * Lock 同步代码块(已经进入了方法体,分配了相应资源) 同步方法(在方法体之外) * <p> * <p> * 面试题:如何解决线程安全问题?有几种方式 * */ class Window implements Runnable { private int ticket = 100; //1.实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { try { //2.调用锁定方法lock() lock.lock(); if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; } else { break; } } finally { //3.调用解锁方法:unlock() lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window w = new Window(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }优先使用顺序: Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)