同步
来源:互联网 发布:数据分析报告案例 编辑:程序博客网 时间: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可以避免线程互相干扰,减少内存一致性错误。