这里将介绍synchronized关键字和其他丰富多彩的多线程控制方法。
一,synchronized同步锁
synchronized关键字的作用:1. 线程同步。2. 保证线程的可见性和有序性。
synchronized关键字有三种用法分别是:
public class Test1 {
public synchronized void test() {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Test1 test1 = new Test1();
//线程1
new Thread(new Runnable() {
@Override
public void run() {
test1.test();
System.out.println(System.currentTimeMillis());
}
}, "Thread1").start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
test1.test();
System.out.println(System.currentTimeMillis());
}
}, "Thread2").start();
}
}
结果:时间相差3000ms。
Thread1
1552904209801
Thread2
1552904212804
这里是使用的test1的锁,获得到锁的线程进入执行状态,未获得锁的线程等待。类锁用法相似只是将synchronized修饰的方法变为static方法。
同步代码块 :使用对象锁
public class Test1 {
public void test() {
synchronized (this) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步代码块 :使用类锁
public class Test1 {
public void test() {
synchronized (Test1.class) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
synchronized是一个可以重入的锁 直接上代码
锁的可重入定义
二,synchronized同步锁的扩展:重入锁
重入锁是synchronized功能的扩展,性能好于synchronized。
重入锁由java.util.concurrent.locks.ReentrentLock类来实现的。
public class Test1 implements Runnable {
private static ReentrantLock lock = new ReentrantLock();
static int i = 0;
@Override
public void run() {
lock.lock();
for(int j = 0; j<10000; j++) {
i++;
}
lock.unlock();
}
public static void main(String[] args) {
Test1 test1 = new Test1();
Thread thread1 = new Thread(test1);
Thread thread2 = new Thread(test1);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i="+i);
}
}
i=20000
这样每个线程都会+10000次,结果什么时候都是20000。
可以看出重入锁在操作的过程中更加的灵活,在何时加锁都能灵活的控制,注意在退出临界区时一定要手动释放锁,否则其他线程将无法执行。
重入锁之中断响应
方法 Lock.lockInterruptibly()
我们知道对于synchronized,如果一个线程等待锁,那么结果只有两种1. 继续等待。2. 拿到锁,继续执行。而重入锁却提供了一种不同的机制,在线程的等待过程中可以响应中断,来结束等待。
import java.util.concurrent.locks.ReentrantLock;
public class Test1 implements Runnable {
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
static int i = 0;
@Override
public void run() {
if("thread1"==Thread.currentThread().getName()||"thread1".equals(Thread.currentThread().getName())) {
try {
//获得锁但优先响应中断
lock1.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName()+"尝试拿到lock2");
//获得锁但优先响应中断
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName()+"拿到了lock2");
} catch (InterruptedException e) {
if(lock1.isHeldByCurrentThread()) {
//释放锁
lock1.unlock();
System.out.println(Thread.currentThread().getName()+"释放了lock1");
}
}
}else {
try {
//获得锁但优先响应中断
lock2.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName()+"尝试拿到lock1");
//获得锁但优先响应中断
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName()+"拿到了lock1");
} catch (InterruptedException e) {
if(lock2.isHeldByCurrentThread()) {
//释放锁
lock2.unlock();
System.out.println(Thread.currentThread().getName()+"释放了lock2");
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
Thread thread1 = new Thread(test1,"thread1");
Thread thread2 = new Thread(test1,"thread2");
thread1.start();
thread2.start();
//运行到这里会产生死锁
Thread.sleep(2000);
//让线程1中断
thread1.interrupt();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部结束");
}
}
线程1和线程2都拿着自己的锁尝试去别人的锁就会产生死锁,给线程1一个中断请求,线程1在等待lock2的时候会去响应中断释放自己的锁并结束线程。
重入锁之等待限时
方法 Lock.tryLock(long time, TimeUnit unit) 等待一段时间如果拿不到锁就结束,参数unit时间单位,时间单位time个数。
方法 Lock.tryLock() 拿不到锁直接结束,不等待。
Lock.tryLock()的情况
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test implements Runnable {
private static Lock lock = new ReentrantLock();
@Override
public void run() {
//线程拿不到锁会立即结束
if(lock.tryLock()) {
System.out.println(Thread.currentThread().getName()+"正在运行");
//睡眠3000ms 模拟第一个进入的线程长时间占有锁资源
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}else {
System.out.println(Thread.currentThread().getName()+"拿不到锁结束");
}
}
public static void main(String[] args) {
Test test = new Test();
Thread thread1 = new Thread(test,"t1");
Thread thread2 = new Thread(test,"t2");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部运行结束");
}
}
t2拿不到锁结束
t1正在运行
全部运行结束
t2拿不到锁结束
t1正在运行
全部运行结束
Lock.tryLock(long time, TimeUnit unit)的情况,这里让线程等待50000ms。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test implements Runnable {
private static Lock lock = new ReentrantLock();
@Override
public void run() {
try {
//线程等待5000ms后还拿不到锁资源就结束
//拿到锁资源就在继续执行
if(lock.tryLock(5000, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName()+"正在运行");
//睡眠3000ms 模拟第一个进入的线程长时间占有锁资源
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}else {
System.out.println(Thread.currentThread().getName()+"拿不到锁结束");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Test test = new Test();
Thread thread1 = new Thread(test,"t1");
Thread thread2 = new Thread(test,"t2");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部运行结束");
}
}
t1正在运行
t2正在运行
全部运行结束
重入锁之公平锁
我们知道线程对资源的是抢占式的,不遵循先来后到的原则,而是随机抢占谁抢到谁用,这样会产生饥饿现象,重入锁就提供了一种公平的方案,排队,谁在前面谁先占有锁。
new ReentrantLock(boolean fair){}
Parameters:fair true if this lock should use a fair ordering policy
参数:如果该锁应使用公平订购策略,则为公平真
如果参数为ture将使用公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestFair implements Runnable {
//参数为ture 将重入锁设置成公平锁
private static Lock lock = new ReentrantLock(true);
public static TestFair testFait = new TestFair();
@Override
public void run() {
while(true) {
lock.lock();
System.out.println(Thread.currentThread().getName()+"正在执行");
lock.unlock();
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(testFait, "t1");
Thread thread2 = new Thread(testFait, "t2");
//将thread1的优先级设置为1 将thread2优先级设置为5
thread1.setPriority(1);
thread2.setPriority(5);
thread1.start();
thread2.start();
}
}
结果我截取了一部分
t1正在执行
t2正在执行
t1正在执行
t2正在执行
t1正在执行
t2正在执行
t1正在执行
t2正在执行
t1正在执行
t2正在执行
即使我设置了线程的优先级,线程依然是交替运行。
重入锁的好搭档 Condition
Condition的作用与Object.wait() 和 Object.notify()/Object.notifyAll()的作用相似,前者是配合重入锁使用的,Condition的方法有
Condition.await() 使线程等待,与Object.wait()有一个不同之处是可以响应中断。
Condition.awaitUninterruptibly(),也是使线程等待但是并不会去响应中断。
Condition.signal()随机唤醒等待池的一个线程。
Condition.signalAll() 唤醒等待池的所有的线程,。
Condition.await()和Condition.signal()
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestCondition implements Runnable {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
private static TestCondition Testcondition = new TestCondition();
@Override
public void run() {
//condition.await()方法必须拿到重入锁
lock.lock();
System.out.println("线程正在运行状态");
try {
condition.await();
System.out.println("线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放掉锁给唤醒线程
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(Testcondition);
thread.start();
Thread.sleep(1000);
//condition.signal()方法必须拿到重入锁
lock.lock();
condition.signal();
//释放掉锁资源给唤醒的线程
lock.unlock();
Thread.sleep(1000);
System.out.println("程序运行结束");
}
}
切记在使用condition使线程等待或者唤醒后,一定要及时释放掉锁,否则即使线程被唤醒,由于拿不到锁也会处于阻塞状态。
信号量
信号量是对同步锁和重入锁的扩展,与同步锁和重入锁不同的是每次可以有多个线程进入临界区。
信号量的构造方法
public Semaphore(int permits) //参数:每次有多少个线程进入代码区
public Semaphore(int permits, boolean fair) //第二个参数指定是否公正
提供的方法
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class TestSemaphore implements Runnable {
private Semaphore semaphore = new Semaphore(3);
@Override
public void run() {
try {
//尝试获得许可
semaphore.acquire();
System.out.println(Thread.currentThread().getId()+"正在执行");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放许可
semaphore.release();
System.out.println("有线程退出");
}
}
public static void main(String[] args) {
//新建一个有固定线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
TestSemaphore testSemaphore = new TestSemaphore();
for(int i=1;i<=10;i++) {
//传进去一个runnable ThreadFactory会自动创建线程
executorService.submit(testSemaphore);
}
}
}
10正在执行
11正在执行
12正在执行
有线程退出
13正在执行
有线程退出
14正在执行
有线程退出
15正在执行
有线程退出
有线程退出
有线程退出
18正在执行
17正在执行
16正在执行
有线程退出
有线程退出
有线程退出
19正在执行
有线程退出
可见每次有五个线程在工作,没退出一个就有一个进来。
读写锁 ReadWriteLock
读写锁是对锁的一种优化,实现了锁的分离。
当程序中的读操作的次数大大超过了写的次数,这种读写锁的效率非常的高。
同步锁和重入锁的不同