synchronized同步对象概念
Object someObject =new Object();
synchronized (someObject){
//此处的代码只有占有了someObject后才可以执行
}
synchronized
表示当前线程独占对象 someObject
,如果有其他线程试图占有对象someObject
,就会等待,直到当前线程释放对someObject
的占用。someObject
又叫同步对象,或者称为对象监视器,所有的对象,都可以作为同步对象,为了达到同步的效果,必须使用同一个同步对象。
释放同步对象的方式: synchronized
块自然结束,或者有异常抛出。
对于synchronized
修饰的对象或方法,为了实现多线程交互,需要使用几个方法:wait()
, notify()
,notifyAll()
。
wait()
方法和notify()
方法,并不是Thread线程上的方法,它们是Object上的方法。因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait()
和notify()
是同步对象上的方法。
wait()
的意思是: 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait()
是有前提条件的,一定是在synchronized
块里,否则就会出错。
notify()
的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll()
的意思是,通知所有等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。
example1:请用三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串。
package multiplethread;
public class PrintABCUsingWaitNotify {
private int state;
private int times;
private static final Object monitor = new Object();
public PrintABCUsingWaitNotify(int times){
this.times = times;
}
public void printLetter(String name, int targetState){
for(int i=0; i<times; i++){
synchronized (monitor){
while (state%3!=targetState){
try {
monitor.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
state++;
System.out.print(name);
monitor.notifyAll();
}
}
}
public static void main(String[] args) {
PrintABCUsingWaitNotify printABCUsingWaitNotify = new PrintABCUsingWaitNotify(10);
new Thread(()->{printABCUsingWaitNotify.printLetter("A", 0); }, "A").start();
new Thread(()->{printABCUsingWaitNotify.printLetter("B", 1);}, "B").start();
new Thread(()->{printABCUsingWaitNotify.printLetter("C", 2);}, "C").start();
}
}
example2: 用两个线程交替打印0~10的整数,一个线程打印奇数,一个线程打印偶数
package multiplethread;
public class EvenOddPrinter {
private int limit;
private volatile int count;
private static final Object monitor = new Object();
public EvenOddPrinter(int count, int limit){
this.count = count;
this.limit = limit;
}
public void print0(){
synchronized (monitor){
while(count<=limit){
try {
System.out.println(String.format("线程[%s]正在打印%d", Thread.currentThread().getName(), count++));
monitor.notifyAll();
monitor.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
monitor.notifyAll();
}
}
public static void main(String[] args) {
EvenOddPrinter printer = new EvenOddPrinter(0, 10);
new Thread(()->{printer.print0();}, "even").start();
new Thread(()->{printer.print0();}, "odd").start();
}
}
example3:用两个线程,一个输出字母,一个输出数字,交替输出 1A2B3C4D...26Z
package multiplethread;
public class DigitLetterPrinter {
private static final Object monitor = new Object();
private volatile int i = 0;
private void print0(){
synchronized (monitor){
for(int i=0; i<26; i++){
if(Thread.currentThread().getName().equals("Digit")){
System.out.print(i+1);
monitor.notifyAll();
try{
monitor.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}else if(Thread.currentThread().getName().equals("Letter")){
System.out.print((char) (i+‘A‘));
monitor.notifyAll();
try {
monitor.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
monitor.notifyAll();
}
}
public static void main(String[] args) {
DigitLetterPrinter printer = new DigitLetterPrinter();
new Thread(()->{printer.print0();}, "Digit").start();
new Thread(()->{printer.print0();}, "Letter").start();
}
}
Lock是一个接口,为了使用一个Lock对象,需要用到
Lock lock = new ReentrantLock();
与synchronized (someObject)
类似的,lock()
方法,表示当前线程占用lock对象,一旦占用,其他线程就不能占用了。与 synchronized
不同的是,一旦synchronized
块结束,就会自动释放对someObject
的占用。Lock却必须调用unlock()
方法进行手动释放,为了保证释放的执行,往往会把unlock()
放在finally中进行。
还是用lock完成上面的example1:请用三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串。
package multiplethread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintABCUsingLock {
private int times;
private int state;
//private volatile int i;
private Lock lock = new ReentrantLock();
public PrintABCUsingLock(int times){
this.times = times;
}
private void print0(String name, int targetNum){
for(int i=0; i<times;){
lock.lock();
if(state%3==targetNum){
state++;
i++;
//System.out.println("state:" + state + " i: " +i);
System.out.print(name);
}
lock.unlock();
}
}
public static void main(String[] args) {
PrintABCUsingLock printer = new PrintABCUsingLock(10);
new Thread(()->{printer.print0("B", 1);}, "B").start();
new Thread(()->{printer.print0("C", 2);}, "C").start();
new Thread(()->{printer.print0("A", 0);}, "A").start();
}
}
main 方法启动后,3 个线程会抢锁,但是 state 的初始值为 0,所以第一次执行 if 语句的内容只能是 线程 A,然后还在 for 循环之内,此时 state = 1
,只有 线程 B 才满足 1% 3 == 1
,所以第二个执行的是 B,同理只有 线程 C 才满足 2% 3 == 2
,所以第三个执行的是 C,执行完 ABC 之后,才去执行第二次 for 循环,所以要把 i++ 写在 for 循环里边,不能写成 for (int i = 0; i < times;i++)
这样。这里我还是想不明白,state++和i++为什么不会同时执行?
使用synchronized
方式进行线程交互,用到的是同步对象的wait()
,notify()
和notifyAll()
方法,
Lock
也提供了类似的解决办法,首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:await()
, signal()
,signalAll()
方法.
注意: Object 中的 wait()
,notify()
,notifyAll()
方法是和"同步锁"(synchronized关键字)捆绑使用的;而 Condition 是需要与"互斥锁"/"共享锁"捆绑使用的。
example4:多线程按顺序调用,A->B->C,A打印 3 次,BB 打印2 次,CC 打印4次,重复 10 次
package multiplethread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintABCUsingLockCondition {
private int state;
private int times;
private static Lock lock = new ReentrantLock();
private static Condition c1 = lock.newCondition();
private static Condition c2 = lock.newCondition();
private static Condition c3 = lock.newCondition();
public PrintABCUsingLockCondition(int times){
this.times = times;
}
public void print0(String name, int targetState, Condition cur, Condition next, int count){
for(int i=0; i<times;){
lock.lock();
try {
while(state%3!=targetState){
cur.await();
}
for(int k=0; k<count; k++) System.out.print(name);
state++;
i++;
next.signal();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
PrintABCUsingLockCondition printer = new PrintABCUsingLockCondition(10);
new Thread(()->{printer.print0("A", 0, c1, c2, 3);}, "A").start();
new Thread(()->{printer.print0("B", 1, c2, c3, 2);}, "B").start();
new Thread(()->{printer.print0("C", 2, c3, c1, 4);}, "C").start();
}
}
synchronized 不占用到锁是不罢休的,会一直试图占用下去。与 synchronized 的钻牛角尖不一样,Lock接口还提供了一个trylock()
方法。trylock()
会在指定时间范围内试图占用,占成功了,执行相关的业务代码。 如果时间到了,还占用不成功,会放弃占用。
注意: 因为使用trylock()
有可能成功,有可能失败,所以后面unlock()
释放锁的时候,需要判断是否占用成功了,如果没占用成功也unlock()
,就会抛出异常。
package multiplethread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread() {
public void run() {
boolean locked = false;
try {
locked = lock.tryLock(1,TimeUnit.SECONDS);
if(locked){
// do something
Thread.sleep(5000);
}
else{
System.out.println("exit!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(locked){
lock.unlock();
}
}
}
};
t1.setName("t1");
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Thread t2 = new Thread() {
public void run() {
boolean locked = false;
try {
locked = lock.tryLock(1,TimeUnit.SECONDS);
if(locked){
// do something
Thread.sleep(5000);
}
else{
System.out.println("exit!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(locked){
lock.unlock();
}
}
}
};
t2.setName("t2");
t2.start();
}
}
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。
发音[‘sem?f?:]
Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
Semaphore的主要方法:
void acquire()
:从此信号量获取一个许可(获取成功,信号量减一),在提供一个许可前一直将线程阻塞,直到有线程释放(release)信号,或者超时。
void release()
:释放一个许可,将其返回给信号量(释放成功,信号量加一)。
int availablePermits()
:返回此信号量中当前可用的许可数。
boolean hasQueuedThreads()
:查询是否有线程正在等待获取。
用semaphore解决上面的example3:用两个线程,一个输出字母,一个输出数字,交替输出 1A2B3C4D...26Z
package multiplethread;
import java.util.concurrent.Semaphore;
public class PrintDigitLetterUsingSemaphore {
private static Semaphore digitSemaphore = new Semaphore(1);
private static Semaphore letterSemaphore = new Semaphore(0);
public void print0(Semaphore cur, Semaphore next){
for(int i=0; i<26; i++){
try{
cur.acquire();
if(Thread.currentThread().getName().equals("Digit")){
System.out.print(i+1);
}else if(Thread.currentThread().getName().equals("Letter")){
System.out.print((char)(i+‘A‘));
}
next.release();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
PrintDigitLetterUsingSemaphore printer = new PrintDigitLetterUsingSemaphore();
new Thread(()->{printer.print0( digitSemaphore, letterSemaphore);}, "Digit").start();
new Thread(()->{printer.print0( letterSemaphore, digitSemaphore);}, "Letter").start();
}
}
example4:通过 N 个线程顺序循环打印从 0 至 20
package multiplethread;
import java.util.concurrent.Semaphore;
public class PrintMultiThreadInOrder {
private static final int THREAD_COUNT = 5;
private static int maxNum = 20;
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
final Semaphore[] semaphores = new Semaphore[THREAD_COUNT];
for(int i=0; i<THREAD_COUNT; i++){
semaphores[i] = new Semaphore(1);
// 除了最后一个信号量,其他的信号量都为0,也就是最后一个syncObject不会被阻塞
if(i!=THREAD_COUNT-1) {
semaphores[i].acquire();
}
}
for(int i=0; i<THREAD_COUNT; i++){
// 信号量数组每个信号量都有位于其前面的信号量,第0个信号量的前一个信号量为数组最后一个信号量
// 通过两个信号量的acquire()和release()操作来保证按顺序打印
Semaphore lastSemaphore = i==0 ? semaphores[THREAD_COUNT-1] : semaphores[i-1];
Semaphore curSemaphore = semaphores[i];
final int idx = i;
new Thread(()->{
try{
while (true){
// 初次执行,让第一个 for 循环没有阻塞的 syncObjects[4] 先获得令牌阻塞了
lastSemaphore.acquire();
System.out.println("Thread" + idx + ": " + num++);
if(num>maxNum){
System.exit(0);
}
// 释放当前信号量,刚好是下个for循环的lastSemaphore,从而保证是按数组信号量的顺序依次打印
curSemaphore.release();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}).start();
}
}
}
LockSupprot
是线程的阻塞原语,用来阻塞线程和唤醒线程。每个使用LockSupport
的线程都会与一个许可关联,如果该许可可用,并且可在线程中使用,则调用park()
将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用 unpark()
使其可用。但是注意许可不可重入,也就是说只能调用一次park()
方法,否则会一直阻塞。(在 AQS 中,就是通过调用 LockSupport.park( )
和 LockSupport.unpark()
来实现线程的阻塞和唤醒的。)
这里用LockSupport
实现example5: 用N个线程交替打印0~M的整数。
package multiplethread;
import java.util.concurrent.locks.LockSupport;
public class PrintDigitUsingLockSupport {
private static final int THREAD_COUNT = 5;
private static int maxNum = 20;
private static int cur = 0;
public static void main(String[] args) {
Thread[] threads = new Thread[THREAD_COUNT];
Printer[] printers = new Printer[THREAD_COUNT];
for(int i=0; i<THREAD_COUNT; i++){
printers[i] = new Printer();
threads[i] = new Thread(printers[i], String.valueOf(i+1));
}
for(int i=0; i<THREAD_COUNT; i++){
Thread nextThread = threads[i==THREAD_COUNT-1 ? 0 : i+1];
printers[i].setNextThread(nextThread);
threads[i].start();
}
//for(int i=0; i<THREAD_COUNT; i++) threads[i].start();
LockSupport.unpark(threads[0]);
}
// 这里用了一个辅助类Printer来实现run()方法,同时给它增加了一个setNextThread的方法,方便在
// 实例化下一个Thread之后再进行setNextThread操作,这样可以保证run方法中的nextThread是我们
// 想要的Thread,而不是空的
static class Printer implements Runnable{
private volatile Thread nextThread;
@Override
public void run() {
while (true){
// 所有线程一开始都是i处于阻塞等待唤醒状态
LockSupport.park();
System.out.println("Thread" + Thread.currentThread().getName() + ": " + cur++);
if(cur>maxNum) System.exit(0);
// 唤醒下一个线程
LockSupport.unpark(nextThread);
}
}
public void setNextThread(Thread nextThread){
this.nextThread = nextThread;
}
}
}
接下来用LockSupport
来解决上面的example3:用两个线程,一个输出字母,一个输出数字,交替输出 1A2B3C4D...26Z,这里相对来说比较简洁,因为只有两个线程,可以直接把线程的实例放到LockSupport
的unpack()
中进行唤醒,像上面那种线程数量很多的时候就比较复杂了,需要定义一个辅助类。
package multiplethread;
import java.util.concurrent.locks.LockSupport;
public class PrintDigitLetterUsingLockSupport {
private static Thread digitThread, letterThread;
public static void main(String[] args) {
digitThread = new Thread(()->{
for(int i=1; i<=26; i++){
System.out.print(i);
// 先阻塞等待唤醒
LockSupport.park();
// 唤醒下一个线程
LockSupport.unpark(letterThread);
}
}, "digitThread");
letterThread = new Thread(()->{
for(int i=0; i<26; i++){
System.out.print((char) (i+‘A‘));
// 唤醒下一个线程
LockSupport.unpark(digitThread);
// 当前线程阻塞
LockSupport.park();
}
}, "letterThread");
digitThread.start();
letterThread.start();
}
}
阻塞队列 (BlockingQueue)
是Java util.concurrent
包下重要的数据结构,BlockingQueue
提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。并发包下很多高级同步类的实现都是基于BlockingQueue
实现的。
BlockingQueue
的主要方法:
put(E e)
: 将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间。
take()
:从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。
我们这里用BlockingQueue
来实现上面的example3:用两个线程,一个输出字母,一个输出数字,交替输出 1A2B3C4D...26Z,思路就是用两个容量为1的阻塞队列,当数字打印线程digitThread
打印一个数字后,往阻塞队列bq1中放入一个数,然后从阻塞队列bq2中取出一个数,此时bq2是空的,因此digitThread
阻塞在这个线程上,而字母打印线程letterThread
先从bq1中取出一个数,然后打印字母,再往bq2中放入一个数,此时digitThread
因为bq2不为空,继续往下打印,而letterThread
因为bq1已经为空了,也无法继续for循环,因此需要等待digitThread
往bq1中放入数,才能不阻塞。
package multiplethread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class PrintDigitLetterUsingBlockingQueue{
static BlockingQueue<Integer> bq1 = new ArrayBlockingQueue<>(1);
static BlockingQueue<Integer> bq2 = new ArrayBlockingQueue<>(1);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<=26; i++){
try {
System.out.print(i);
bq1.put(1);
bq2.take();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0; i<26; i++){
try{
bq1.take();
System.out.print((char)(i+‘A‘));
bq2.put(1);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}
}
用两个线程,一个输出字母,一个输出数字,交替输出1A2B3C4D...26Z
原文:https://www.cnblogs.com/zhengxch/p/14729692.html