线程的同步
- 之前的 AtomicInteger 类只能保证"变量"的原子性操作,而对多行代码进行"原子性"操作,使用 AtomicInteger 类就不能达到效果了;
- 线程同步是为了防止多线程访问共享资源时发生冲突;
- 同步就是指一个线程等待另一个线程操作完再继续的情况;
1. 线程安全
- 当一个类很好地同步以保护它的数据时,这个类就是线程安全的(thread safe),线程不安全就是不提供数据访问保护,有可能多个数据先后更改数据造成某些线程得到的是无效数据;
- 线程安全问题都是由多个线程对共享的变量进行读写引起的,以下就是线程不同步的例子:
class MyThread implements Runnable {
private int i = 0;
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
System.out.println(i++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt, "thread 1").start();
new Thread(mt, "thread 2").start();
}
}
- 如果某一个线程转换出现延迟,那么就会出错,通常有两种解决办法:同步代码块、同步方法;
2. 关键字 synchronized
- synchronized 关键字:表示“同步”的。它可以对“多行代码”进行“同步”——将多行代码当成是一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其它线程才会执行。这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕;
- synchronized 被称为“重量级的锁”方式,也是“悲观锁”——效率比较低,一般用于特别重要的数据,如资金等;
- synchronized 有几种使用方式:同步代码块和同步方法【常用】;
3. 同步代码块
- 同步代码代码块就是用 synchronized 关键字定义的代码块,但是在同步的时候需要设置一个对象锁;
- 只要锁住的对象是同一个对象,就可以用,一般都会给当前对象 this 上锁;
- synchronized 代码块牺牲了执行速度,安全性虽然提高了,性能却降低了;
a. 格式
- 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁;
- 锁对象可以是任意类型;
- 多个线程对象要使用同一把锁;
- 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED);
synchronized(同步锁){
需要同步操作的代码
}
b. 应用
class MyThread implements Runnable {
private int i = 0;
@Override
public void run() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName());
System.out.println(i++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt, "thread 1").start();
new Thread(mt, "thread 2").start();
}
}
4. 同步方法
- 同步方法就是用了 synchronized 定义的方法;
a. 格式
- 对于非 static 方法,同步锁就是 this;
- 对于 static 方法,我们使用当前方法所在类的字节码对象(类名.class);
public synchronized void method(){
可能会产生线程安全问题的代码
}
b. 应用
class MyThread implements Runnable {
private int i = 0;
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
this.s();
}
public synchronized void s() {
System.out.println("同步方法");
try {
System.out.println(i++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt, "thread 1").start();
new Thread(mt, "thread 2").start();
}
}
5. 死锁
- 如果有多个进程在争用对多个锁的独占访问,那么有可能产生死锁;
- 要避免死锁,应该确保所有的线程都以相同的顺序获取锁;
- 在真实情境中,死锁较难被发现;
class A {
synchronized void funA(B b) {
String name = Thread.currentThread().getName();
System.out.println(name + " 进入 A.foo ");
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println(name + " 调用 B 类中的last()方法");
b.last();
}
synchronized void last() {
System.out.println("A 类中的last()方法");
}
}
class B {
synchronized void funB(A a) {
String name = Thread.currentThread().getName();
System.out.println(name + " 进入 B.foo");
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println(name + " 调用 A 类中的last()方法");
a.last();
}
synchronized void last() {
System.out.println("B 类中的last()方法");
}
}
class Test implements Runnable {
A a = new A();
B b = new B();
Test() {
Thread.currentThread().setName("Main -->> Thread");
new Thread(this).start();
a.funA(b);
System.out.println("main 线程运行完毕");
}
public void run() {
Thread.currentThread().setName("Test -->> Thread");
b.funB(a);
System.out.println("其他线程运行完毕");
}
public static void main(String[] args) {
new Test();
}
}
6. Lock 锁
- java.util.concurrent.locks.Lock 机制提供了比 synchronized 代码块和 synchronized 方法更广泛的锁定操作,同步代码块/同步方法具有的功能 Lock 都有,除此之外更强大 Lock 锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁;public void unlock()
:释放同步锁;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Ticket implements Runnable {
private int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
7. 面试题
- 线程 1 要依次使用 A B C D 四个锁,问:线程 2 为以下哪种顺序获得这四种锁时,不会产生死锁?
a) A B C D;b) D C B A;c) B C D A;d) D B C A - 答:A,因为一个锁只能被共享一次,不一样的顺序会导致不同锁之间相互依赖,如下图:
![7](https://i-blog.csdnimg.cn/blog_migrate/71d3cc1af19af6ef62f62055c85c0a7e.jpeg)