一、问题引入
要实现三个线程,对变量count执行++操作,每个线程加完之后,输出count的值
class MyRunnable implements Runnable{
private int count = 0;
@Override
public void run() {
addCount();
}
private void addCount(){
count++;
// 此处睡眠100ms,模拟可能有多个线程执行到这
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public class ThreadTest {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
// 启动三个线程
Thread t1 = new Thread(r, "t1");
Thread t2 = new Thread(r, "t2");
Thread t3 = new Thread(r, "t3");
t1.start();
t2.start();
t3.start();
}
}
问题结果如下:
线程t3执行完count++后,cpu轮询到t1操作count++,t1执行完count++后,cpu轮询为t2操作count++,所以就会出现三个线程打印count值时读取count值为3
要想使出现理想的效果,就要使count++和打印count后,才允许别的线程执行count++操作
注意:count也有可能最后不为3,一个线程如果count++后,没有及时写入到内存,另一个线程读到修改前的count值,从count最终的值小于3
二、对象锁
锁是任何一个类的对象,需要多个线程共用一把锁
synchronized (this),this代表MyRunnable的对象,在主方法中,创建了一个MyRunnable对象,所以可以保证只有一个MyRunnable对象,即多个线程共用一把锁
当第一个线程执行synchronized (this)时,此时线程会拿到MyRunnable对象锁,当第二个线程执行到synchronized (this)时,拿不到锁,只有当第一个线程执行完同步代码块,释放锁,第二个线程拿到锁,进入同步代码块
同步代码块:
private void addCount(){
synchronized (this) {
count++;
// 此处睡眠100ms,模拟可能有多个线程执行到这
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
同步方法:
直接在非静态方法上加上synchronized与同步代码块相同,也相当于synchronized (this)
private synchronized void addCount() {
count++;
// 此处睡眠100ms,模拟可能有多个线程执行到这
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
三、类锁
如果在主线程创建三个MyRunnable对象,用对象锁没什么作用了,因为此时有三个对象,不能保证对象唯一
同步代码块:
使用任何一个类的类锁,每个类,都有唯一一个Class对象,如MyRunnale.class,虚拟机运行期间,只有唯一一个Class对象
class MyRunnable implements Runnable {
private static int count = 0;
@Override
public void run() {
addCount();
}
private void addCount() {
synchronized (MyRunnable.class) {
count++;
// 此处睡眠100ms,模拟可能有多个线程执行到这
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyRunnable r1 = new MyRunnable();
MyRunnable r2 = new MyRunnable();
MyRunnable r3 = new MyRunnable();
// 启动三个线程
Thread t1 = new Thread(r1, "t1");
Thread t2 = new Thread(r2, "t2");
Thread t3 = new Thread(r3, "t3");
t1.start();
t2.start();
t3.start();
}
}
同步方法:
作用在静态方法上,相当于synchronized (MyRunnable.class)
private static synchronized void addCount() {
count++;
// 此处睡眠100ms,模拟可能有多个线程执行到这
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
四、ReentrantLock锁
synchronized锁定后,执行完同步代码块,自动的释放锁;
ReentrantLock需要手动的启动锁,手动的释放锁;
class MyRunnable implements Runnable {
private int count = 0;
// ReentrantLock(boolean fair) fair参数是否公平,true,先来的线程先进入同步代码块;false,线程共同竞争锁
// 1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
addCount();
}
private void addCount() {
try {
// 2.调用锁定方法lock()
lock.lock();
count++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " count = " + count);
} finally {
// 3.调用解锁方法unlock()
lock.unlock();
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
// 启动三个线程
Thread t1 = new Thread(r, "t1");
Thread t2 = new Thread(r, "t2");
Thread t3 = new Thread(r, "t3");
t1.start();
t2.start();
t3.start();
}
}
线程同步执行的结果:
五、synchronized与ReentrantLock
synchronized和ReentrantLock都是可重入锁,避免死锁。
1.synchronized
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程指针。
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。