同步

来源:互联网 发布:数据分析报告案例 编辑:程序博客网 时间:2024/04/27 22:57

1.线程间的相互干扰

笔者上一篇文章讲到使用Runnable来实现多线程可以共享资源,但是这也带来了问题。我们来看上一篇文章的一段代码

public class MyThread implements Runnable {private int ticket = 5;public void run() {while (true) {if (ticket > 0) {//加入休眠使效果更明显try {Thread.sleep(4000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}//输出正在运行的线程名字以及剩余票数System.out.println(Thread.currentThread().getName()+"正在售票" + ticket--);}}}}public class ThreadTest {public static void main(String[] args) {MyThread mt=new MyThread();//启动线程new Thread(mt).start();new Thread(mt).start();new Thread(mt).start();}}

当运行这段代码时可能会出现以下情况(这种错误的出现不可预测,笔者也是运行了多次程序才出现):Thread-0正在售票0

Thread-2正在售票-1

按道理来说这两种情况是不应该出现的,那么造成这种错误的原因是什么呢?

这是因为有多个线程对同时对ticket进行了操作。我们来模拟这个过程:

整个线程运行的过程是获取当前ticket的值,进入判断语句若符合条件则休眠4秒,然后输出当前ticket的值,再将ticket减1然后返回。

假设线程1获得了当前Ticket的值1,但是还没有来的及将ticket减1返回。但是此时线程0和线程2已经先后进入判断语句,就在这时ticket减1了,然后线程0输出的就是减1后的结果0了,线程0再减1返回,线程2获得的是再次减1的结果,即-1.

2.内存一致性错误

int count=0

假设有A和B两个线程共享这个变量。A线程对变量进行count++操作,线程B对变量进行输出操作System.out.println(count);

这是线程B输出的结果可能是0。因为线程A对变量的操作对线程B不可见。

解决以上两个错误的方法之一就是使用同步。

3.同步方法

定义同步方法的格式:访问修饰符 synchronized 返回值类型 方法名(数据类型 变量名){

方法体;

}

以上文中的代码为例

public class MyThread implements Runnable {private int ticket = 5;public void run() {while (true) {this.sale();}}        /*        *同步方法        */public synchronized void sale(){if (ticket > 0) {//加入休眠使效果更明显try {Thread.sleep(4000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}//输出正在运行的线程名字以及剩余票数System.out.println(Thread.currentThread().getName()+"正在售票" + ticket--);}}}public class ThreadTest {public static void main(String[] args) {MyThread mt=new MyThread();//启动线程new Thread(mt).start();new Thread(mt).start();new Thread(mt).start();}}

4.同步代码块

同步代码块定义格式:synchronized(){

需要同步的代码;

}

public class MyThread implements Runnable {private int ticket = 5;public void run() {while (true) {//同步代码块                          synchronized (this) {if (ticket > 0) {// 加入休眠使效果更明显try {Thread.sleep(4000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 输出正在运行的线程名字以及剩余票数System.out.println(Thread.currentThread().getName() + "正在售票" + ticket--);}}}}}public class ThreadTest {public static void main(String[] args) {MyThread mt=new MyThread();//启动线程new Thread(mt).start();new Thread(mt).start();new Thread(mt).start();}}

注意:1)使用太多的同步容易出现死锁,关于死锁本篇文章不做说明,笔者将另写一篇。

2)原子性动作是指一次性完成的动作,即要么一次执行完所有的动作要么不执行,volatile声明的所有变量都是原子性动作。使用volati可以避免线程互相干扰,减少内存一致性错误。