Java重修笔记 第五十六天 坦克大战(六)多线程基础 - 线程同步、死锁
-
多线程同步机制
多线程编程中,一些敏感数据可能会被多个线程同时访问造成数据混乱(例如票数),使用线程同步机制,通过锁对象(对象实例或类实例)的方式来保证该段代码在任意时刻,最多只能由一个线程来访问,从而保证了数据的安全性。
-
synchronized 实现线程同步
1. 修饰普通方法
同步普通方法本质上锁的是:调用该方法的实例对象
(1)不加锁,同一个对象多个线程一起执行,互不干扰
public class Synchronized {
public static void main(String[] args) {
// 不加锁,同一个对象多个线程一起执行,互不干扰
MyClass01 myClass01 = new MyClass01();
new Thread(myClass01).start();
new Thread(myClass01).start();
}
}
class MyClass01 implements Runnable {
// 两个线程同时进入 run 方法
@Override
public void run() {
method01();
method02();
}
public void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:(结果不固定)
(2)不加锁,不同对象多个线程一起执行,互不干扰
public class Synchronized {
public static void main(String[] args) {
// 不加锁,不同对象多个线程一起执行,互不干扰
MyClass01 myClass0101 = new MyClass01();
MyClass01 myClass0102 = new MyClass01();
new Thread(myClass0101).start();
new Thread(myClass0102).start();
}
}
class MyClass01 implements Runnable {
// 两个线程同时进入 run 方法
@Override
public void run() {
method01();
method02();
}
public void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:(结果不固定)
(3)给普通方法加锁,锁住了同一个对象实例 this,线程阻塞
public class Synchronized03 {
public static void main(String[] args) {
// 给普通方法加锁,锁住了同一个对象实例 this,线程阻塞
MyClass02 myClass02 = new MyClass02();
new Thread(myClass02).start();
new Thread(myClass02).start();
}
}
class MyClass02 implements Runnable {
// 两个线程同时执行到 run 方法
@Override
public void run() {
method01();
method02();
}
// 这个方法在两个线程中都锁住了 this 对象
// 所以必须等第一个线程执行完 method01 方法, 下一个线程才能执行
public synchronized void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:
(4)给普通方法加锁,锁住了不同的对象实例,互不干扰
public class Synchronized03 {
public static void main(String[] args) {
// 给普通方法加锁,锁住不同的对象实例,
MyClass02 myClass0201 = new MyClass02();
MyClass02 myClass0202 = new MyClass02();
new Thread(myClass0201).start();
new Thread(myClass0202).start();
}
}
class MyClass02 implements Runnable {
// 两个线程同时执行到 run 方法
@Override
public void run() {
method01();
method02();
}
// 锁住两个不同的实例对象不会阻塞
public synchronized void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:(结果不固定)
2. 修饰静态方法
同步静态方法本质上锁的是:调用该方法的类对象
(1)不加锁,同一个对象多个线程一起执行,互不干扰
public class Synchronized03 {
public static void main(String[] args) {
// 不加锁,同一个对象多个线程一起执行,互不干扰
MyClass03 myClass03 = new MyClass03();
new Thread(myClass03).start();
new Thread(myClass03).start();
}
}
class MyClass03 extends Thread {
// 两个线程同时执行到 run 方法
@Override
public void run() {
method01();
method02();
}
public static void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public static void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:(结果不固定)
(2)不加锁,不同对象多个线程一起执行,互不干扰
public class Synchronized03 {
public static void main(String[] args) {
// 不加锁,不同对象多个线程一起执行,互不干扰
MyClass03 myClass0301 = new MyClass03();
MyClass03 myClass0302 = new MyClass03();
new Thread(myClass0301).start();
new Thread(myClass0302).start();
}
}
class MyClass03 extends Thread {
// 两个线程同时执行到 run 方法
@Override
public void run() {
method01();
method02();
}
public static void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public static void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:(结果不固定)
(3)给静态方法加锁,锁住了同一个对象实例,也就是同一个类实例,线程阻塞
public class Synchronized03 {
public static void main(String[] args) {
// 给静态方法加锁,锁住了同一个对象实例,也就是同一个类实例,线程阻塞
MyClass04 myClass04 = new MyClass04();
new Thread(myClass04).start();
new Thread(myClass04).start();
}
}
class MyClass04 implements Runnable {
// 两个线程同时执行到 run 方法
@Override
public void run() {
method01();
method02();
}
// 锁住了同一个类实例,线程阻塞
public static synchronized void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public static void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:
(4)给静态方法加锁,锁住了不同的对象实例,但本质锁的是同一个类实例,线程阻塞
public class Synchronized03 {
public static void main(String[] args) {
// 给静态方法加锁,锁住了同一个对象实例,也就是同一个类实例,线程阻塞
MyClass04 myClass04 = new MyClass04();
new Thread(myClass04).start();
new Thread(myClass04).start();
}
}
class MyClass04 implements Runnable {
// 两个线程同时执行到 run 方法
@Override
public void run() {
method01();
method02();
}
// 锁住了同一个类实例,线程阻塞
public static synchronized void method01() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");
}
public static void method02() {
System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");
}
}
运行结果:
3. 修饰代码块
通过在 synchronized 关键字后面的对象(实例对象或类对象)来上锁,随便 new 个啥进去都行,只要保证不同线程中括号里的是同一个对象实例或类对象,那么就能成功上锁
(1)不加锁,同一个对象多个线程一起执行,互不干扰
public class Synchronized04 {
public static void main(String[] args) {
// 不加锁,同一个对象多个线程一起执行,互不干扰
MyClass05 myClass05 = new MyClass05();
for (int i = 0; i < 5; i++) {
new Thread(myClass05).start();
}
}
}
class MyClass05 implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:(结果不固定)
(2)加锁,但是每个线程传入的对象不一样,没锁住互不干扰
public class Synchronized04 {
public static void main(String[] args) {
// 加锁,但是每个线程传入的对象不一样,没锁住互不干扰
for (int i = 0; i < 5; i++) {
MyClass05 myClass05 = new MyClass05();
new Thread(myClass05).start();
}
}
}
class MyClass05 implements Runnable {
@Override
public void run() {
synchronized (this) {
try {
Thread.sleep(1000);
System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:(结果不固定)
(3)加锁,每个线程传入同样的对象,线程阻塞
public class Synchronized04 {
public static void main(String[] args) {
// 加锁,每个线程传入同样的对象,线程阻塞
MyClass05 myClass05 = new MyClass05();
for (int i = 0; i < 5; i++) {
new Thread(myClass05).start();
}
}
}
class MyClass05 implements Runnable {
@Override
public void run() {
synchronized (this) {
try {
Thread.sleep(1000);
System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
(4)加锁,锁住类对象,线程阻塞
public class Synchronized04 {
public static void main(String[] args) {
// 加锁,锁住类对象,线程阻塞
for (int i = 0; i < 5; i++) {
MyClass05 myClass05 = new MyClass05();
new Thread(myClass05).start();
}
}
}
class MyClass05 implements Runnable {
@Override
public void run() {
synchronized (MyClass05.class) {
try {
Thread.sleep(1000);
System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
-
实现上锁步骤
1. 找到需要上锁的部分
2. 用代码块或者方法封装起来
3. 让多个线程判断锁的对象为同一个即可
-
线程死锁
多个线程都占用了对方的锁资源,即 A 线程想要 B 线程持有的锁资源,而 B 线程想要 A 线程持有的锁资源,且双方互不释放,就会导致死锁的发生。
public class DeadLock01 {
public static void main(String[] args) {
DeadLockDemo deadLockDemo01 = new DeadLockDemo(true);
deadLockDemo01.setName("线程 A ");
DeadLockDemo deadLockDemo02 = new DeadLockDemo(false);
deadLockDemo02.setName("线程 B ");
deadLockDemo01.start();
deadLockDemo02.start();
}
}
class DeadLockDemo extends Thread {
static Object object1 = new Object(); // 保证不管有多少个实例对象, 都共享同一个 object1
static Object object2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// 线程 A 传进来的 flag 为 true, 线程 B 同步传进来的 flag 为 false
// 这样就会出现一个问题, 那就是线程 A 持有 object1 锁并在尝试获取 object2 锁
// 但是线程 B 持有着 object2 锁同时尝试获取 object1 锁, 双方都持有对方想要的锁却不释放
// 这样就造成了程序死锁的发生
if (flag) {
synchronized (object1) { // 加入对象互斥锁
System.out.println(Thread.currentThread().getName() + "进入 object1 锁的代码块");
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "进入 object2 锁的代码块");
}
}
} else {
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "进入 object2 锁的代码块");
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + "进入 object1 锁的代码块");
}
}
}
}
}
运行结果:
-
释放锁