JavaSE 多线程——线程安全

一、产生线程安全问题的条件

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改的行为

二、线程同步

  • 可以使用synchronized线程同步机制解决线程安全问题(当然还有其他方式,见下文),即线程排队执行,不能并发
  • 线程同步会降低部分效率,但数据安全才是第一位的,即以安全为主

1. 同步编程模型

  • 一个线程获得了一个任务,然后去执行这个任务,当这个任务执行完毕后,才能执行接下来的另外一个任务,且这个线程不能将当前的任务放置在一边,转而去做另外一个任务(线程之间有等待关系,一个没执行完,另一个不能执行),同步相当于排队,排队执行,效率较低

2. 异步编程模型

  • 一个线程中执行一堆任务,这个线程可以自由的保存,恢复任务的状态(线程之间各执行各的,不需要相互等待),其实就是多线程并发,异步相当于并发,效率较高

三、synchronized关键字

  • 在锁池找共享对象的对象锁,找的时候,会释放之前占有的CPU时间片,有可能找到了,有可能没找到,没找到则在锁池中等待,找到了就进入就绪状态继续抢夺CPU时间片
  • 执行原理
    假设A和B线程并发,开始执行代码的时候,是一先一后的,假设A先执行了,遇到了synchronized关键字,这个时候会自动找“共享对象”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直占有这把锁,直到同步代码块代码结束,这把锁才会释放,假设A已经占有这把锁,此时B也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被A占有,B只能在同步代码块外面等待A的结束,直到A把同步代码块执行结束了,A归还这把锁,此时B终于等到这把锁,然后B占有这把锁进入同步代码块执行程序
  • synchronized关键字尽量不要嵌套使用,容易造成死锁现象
  • 在某一时刻,只有一个线程可以持有一个对象的锁

1. 语法格式

1.1 同步代码块

synchronized(同步共享对象) {
	// 线程同步代码块
}
  • () 中的数据必须是多线程共享的对象,否则达不到多线程排队,即这个共享对象一定是需要排队执行的这些线程对象所共享的

1.2 在实例方法上使用synchronized

  • 表示共享对象一定是this(锁this,不能是其他对象),即对象锁,并且同步代码块是整个方法体,可能会无故扩大同步的范围,导致程序的执行效率降低,这种方式不灵活
  • 当然这种方式的优点是代码简洁,所以如果共享的对象是this,并且需要同步的代码块是整个方法体,建议使用这种方式

