1、多窗口卖票
- 三个窗口卖100张票 ```json public class Main { public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
new Thread(myRunnable).start();
new Thread(myRunnable).start();
}
static class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
} else {
System.out.println(Thread.currentThread().getName() + "卖完了");
break;
}
}
}
}
}
- CPU在不同线程间切换,共享数据不是安全的;会出现卖了同一张票、不存在的票问题
- 最终原因:CPU在三个线程间轮流切换执行(例如CPU先执行线程1,执行到某一行代码,CPU又去执行了线程2,这期间的共享数据就是不准确的)<br />
```json
Thread-0正在卖第3张票
Thread-2正在卖第2张票
Thread-1正在卖第2张票
Thread-1卖完了
Thread-2正在卖第0张票
Thread-2卖完了
Thread-0正在卖第-1张票
Thread-0卖完了
这些线程安全问题,可以通过添加synchronized同步来解决
2、同步代码块
测试类 ```json public class Main { public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "SyncThread1").start();
new Thread(runnable, "SyncThread2").start();
/**
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
*/
// 使用的不同MyRunnable对象,所以不能同步
MyRunnable r1 = new MyRunnable();
MyRunnable r2 = new MyRunnable();
new Thread(r1, "SyncThread1").start();
new Thread(r2, "SyncThread2").start();
/**
SyncThread1:1
SyncThread2:0
SyncThread2:3
SyncThread1:2
SyncThread2:4
SyncThread1:5
SyncThread1:6
SyncThread2:6
SyncThread2:7
SyncThread1:7
*/
}
static class MyRunnable implements Runnable {
// 此处是静态变量,归类所有,所有的实例访问是同一个
public static int count;
public MyRunnable() {
count = 0;
}
public void run() {
// 此处锁的是MyRunnable对象
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
- 结论
- synchronized (this):锁的是当前MyRunnable对象
- 例子一结论:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞<br />
- 例子二结论:synchronized锁定的是对象,这时会有两把锁分别锁定r1对象和r2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行<br />
<a name="1cAMQ"></a>
### 3、同步非静态方法
- synchronized修饰一个非静态方法,锁的是Runnable的一个对象<br />
- synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数<br />
```json
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "SyncThread1").start();
new Thread(runnable, "SyncThread2").start();
/**
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
*/
// 使用的不同MyRunnable对象,所以不能同步
MyRunnable r1 = new MyRunnable();
MyRunnable r2 = new MyRunnable();
new Thread(r1, "SyncThread1").start();
new Thread(r2, "SyncThread2").start();
/**
SyncThread2:0
SyncThread1:1
SyncThread1:2
SyncThread2:2
SyncThread2:3
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread1:6
SyncThread2:6
*/
}
static class MyRunnable implements Runnable {
public static int count;
public MyRunnable() {
count = 0;
}
public synchronized void run() {
// 此处锁的是MyRunnable对象
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
结论
synchronized修饰一个静态方法,锁的是Runnable类
thread1和thread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以thread1和thread2相当于用了同一把锁
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "SyncThread1").start();
new Thread(runnable, "SyncThread2").start();
/**
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
*/
MyRunnable r1 = new MyRunnable();
MyRunnable r2 = new MyRunnable();
new Thread(r1, "SyncThread1").start();
new Thread(r2, "SyncThread2").start();
/**
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
*/
}
static class MyRunnable implements Runnable {
public static int count;
public MyRunnable() {
count = 0;
}
public void run() {
method();
}
// 此处锁的是MyRunnable类,包含所有类实例
private synchronized static void method() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
结论
- r1和r2是MyRunnable的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁
- r1和r2是MyRunnable的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁
5、同步类
- synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁
```json public class Main { public static void main(String[] args) {
// MyRunnable runnable = new MyRunnable(); // new Thread(runnable, “SyncThread1”).start(); // new Thread(runnable, “SyncThread2”).start();
/**
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
*/
MyRunnable r1 = new MyRunnable();
MyRunnable r2 = new MyRunnable();
new Thread(r1, "SyncThread1").start();
new Thread(r2, "SyncThread2").start();
/**
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
*/
}
static class MyRunnable implements Runnable {
public static int count;
public MyRunnable() {
count = 0;
}
public void run() {
synchronized(MyRunnable.class){
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
<a name="c3d3b779"></a>
### 5、synchronized使用限制
- synchronized关键字不能继承<br />
- 如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以<br />
```json
class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public synchronized void method() { }
}
当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了
class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public void method() { super.method(); }
}
在定义接口方法时不能使用synchronized关键字
interface MyClick{
// 此处不允许使用修饰符synchronized
synchronized void add();
}
构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步