Java 中的线程同步问题:
1. 线程同步:
对于访问同一份资源的多个线程之间, 来进行协调的这个东西.
2. 同步方法:
当某个对象调用了同步方法时, 该对象上的其它同步方法必须等待该同步方法执行完毕后, 才能被执行.
3. 同步块:
通常将共享资源的操作放置在 synchronized 定义的区域内, 这样当其它线程也获取到这个锁时, 必须等待锁被释放, 才能进入该区域.
Demo_1:
class Timer { private static int num = 0; public void add(String name) { num++; try { Thread.sleep(100); // 即使不写 Thread.sleep(100), 这个结果可能是对的,也有可能是错的, 不确定. 中间还是有可能被打断. } catch (InterruptedException e) { } System.out.println(name+", 你是第 "+num+"个使用timer的线程"); } } class TestSync implements Runnable { Timer timer = new Timer(); public static void main(String[] args) { TestSync test = new TestSync(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } @Override public void run() { timer.add(Thread.currentThread().getName()); } }
会出现的运行结果:
t1, 你是第 2个使用timer的线程
t2, 你是第 2个使用timer的线程
原因:这个线程在执行 add()方法的时候, 被另外一个线程给打断了.
解决办法:
num++;
try {Thread.sleep(100);
} catch (InterruptedException e) {}
上面这几句话, 应该作为一个原子性的输出, 你不应该在中途打断.
第一种解决办法:采用同步块, 如 Demo_2
Demo_2:
class Timer { private static int num = 0; public void add(String name) { synchronized(this){ // 同步块 num++; try {Thread.sleep(100); } catch (InterruptedException e) {} System.out.println(name+", 你是第 "+num+"个使用timer的线程"); } } } class TestSync implements Runnable { Timer timer = new Timer(); public static void main(String[] args) { TestSync test = new TestSync(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } @Override public void run() { timer.add(Thread.currentThread().getName()); } } // 运行结果如下: // t1, 你是第 1个使用timer的线程 // t2, 你是第 2个使用timer的线程
【注】:既然锁定了当前对象了, 那么这个 num 也就锁定了, 它里面的成员变量当然也锁定(互斥锁).
【注】:锁定当前对象:这执行后面的大括号里面的语句的过程中, 一个线程的执行过程中, 不会被另外一个线程锁打断.
一旦某个线程已经进入到锁定的区域当中, 那么你放心, 不可能有另外一个线程也在里面(锁的机制).
第二种解决办法:采用同步块, 如 Demo_3
Demo_3:
class Timer { private static int num = 0; public synchronized void add(String name) { //同步方法 num++; try {Thread.sleep(100); } catch (InterruptedException e) {} System.out.println(name+", 你是第 "+num+"个使用timer的线程"); } } class TestSync implements Runnable { Timer timer = new Timer(); public static void main(String[] args) { TestSync test = new TestSync(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } @Override public void run() { timer.add(Thread.currentThread().getName()); } } // 运行结果如下: // t1, 你是第 1个使用timer的线程 // t2, 你是第 2个使用timer的线程
分析 Demo_3 的执行过程:
t1 开始执行, 调用 add() 方法, num++, 然后 t1 睡着了, 睡着了也没关系, t1 睡着也抱着那把锁.
别人(t2)也进不来, 你必须等它执行完了, 你才可以继续执行.
睡着了, 也不放开那把锁, 你也没办法.
4. 线程同步总结:
4.1. 在 Java 语言中, 引入了对象互斥锁的概念, 保证共享数据操作的完整性. 每个对象都对应于一个可称为"互斥锁"的标记,
这个标记保证在任一时刻, 只有一个线程访问该对象.
4.2. 关键字 synchronized 与对象的互斥锁联系. 当某个对象用 synchronized 来修饰时, 表明该对象在任一时刻只能由一个线程访问.