线程之间的共享变量(比如之前悬念中的value变量)存储在主内存(main memory)中,每个线程都有一个私有的工作内存(本地内存),工作内存中存储了该线程以读/写共享变量的副本。
private static int value = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) value++;
System.out.println("线程1完成");
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) value++;
System.out.println("线程2完成");
});
t1.start();
t2.start();
Thread.sleep(1000); //主线程停止1秒,保证两个线程执行完成
System.out.println(value);
}
当两个线程同时读取value的时候,可能会同时拿到同样的值,而进行自增操作之后,也是同样的值,再写回主内存后,本来应该进行2次自增操作,实际上只执行了一次!
通过synchronized关键字来创造一个线程锁,首先我们来认识一下synchronized代码块,它需要在括号中填入一个内容,必须是一个对象或是一个类,我们在value自增操作外套上同步代码块:
private static int value = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (Main.class){ //使用synchronized关键字创建同步代码块
value++;
}
}
System.out.println("线程1完成");
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (Main.class){
value++;
}
}
System.out.println("线程2完成");
});
t1.start();
t2.start();
Thread.sleep(1000); //主线程停止1秒,保证两个线程执行完成
System.out.println(value);
}
注意:必须是使用同一个对象的锁
synchronized关键字也可以作用于方法上,调用此方法时也会获取锁,如果该方法是静态方法,那么就是以当前这个类作为锁
public class test {
private static int value = 0;
private static synchronized void add(){
value++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
add();
}
System.out.println("线程1完成");
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
add();
}
System.out.println("线程2完成");
});
Thread t3 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
value++;
}
System.out.println("线程3完成");
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.out.println(value);
}
}
如果我们测试上面的代码,只启动t1
和t2
线程的话,value
的值是正常的,但是如果还启动了t3
的话因为没有同步,所以结果是随机的
这个时候在t3
的线程中给value
加锁再自增,因为add()
是test
类的静态方法,所以使用synchronized(test.class)
加锁
如果synchronized关键字作用于非static方法上时,则是以该类的实例化对象作为锁
public class test {
private static int value = 0;
private synchronized void add(){
value++;
}
public static void main(String[] args) throws InterruptedException {
test t = new test();//test的实例化对象
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
t.add();
}
System.out.println("线程1完成");
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
t.add();
}
System.out.println("线程2完成");
});
Thread t3 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (t) {
value++;
}
}
System.out.println("线程3完成");
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000); //主线程停止1秒,保证两个线程执行完成
System.out.println(value);
}
}
wait()
和notify()
方法
wait()
、notify()
以及notifyAll()
,需要配合synchronized来使用
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
Thread t1 = new Thread(() -> {
synchronized (o1){
try {
System.out.println("开始等待");
o1.wait(); //进入等待状态并释放锁
System.out.println("等待结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (o1){
System.out.println("开始唤醒!");
o1.notify(); //唤醒处于等待状态的线程
for (int i = 0; i < 50; i++) {
System.out.println(i);
}
//唤醒后依然需要等待这里的锁释放之前等待的线程才能继续
}
});
t1.start();
Thread.sleep(1000);
t2.start();
}
我们可以发现,对象的wait()
方法会暂时使得此线程进入等待状态,同时会释放当前代码块持有的锁,这时其他线程可以获取到此对象的锁,当其他线程调用对象的notify()
方法后,会唤醒刚才变成等待状态的线程(这时并没有立即释放锁)。注意,必须是在持有锁(同步代码块内部)的情况下使用,否则会抛出异常!
notifyAll其实和notify一样,也是用于唤醒,但是前者是唤醒所有调用wait()
后处于等待的线程,而后者是看运气随机选择一个。