package demo01;
/**
* sychronized关键字
* 对某个对象加锁
*/
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized (o) {//任何线程要执行下面的代码,必须要先拿到o对象的锁
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count);
}
}
public static void main(String[] args) {
final T t = new T();
Thread thread1 = new Thread(t::m);
Thread thread2 = new Thread(t::m);
thread1.start();
thread2.start();
}
}
运行结果
Thread-0 count = 9
Thread-1 count = 8
图来自:https://blog.csdn.net/zl_StepByStep/article/details/88760572
每次new出一个毫无其他功能的对象就当锁的对象比较麻烦。所以可以用synchronized(this)
package demo2;
public class T {
private int count = 10;
public void m() {
synchronized (this) {//任何线程要执行下面的代码,必须先拿到this的锁
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count);
}
}
//m()等同于下面的m2()
public synchronized void m2() { //等同于在方法的代码执行时要synchronized(this)
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%scount =%d%n", Thread.currentThread().getName(), count);
}
public static void main(String[] args) {
final T t = new T();
Thread thread1 = new Thread(t::m);
Thread thread2 = new Thread(t::m);
thread1.start();
thread2.start();
}
}
运行结果
Thread-0 count = 9
Thread-1 count = 8
package demo3;
/**
* synchronized锁定静态方法
*/
public class T {
private static int count = 10;
public synchronized static void m() {
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count);
}
public static void mm() {
synchronized (T.class) { //T.class是Class中的一个对象,这里是不能用synchronized(this)的,因为静态方法是不需要new对象去访问的
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count);
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(T::m);
Thread thread2 = new Thread(T::m);
thread1.start();
thread2.start();
}
}
运行结果
Thread-0 count = 9
Thread-1 count = 8
没有锁住线程的run方法之前
package demo4;
public class T implements Runnable {
private int count = 10;
@Override
public /*synchronized*/ void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
T t = new T();
for (int i = 0; i < 5; i++) {
new Thread(t, "THREAD-" + i).start();
}
}
}
运行结果
THREAD-1 count = 9
THREAD-0 count = 8
THREAD-4 count = 5
THREAD-3 count = 6
THREAD-2 count = 7
注意:
start() 方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。从程序运行的结果可以发现,多线程程序是乱序执行。
锁住线程的run方法之后
package demo4;
public class T implements Runnable {
private int count = 10;
@Override
public synchronized void run() {
//加了synchronized这两条语句相当于是一个原子操作,一个run方法执行完毕释放了锁,下一个线程才能拿到锁执行run方法
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
T t = new T();
for (int i = 0; i < 5; i++) {
new Thread(t, "THREAD-" + i).start();
}
}
}
运行结果
THREAD-0 count = 9
THREAD-4 count = 8
THREAD-1 count = 7
THREAD-3 count = 6
THREAD-2 count = 5
答案是可以的。
package demo5;
/*同步方法与非同步方法是可以同时调用的。只有synchronized修饰的方法在运行过程中才需要申请锁,普通方法是不需要申请的*/
public class T {
public synchronized void m1() { //同步方法
System.out.println(Thread.currentThread().getName() + " m1.start... ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() { //非同步方法
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
}
}
运行结果:
t1 m1.start...
t2 m2
t1 m1 end
package demo6;
import java.util.concurrent.TimeUnit;
/*对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题,读到在写的过程中还没有完成的数据,可以对读方法加锁*/
public class Account {
private String name;
private double balance; //账户余额为成员变量 默认为0.0
public synchronized void set(String name, double balance) { //写
this.name = name;
try {
Thread.sleep(2000); //2s
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name) { //读
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(() -> a.set("张三", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("张三")); //0.0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("张三")); //100.0
}
}
运行结果
0.0
100.0
因为不加synchronized,count–和打印语句中间,有可能有别的线程来执行count–,导致前后数据不一致。加了synchronized这两条语句相当于是一个原子操作,一个run方法执行完毕释放了锁,下一个线程才能拿到锁执行run方法!
package demo6;
import java.util.concurrent.TimeUnit;
/*对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题,读到在写的过程中还没有完成的数据,可以对读方法加锁*/
public class Account {
private String name;
private double balance; //账户余额为成员变量 默认为0.0
public synchronized void set(String name, double balance) { //写
this.name = name;
try {
Thread.sleep(2000); //2s
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public synchronized double getBalance(String name) { //读
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(() -> a.set("张三", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("张三")); //0.0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("张三")); //100.0
}
}
运行结果
100.0
100.0
答案是可以的。
package demo7;
import java.util.concurrent.TimeUnit;
/*一个同步方法可以调用另外一个同步方法。一个线程已经拥有某个对象的锁,再次申请的时候任然会得到该对象的锁,即synchronized获得的锁是可重入的。*/
public class T {
synchronized void m1() {
System.out.println("m1 start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m1).start();
}
}
运行结果
m1 start...
m2
答案是可以的。
package demo8;
import java.util.concurrent.TimeUnit;
/*一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到锁的对象,即可重入的。
在继承中,子类同步方法可以调用父类的同步方法*/
public class T {
synchronized void m() {
System.out.println("m start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new Thread(new TT()::m).start(); //锁定的都是同一个对象(子类对象)
}
}
class TT extends T {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
运行结果
child m start
m start...
m end
child m end
package demo9;
import java.util.concurrent.TimeUnit;
/**
* 程序执行过程中,如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然会发生不一致的情况;
* 比如,在一个web application处理请求时,多个servlet线程共同访问同一个资源,这时如果异常处理不合适;
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据,因此要非常小心地处理同步业务逻辑中的异常。
*/
public class T {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start");
while (true) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1 / 0; //此处抛出异常,锁将会被释放。要想锁不被释放,可以在这里进行catch,然后循环继续
}
}
}
public static void main(String[] args) {
T t = new T();
Runnable r = t::m;
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
运行结果
t1 start
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
Exception in thread "t1" t2 start
t2 count = 6
java.lang.ArithmeticException: / by zero
at demo9.T.m(T.java:24)
at java.lang.Thread.run(Thread.java:745)
t2 count = 7
t2 count = 8
t2 count = 9
t2 count = 10
两个线程t1和t2争用同一个对象的锁,正常来说线程t1获取到t对象的锁之后就会一直在while进行循环,t2线程根本拿不到t对象的锁,因为t1线程还没执行完不会释放锁。但是当线程t1跑到count==5的时候会抛出一个异常,导致锁释放了,这个时候t2就获取到t对象的锁了。
解决方式就是进行try…catch
package demo9;
import java.util.concurrent.TimeUnit;
/**
* 程序执行过程中,如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然会发生不一致的情况;
* 比如,在一个web application处理请求时,多个servlet线程共同访问同一个资源,这时如果异常处理不合适;
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据,因此要非常小心地处理同步业务逻辑中的异常。
*/
public class T {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start");
while (true) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
try {
int i = 1 / 0; //此处抛出异常,锁将会被释放。要想锁不被释放,可以在这里进行catch,然后循环继续
} catch (Exception e) {
}
}
}
}
public static void main(String[] args) {
T t = new T();
Runnable r = t::m;
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
运行结果
t1 start
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
t1 count = 6
t1 count = 7
package demo10;
/**
* volatile关键字,使一个变量在多个线程间可见
* AB线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道使用volatile关键字,会让所有线程都会读到变量的修改值
* 在下面的代码中,running是存在于堆内存的t对象中
* 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存,
* 这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行使用volatile,
* 将会强制所有线程都去堆内存中读取running的值可以阅读这篇文章进行更深入的理解
* http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
*/
public class T {
/*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
void m() {
System.out.println("m start");
while (running) { //死循环。只有running=false时,才能执行后面的语句
}
System.out.println("m end");
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m, "t1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false; //若running不被volatile关键字修饰时,线程“看不见”running被修改了
}
}
运行结果
m start
package demo10;
/**
* volatile关键字,使一个变量在多个线程间可见
* AB线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道使用volatile关键字,会让所有线程都会读到变量的修改值
* 在下面的代码中,running是存在于堆内存的t对象中
* 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存,
* 这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行使用volatile,
* 将会强制所有线程都去堆内存中读取running的值可以阅读这篇文章进行更深入的理解
* http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
*/
public class T {
volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
void m() {
System.out.println("m start");
while (running) { //死循环。只有running=false时,才能执行后面的语句
}
System.out.println("m end");
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m, "t1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false; //若running不被volatile关键字修饰时,线程“看不见”running被修改了
}
}
运行结果
m start
m end
volatile 只保证原子性,而 synchronized 既保证可见性又保证原子性,但是synchronized太“重”了(效率很低)!
图来自:https://blog.csdn.net/zl_StepByStep/article/details/88760572
package demo11;
import java.util.ArrayList;
import java.util.List;
/*10个线程分别执行10000次count++,count是对象vna的成员变量,按理来说最终count=100000,
但是最终每次执行结果都不一样,count一直小于100000,说明volatile不具备原子性*/
public class T {
volatile int count = 0;
void m() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread" + i));
}
threads.forEach(Thread::start);
threads.forEach((o) -> {
try {
//join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续。通常用于在main()主线程内,等待其它线程完成再结束main()主线程。
o.join(); //相当于在main线程中同步o线程,o执行完了,main线程才有执行的机会
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
运行结果
84772
package demo12;
import java.util.ArrayList;
import java.util.List;
/*上例中,可以用synchronized解决,synchronized可以保证可见性和原子性,volatile只能保证可见性*/
public class T {
int count = 0;
synchronized void m() { //m方法加了synchronized修饰,保证了原子性和可见性
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach(Thread::start);
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count); //100000
}
}
运行结果
100000
package demo13;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但是不能保证多个方法连续调用是原子性的
*/
public class T {
AtomicInteger count = new AtomicInteger(0);
void m() {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet(); //原子操作
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach(Thread::start);
threads.forEach(o -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
运行结果
100000
package demo14;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 写一个程序,证明Atomxxx类比synchronized更高效
*/
public class T {
AtomicInteger atomicCount = new AtomicInteger(0);
int count = 0;
void m() {
for (int i = 0; i < 1000000; i++) {
atomicCount.incrementAndGet(); //原子操作
}
}
void m2() {
for (int i = 0; i < 1000000; i++) {
synchronized (this) {
count++;
}
}
}
public static void main(String[] args) {
T t1 = new T();
T t2 = new T();
long time1 = time(t1::m);
System.out.println(t1.atomicCount);
long time2 = time(t2::m2);
System.out.println(t2.count);
System.out.println(time1);
System.out.println(time2);
}
private static long time(Runnable runnable) {
List<Thread> threads = new ArrayList<>();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(runnable, "thread-" + i));
}
threads.forEach(Thread::start);
threads.forEach(o -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
}
运行结果
10000000
10000000
177
373
package demo15;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* AtomXXX类可以保证可见性吗?请写一个程序来证明
*/
public class T {
AtomicBoolean running = new AtomicBoolean(true);
void m() {
System.out.println("m start");
while (running.get()) { //死循环。只有running=false时,才能执行后面的语句
}
System.out.println("m end");
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m, "t1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running.getAndSet(false);
}
}
运行结果
m start
m end
package demo16;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 写一个程序证明AtomXXX类的多个方法并不构成原子性
*/
public class T {
AtomicInteger count = new AtomicInteger(0);
void m() {
for (int i = 0; i < 10000; i++) {
if (count.get() < 100 && count.get() >= 0) { //如果未加锁,之间还会有其他线程插进来
count.incrementAndGet();
}
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread" + i));
}
threads.forEach(Thread::start);
threads.forEach((o) -> {
try {
//join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续。通常用于在main()主线程内,等待其它线程完成再结束main()主线程。
o.join(); //相当于在main线程中同步o线程,o执行完了,main线程才有执行的机会
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
运行结果
期望结果是100,但是实际运行结果可能是101
101
package demo17;
import java.util.concurrent.TimeUnit;
/**
* synchronized优化
* 同步代码块中的语句越少越好
*/
public class T {
int count = 0;
synchronized void ml() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
count++;
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
//采用细粒度的锁,可以使线程争用时间变短,从而提高效率
synchronized (this) {
count++;
}
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package demo18;
import java.util.concurrent.TimeUnit;
/**
* 锁定某个对象o,如果o的属性发生改变,不影响使用.
* 但是如果o变成另外一个对象,则锁定的对象发生改变.
* 应该避免将锁定对象的引用变成另外对象
*/
public class T {
Object o = new Object();
void m() {
synchronized (o) {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
T t = new T();
//启动线程
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建第二个线程
Thread t2 = new Thread(t::m, "t2");
//锁定对象发生变化,所以t2线程得以进行,如注释掉这句话,线程2将永远得不到执行机会
//锁是锁在堆内存 不是锁在栈内存
t.o = new Object();
t2.start();
}
}
运行结果
t1
t1
t1
t2
t1
t2
t1
t2
t1
package demo19;
/**
* 不要以字符串常量作为锁定对象
* 在下面m1 m2 其实锁定的是同一个对象
* 这种情况下还会发生比较诡异的现象,比如你用到了一个类库,在该类库中的代码锁定了"Hello",
* 但是你读不到源码,所以你在自己的代码中锁定了"Hello",这时候有可能发生非常诡异的死锁阻塞,
* 因为你的程序和你用到的类库不经意间使用了同一把锁.
*/
public class T {
String s1 = "Hello";
String s2 = "Hello";
void m1() {
synchronized (s1) {
while (true) {
System.out.println("m1");
}
}
}
void m2() {
synchronized (s2) {
while (true) {
System.out.println("m2");
}
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m1).start();
new Thread(t::m2).start();
}
}
运行结果
m1
m1
m1
m1
m1
m1
m1
m1
m1
m1
只有调用m1方法的线程能够执行。
package demo20;
public class T {
private Object o1 = new Object();
private Object o2 = new Object();
public void m1() {
synchronized (o1) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("如果出现这句话表示没有死锁");
}
}
}
public void m2() {
synchronized(o2) {
synchronized (o1) {
System.out.println("如果出现这句话表示没有死锁");
}
}
}
public static void main(String[] args) {
T t=new T();
new Thread(t::m1).start();
new Thread(t::m2).start();
}
}
package demo21;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 曾经的面试题
* 实现一个容器,提供两个方法,add,size
* 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束
*
* 分析下面这个程序,能完成这个功能?
*/
public class MyContainer1 {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer1 myContainer = new MyContainer1();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
myContainer.add(new Object());
System.out.println("add " + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
while (true) {
if (myContainer.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}
运行结果
add 0
add 1
add 2
add 3
add 4
add 5
add 6
add 7
add 8
add 9
我们可以发现在size等于5的时候t2并没有结束,原因就因为没有加volatile关键字,我们需要添加volatile关键字,保证t2线程能够得到通知。
package demo21;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 曾经的面试题
* 实现一个容器,提供两个方法,add,size
* 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束
*
* 分析下面这个程序,能完成这个功能?
*
* 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。
* 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。
* 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。
*
* t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做?
*/
public class MyContainer2 {
volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer2 myContainer = new MyContainer2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
myContainer.add(new Object());
System.out.println("add " + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
while (true) {
if (myContainer.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}
运行结果
add 0
add 1
add 2
add 3
add 4
t2 结束
add 5
add 6
add 7
add 8
add 9
虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。
if (myContainer.size() == 5)
这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。while (true)
很浪费CPU,如果不用死循环应该怎么做?package demo21;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 曾经的面试题
* 实现一个容器,提供两个方法,add,size
* 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束
*
* 分析下面这个程序,能完成这个功能?
*
* 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。
* 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。
* 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。
*
* t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做?
*
*
* 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
* 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。
*
*
* 阅读下面的程序,并分析输出的结果
* 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
* 想想这是为什么?
*
* 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
* 当 notify/notifyAll() 被执行时,会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,
* 直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
*
*
* 这里并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出的原因是
* 当t2线程的中判断size不等于5,调用wait方法,等待被t1叫醒,并且释放当前的lock对象的锁。
* 然后t1线程在执行中判断size等于5了,就会调用notify方法,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。
* 只有等到t1结束之后,也就是执行完synchronized 代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。
*/
public class MyContainer3 {
volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer3 myContainer = new MyContainer3();
final Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
System.out.println("t2 启动");
if (myContainer.size() != 5) {
try {
lock.wait(); //size不等于5时,就一直在那等着,直到被t1叫醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 启动");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
myContainer.add(new Object());
System.out.println("add " + i);
if (myContainer.size() == 5) {
lock.notify(); //唤醒等待的t2线程
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}
运行结果
t2 启动
t1 启动
add 0
add 1
add 2
add 3
add 4
add 5
add 6
add 7
add 8
add 9
t2 结束
输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出,想想这是为什么?
原因是:当t2线程的中判断size不等于5,调用wait方法,等待被t1线程叫醒,并且释放当前的lock对象的锁。然后t1线程获取到锁,并在执行到判断size等于5了,就会调用notify方法去唤醒等待的线程t2,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。
只有等到t1结束之后,也就是执行完synchronized代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。
解决方式如下:
package demo21;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 曾经的面试题
* 实现一个容器,提供两个方法,add,size
* 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束
*
* 分析下面这个程序,能完成这个功能?
*
* 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。
* 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。
* 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。
*
* t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做?
*
*
* 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
* 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。
*
*
* 阅读下面的程序,并分析输出的结果
* 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
* 想想这是为什么?
*
* 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
* 当 notify/notifyAll() 被执行时,会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,
* 直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
*
*
* 这里并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出的原因是
* 当t2线程的中判断size不等于5,调用wait方法,等待被t1叫醒,并且释放当前的lock对象的锁。
* 然后t1线程在执行中判断size等于5了,就会调用notify方法,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。
* 只有等到t1结束之后,也就是执行完synchronized 代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。
*
*
* 解决方式如下
*/
public class MyContainer4 {
volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer4 myContainer = new MyContainer4();
final Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
System.out.println("t2 启动");
if (myContainer.size() != 5) {
try {
lock.wait(); //size不等于5时,就一直在那等着,直到被t1叫醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
lock.notify(); //通知线程t1继续执行
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 启动");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
myContainer.add(new Object());
System.out.println("add " + i);
if (myContainer.size() == 5) {
lock.notify(); //唤醒等待的t2线程,本线程继续执行,直至synchronized包裹的代码块结束或者调用了wait
try {
lock.wait(); //释放锁,让t2线程获取锁,让t2得以执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}
运行结果
t2 启动
t1 启动
add 0
add 1
add 2
add 3
add 4
t2 结束
add 5
add 6
add 7
add 8
add 9
上述使用wait和notify也解决了问题,但是有点过于复杂。java提供了门闩。
package demo21;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 曾经的面试题
* 实现一个容器,提供两个方法,add,size
* 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束
*
* 分析下面这个程序,能完成这个功能?
*
* 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。
* 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。
* 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。
*
* t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做?
*
*
* 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
* 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。
*
*
* 阅读下面的程序,并分析输出的结果
* 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
* 想想这是为什么?
*
* 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
* 当 notify/notifyAll() 被执行时,会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,
* 直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
*
*
* 这里并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出的原因是
* 当t2线程的中判断size不等于5,调用wait方法,等待被t1叫醒,并且释放当前的lock对象的锁。
* 然后t1线程在执行中判断size等于5了,就会调用notify方法,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。
* 只有等到t1结束之后,也就是执行完synchronized 代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。
*
*
* notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行整个通信过程比较繁琐
* 使用Latch(门闩)替代wait notify来进行通知
* 好处是通信方式简单,同时也可以指定等待时间
* 使用await和countdown方法替代wait和notify
* CountDownLatch不涉及锁定,当count的值为0时当前线程继续运行
* 当不涉及同步,只是涉及线程通信的时候,用synchronized+wait/notify就显得太重了
* 这时应该考虑countdownlatch/cyclicbarrier/semaphore
*/
public class MyContainer5 {
volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer5 myContainer = new MyContainer5();
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
System.out.println("t2 启动");
if (myContainer.size() != 5) {
try {
latch.await();
//也可以指定等待时间
//latch.await(5000,TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
myContainer.add(new Object());
System.out.println("add " + i);
if (myContainer.size() == 5) {
//打开门闩,让t2得以执行
latch.countDown();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("t1结束");
}, "t1").start();
}
}
运行结果
t2 启动
t1 启动
add 0
add 1
add 2
add 3
add 4
t2 结束
add 5
add 6
add 7
add 8
add 9
t1结束
https://www.bilibili.com/video/av57098526
https://www.bilibili.com/video/av33688545?p=20
https://cloud.tencent.com/developer/article/1521408
https://www.cnblogs.com/dddyyy/p/9965836.html
https://blog.csdn.net/zl_StepByStep/article/details/88760572
https://gitee.com/cckevincyh/java_concurrent_learning