1.3 在静态方法上使用synchronized

  • 表示找类锁,而类锁永远只有1把
  • “Java的synchronized()方法类似于操作系统概念中的互斥内存块,在Java中的Object类对象中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现Java中简单的同步、互斥操作。明白这个原理,就能理解为什么synchronized(this)与synchronized(static XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例
  • 总结
    当synchronized作用于普通方法时,锁对象是this;
    当synchronized作用于静态方法是时,锁对象是当前类的Class对象;
    当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj

2. synchronized的应用

  • 问题:doOther方法执行的时候需要等待doSome方法的结束吗?

2.1 应用一

  • 不需要,因为doOther()方法没有使用synchronized关键字修饰,所以不共享对象
public class Hw {
	public static void main(String[] args) {
		/**
		 * doOther方法执行的时候需要等待doSome方法的结束吗?
		 *  不需要,因为doOther()方法没有使用synchronized关键字修饰
		 */
		MyClass mc = new MyClass();

		Thread thread1 = new MyThread(mc);
		Thread thread2 = new MyThread(mc);

		thread1.setName("thread1");
		thread2.setName("thread2");

		thread1.start();
		try {
			// 此睡眠是为了保证thread1线程先执行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread2.start();
	}
}

class MyThread extends Thread {
	private MyClass mc;

	public MyThread(MyClass mc) {
		this.mc = mc;
	}

	public void run() {
		if (Thread.currentThread().getName().equals("thread1")) {
			mc.doSome();
		}
		if (Thread.currentThread().getName().equals("thread2")) {
			mc.doOther();
		}
	}
}

class MyClass {
	public synchronized void doSome() {
		System.out.println("doSome begin");
		try {
			Thread.sleep(1000 * 3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("doSome over");
	}

	// 没有使用synchronized关键字修饰
	public void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther over");
	}
}

2.2 应用二

  • 需要,因为doOther()方法使用了synchronized关键字修饰,所以共享对象
public class Hw {
	public static void main(String[] args) {
		/**
		 * doOther方法执行的时候需要等待doSome方法的结束吗?
		 *  需要,因为doOther()方法使用了synchronized关键字修饰
		 */
		MyClass mc = new MyClass();

		Thread thread1 = new MyThread(mc);
		Thread thread2 = new MyThread(mc);

		thread1.setName("thread1");
		thread2.setName("thread2");

		thread1.start();
		try {
			// 此睡眠是为了保证thread1线程先执行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread2.start();
	}
}

class MyThread extends Thread {
	private MyClass mc;

	public MyThread(MyClass mc) {
		this.mc = mc;
	}

	public void run() {
		if (Thread.currentThread().getName().equals("thread1")) {
			mc.doSome();
		}
		if (Thread.currentThread().getName().equals("thread2")) {
			mc.doOther();
		}
	}
}

class MyClass {
	public synchronized void doSome() {
		System.out.println("doSome begin");
		try {
			Thread.sleep(1000 * 3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("doSome over");
	}

	// 使用synchronized关键字修饰
	public synchronized void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther over");
	}
}

2.3 应用三

  • 不需要,因为创建了两个MyClass对象,是两把锁,所以没有共享对象
public class Hw {
	public static void main(String[] args) {
		/**
		 * doOther方法执行的时候需要等待doSome方法的结束吗?
		 *  不需要,因为MyClass对象是两个,两把锁,没有共享对象
		 */
		 
        // 创建两个MyClass对象
		MyClass mc = new MyClass();
		MyClass mc2 = new MyClass();

		Thread thread1 = new MyThread(mc);
		Thread thread2 = new MyThread(mc2);

		thread1.setName("thread1");
		thread2.setName("thread2");

		thread1.start();
		try {
			// 此睡眠是为了保证thread1线程先执行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread2.start();
	}
}

class MyThread extends Thread {
	private MyClass mc;

	public MyThread(MyClass mc) {
		this.mc = mc;
	}

	public void run() {
		if (Thread.currentThread().getName().equals("thread1")) {
			mc.doSome();
		}
		if (Thread.currentThread().getName().equals("thread2")) {
			mc.doOther();
		}
	}
}

class MyClass {
	public synchronized void doSome() {
		System.out.println("doSome begin");
		try {
			Thread.sleep(1000 * 3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("doSome over");
	}

	// 使用synchronized关键字修饰
	public synchronized void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther over");
	}
}

2.4 应用四

  • 需要,因为在静态方法上使用synchronized关键字表示找类锁,而不管创建了几个对象,类锁只有1把,所以共享对象
public class Hw {
	public static void main(String[] args) {
		/**
		 * doOther方法执行的时候需要等待doSome方法的结束吗?
		 *  需要,因为在静态方法上使用synchronized关键字表示找类锁,而不管创建了几个对象,类锁只有1把,所以共享对象
		 */
		MyClass mc = new MyClass();

		Thread thread1 = new MyThread(mc);
		Thread thread2 = new MyThread(mc);

		thread1.setName("thread1");
		thread2.setName("thread2");

		thread1.start();
		try {
			// 此睡眠是为了保证thread1线程先执行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread2.start();
	}
}

class MyThread extends Thread {
	private MyClass mc;

	public MyThread(MyClass mc) {
		this.mc = mc;
	}

	public void run() {
		if (Thread.currentThread().getName().equals("thread1")) {
			mc.doSome();
		}
		if (Thread.currentThread().getName().equals("thread2")) {
			mc.doOther();
		}
	}
}

class MyClass {
    // 静态方法使用synchronized关键字修饰
	public synchronized static void doSome() {
		System.out.println("doSome begin");
		try {
			Thread.sleep(1000 * 3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("doSome over");
	}

	// 静态方法使用synchronized关键字修饰
	public synchronized static void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther over");
	}
}

四、Java中变量的线程安全性

  • Java中变量严格分为:局部变量(栈中)、静态变量(方法区中)、实例变量(堆中)
    链接: Java 变量严格分类及汇总
  • 局部变量永远都不会存在线程安全问题,因为局部变量在栈中,永远不会共享(一个线程一个栈)
  • 堆区只有一个,实例变量在堆中;方法区只有一个,静态变量在方法区中,堆和方法区都是多线程共享的,可能存在线程安全问题

五、解决线程安全问题的三种方式

  1. 尽量使用局部变量代替成员变量(实例变量和静态变量)
  2. 如果必须使用实例变量,则应该创建多个对象,于是实例变量的内存就难以共享
  3. 如果既不能使用局部变量,也不能创建多个对象,那么选择使用synchronized线程同步机制,所以不能直接选择使用线程同步机制,应该综合考量,毕竟线程同步机制执行效率较低,系统用户吞吐量较低,用户体验差

六、死锁

1. 什么是死锁

  • 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,不出现异常,也不出现错误,一直僵持,由于线程被无限期地阻塞,因此程序不可能正常终止

2. 死锁的条件

  1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
  3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有
  4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路

3. 死锁实例

  • synchronized关键字尽量不要嵌套使用,容易造成死锁现象
public class DeadLock {
	public static void main(String[] args) {
		Object object1 = new Object();
		Object object2 = new Object();

		// t1和t2两个线程共享o1,o2
		Thread thread1 = new MyThread1(object1, object2);
		Thread thread2 = new MyThread2(object1, object2);

		thread1.start();
		thread2.start();
	}
}

class MyThread1 extends Thread {
	Object object1;
	Object object2;

	public MyThread1(Object object1, Object object2) {
		this.object1 = object1;
		this.object2 = object2;
	}

	public void run() {
		synchronized (object1) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (object2) {

			}
		}
	}
}

class MyThread2 extends Thread {
	Object object1;
	Object object2;

	public MyThread2(Object object1, Object object2) {
		this.object1 = object1;
		this.object2 = object2;
	}

	public void run() {
		synchronized (object2) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (object1) {

			}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jayco江柯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值