锁对象的改变:
关于锁,如果多个线程争的是一个锁,那么就是同步的,如果不是一个锁就是异步的。
那么关键点就在于是不是同一个锁,如果在运行过程中锁改变了,那么变成异步的话就会出现问题,需注意。
volatile 关键字:
在多个线程中可见,不具备原子性。
线程中的实例属性在 -server 模式中,线程会一直在私有堆栈中取得,但是如果我们在主线程中更新了这个标识位,那么只是更新了公共堆栈。
所以为了解决这个问题,volatile关键字修饰的属性会强制从公共堆栈中取值。
如下:
static class MyThread extends Thread{ private volatile boolean isRun = false; public void setRun (boolean run){ isRun = run; } @Override public void run() { System.out.println("begin"); while (!isRun){ System.out.println(Thread.currentThread().getName()); } System.out.println("end"); } } public static void main(String[] args){ Thread t = new MyThread(); t.start(); try { Thread.sleep(1); ((MyThread) t).setRun(true); } catch (InterruptedException e) { e.printStackTrace(); } }
输出:
注意(与synchronized 比较):
1.volatile 性能更好,但只能修饰属性变量
2.多线程访问volatile 不会发生阻塞
3.能保证可见性,但不能保证原子性
关于i++
它是非线程安全的,操作步骤:
1.从内存中取出i值
2.计算i的值
3.写入到内存
但是并不是同步的。
如:
private static volatile int i = 0; public static void main(String[] args){ Runnable run = new Runnable() { @Override public void run() { for (int j = 0;j < 10; j++) { i ++; System.out.println(Thread.currentThread().getName()+":: "+i); } } }; Thread t = new Thread(run); Thread t2 = new Thread(run); t.start(); t2.start(); }
输出:
从输出结果可以看出,由于不是同步操作,所以线程1和线程2乱序输出,线程1和线程2同时刚开始同时输出了2(这个例子中)。
加上synchronized
public void run() { for (int j = 0; j < 10; j++) { synchronized (this) { i++; System.out.println(Thread.currentThread().getName() + ":: " + i); } } }
输出:
第二种方式:使用原子类
private static AtomicInteger i = new AtomicInteger(0);
但原子类也有可能出现乱序。
如果在方法中使用了原子类,但是由于方法并不是同步的,所有有可能会先执行后面的方法。
遇到这样的情况,那就加上synchronized 。