今天脑子里突然冒出这个问题,于是自己写了下。
目录
方法1:利用volatile进行同步
方法2:利用wait()/notify()
方法3:Condition
方法4:信号量Semaphore
方法5:AQS
方法6: LockSupport
方法1:利用volatile进行同步
volatile是可以同步的,这个关键字保证了内存可见性。那么只要你的操作是原子的就可以保证线程安全。什么叫原子的呢?
falg = false;这种就属于原子的。
而i++这种不属于原子操作。
所以我们思路如下:利用一个volatile修饰的变量,控制线程调度。代码如下:
public class Test2 {
static int i = 1;
static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(()->{
while (true) {
if(flag) {
System.out.println(
Thread.currentThread().getName()+" : "+(i++));
flag = false;
}
}
}).start();
new Thread(()->{
while (true) {
if(!flag) {
System.out.println(
Thread.currentThread().getName()+" : "+(i++));
flag = true;
}
}
}).start();
}
}
很简单吧?就是有点费CPU。。。
不过这种方法可以控制多个线程协作的顺序。
方法2:利用wait()/notify()
public class Test2 {
static int i = 1;
//锁
static final Object lockObj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(new Task()).start();
// 让第二个线程启动
Thread.sleep(100);
new Thread(new Task()).start();
}
}
class Task implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Test2.lockObj) {
System.out.println(
Thread.currentThread().getName()+" : "+(Test2.i++));
Test2.lockObj.notifyAll();
try {
// 注意此操作会释放掉lockObj这个锁。
Test2.lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 我在这里休眠是为了提醒你,
// 只有synchronized释放掉锁,另一个线程才从有机会从wait()中被唤醒;
// 而不是notify()/notifyALl()后被就唤醒了
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
方法3:Condition
有wait()当然也有Condition啦,同方法2差不多:
public class Test2 {
static int i = 1;
//锁
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(new Task()).start();
// 让第二个线程启动
Thread.sleep(100);
new Thread(new Task()).start();
}
}
class Task implements Runnable {
@Override
public void run() {
while (true) {
Test2.lock.lock();
System.out.println(
Thread.currentThread().getName()+" : "+(Test2.i++));
Test2.condition.signalAll();
try {
Test2.condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Test2.lock.unlock();
}
}
}
方法4:信号量Semaphore
注意信号量Semaphore可以设置获取策略是否公平。如果不公平(false),那么结果就错了。
public class Test2 {
static int i = 1;
//true:公平锁.否则结果不对
static Semaphore semaphore = new Semaphore(1,true);
public static void main(String[] args) throws InterruptedException {
new Thread(new Task()).start();
// 让第二个线程启动
Thread.sleep(100);
new Thread(new Task()).start();
}
}
class Task implements Runnable {
@Override
public void run() {
while (true) {
try {
// 获取不到就会阻塞在这里
Test2.semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
Thread.currentThread().getName()+" : "+(Test2.i++));
Test2.semaphore.release();
}
}
}
方法5:AQS
其实上面的condition、Semaphore这些底层都是AQS。
那么我们何不自己实现一个?简单介绍些AQS:
AQS中有个状态state,我们的加锁,解锁都可以根据state来变。
具体到这个demo:
我们用公平锁实现,重写tryAcquire(int arg)和tryRelease(int arg)即可。
我们假设是这样的逻辑:(彩色框内数字就是state的值)
代码如下:
public class Test2 {
static int i = 1;
public static void main(String[] args) throws InterruptedException {
final Sync sync = new Sync(0);
//new CountDownLatch()
new Thread(new Task(sync,0)).start();
// 让第二个线程启动
Thread.sleep(100);
new Thread(new Task(sync,2)).start();
}
}
class Sync extends AbstractQueuedSynchronizer {
Sync(int state) {
setState(state);
}
/**
* CAS失败则阻塞
* @param arg
* @return
*/
@SneakyThrows
@Override
public boolean tryAcquire(int arg) {
if(compareAndSetState(arg,arg+1)) {
return true;
}
return false;
}
public boolean tryRelease(int arg) {
int state = getState();
if(state == 1) {
setState(2);
}else if(state == 3){
setState(0);
}
return true;
}
}
class Task implements Runnable {
Sync sync;
int initState;
public Task(Sync sync,int initState) {
this.sync = sync;
this.initState = initState;
}
@Override
public void run() {
while (true) {
// 获取不到就会阻塞在这里
sync.acquire(initState);
System.out.println(Thread.currentThread().getName()+" : "+(Test2.i++));
// 这个参数不写1也没事,因为我们tryRelease没用到
sync.release(1);
}
}
}
方法6: LockSupport
AQS的阻塞和唤醒也是利用的LockSupport类。所以方法5我们只是来熟悉下AQS而已。
public class Test2 {
static int i = 1;
static Thread t1 = null;
static Thread t2 = null;
public static void main(String[] args) throws InterruptedException {
t1 = new Thread(()->{
while (true) {
// 获取不到就会阻塞在这里
LockSupport.park();
System.out.println(Thread.currentThread().getName()+" : "+(Test2.i++));
// 这个参数不写1也没事,因为我们tryRelease没用到
LockSupport.unpark(t2);
}
});
t2 = new Thread(()->{
while (true) {
// 获取不到就会阻塞在这里
LockSupport.park();
System.out.println(Thread.currentThread().getName()+" : "+(Test2.i++));
// 这个参数不写1也没事,因为我们tryRelease没用到
LockSupport.unpark(t1);
}
});
t1.start();
t2.start();
// t1一直等人解锁呢1
LockSupport.unpark(t1);
}
}
class Task implements Runnable {
Thread otherThread;
public Task(Thread otherThread) {
this.otherThread = otherThread;
}
@Override
public void run() {
while (true) {
// 获取不到就会阻塞在这里
LockSupport.park();
System.out.println(Thread.currentThread().getName()+" : "+(Test2.i++));
// 这个参数不写1也没事,因为我们tryRelease没用到
LockSupport.unpark(otherThread);
}
}
}