多线程 3
线程的死锁问题
什么是死锁?
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁;出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
如何解决死锁?
➢ 专门的算法、原则
➢ 尽量减少同步资源的定义
➢ 尽量避免嵌套同步
死锁演示
示例代码1:
public class ThreadTest_7 {
public static void main(String[] args) {
StringBuffer stringBuffer1 = new StringBuffer();
StringBuffer stringBuffer2 = new StringBuffer();
new Thread(){
@Override
public void run(){
synchronized (stringBuffer1){
stringBuffer1.append("A");
stringBuffer2.append("1");
synchronized (stringBuffer2){
stringBuffer1.append("B");
stringBuffer2.append("2");
System.out.println(stringBuffer1);
System.out.println(stringBuffer2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (stringBuffer2){
stringBuffer1.append("C");
stringBuffer2.append("3");
synchronized (stringBuffer1){
stringBuffer1.append("D");
stringBuffer2.append("4");
System.out.println(stringBuffer1);
System.out.println(stringBuffer2);
}
}
}
}).start();
}
}
运行结果:
示例代码2:
public class ThreadTest_7 {
public static void main(String[] args) {
StringBuffer stringBuffer1 = new StringBuffer();
StringBuffer stringBuffer2 = new StringBuffer();
new Thread(){
@Override
public void run(){
synchronized (stringBuffer1){
stringBuffer1.append("A");
stringBuffer2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (stringBuffer2){
stringBuffer1.append("B");
stringBuffer2.append("2");
System.out.println(stringBuffer1);
System.out.println(stringBuffer2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (stringBuffer2){
stringBuffer1.append("C");
stringBuffer2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (stringBuffer1){
stringBuffer1.append("D");
stringBuffer2.append("4");
System.out.println(stringBuffer1);
System.out.println(stringBuffer2);
}
}
}
}).start();
}
}
运行结果:
对比发现,示例代码2中加入了sleep()后,由于一个线程进入阻塞后,另一个线程立即进入,两个线程之间相互等待对方结束线程,导致死锁问题的产生~ ~
同步锁
Lock(锁)的概念
从JDK 5.0开始,Java提供了更强大的线程同步机制,通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
代码演示
参考代码:
public class Lock_test {
public static void main(String[] args) {
Window window = new Window();
Thread window1 = new Thread(window);
Thread window2 = new Thread(window);
Thread window3 = new Thread(window);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(); //默认false
@Override
public void run() {
while(true) {
try {
//2.调用锁定方法:lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}finally{
//3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
运行结果:
synchronized 和 lock 的异同?
相同:
二者都可以解决线程安全问题~~
不同:
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放;
- Lock只有代码块锁,synchronized有代码块锁和方法锁;
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock----->同步代码块(已经进入了方法体,分配了相应资源) ----->同步方法中(在方法体之外) ~~
练习
银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。问题:该程序是否有安全问题,如果有,如何解决?
[提示]
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
参考代码:
public class AccountTest {
public static void main(String[] args) {
Account account = new Account(0);
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
customer1.setName("老公");
customer2.setName("老婆");
customer1.start();
customer2.start();
}
}
class Account{
private double balance;
public Account(double balance) {
this.balance = balance;
}
//存钱
public synchronized void deposit(double amt){
if (amt>0){
balance += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":存钱成功,余额为:"+ balance);
}
}
}
class Customer extends Thread{
private Account account;
public Customer(Account account) {
this.account = account;
}
@Override
public void run(){
for (int i=0;i<5;i++){
account.deposit(1000);
}
}
}
运行结果:
线程的通信
概述
线程通信的目的是为了能够让线程之间相互发送信号。另外,线程通信还能够使得线程等待其它线程的信号,比如,线程B可以等待线程A的信号,这个信号可以是线程A已经处理完成的信号。
线程通信涉及的方法
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程。
- notifyALl():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
- wait(), notify(), notifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(), notify(), notifyALl()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则,会出现IllegalMonitorStateException异常。
- wait(), notify(), notifyALl()三个方法是定义在java. Lang. object类中。
练习
例题:使用两个线程打印1-100,线程1和线程2交替打印。
参考代码:
public class ThreadCommunicateTest {
public static void main(String[] args) {
Number number = new Number();
Thread thread1 = new Thread(number);
Thread thread2 = new Thread(number);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this){
//将阻塞的进程唤醒
notify();
if (number<100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
运行结果:
sleep()方法和wait()方法的异同?
答:
相同点: 一旦执行方法,都会使得当前线程进入阻塞状态。
不同点:
- 两个方法声明的位置不同: Thread类中声明sLeep(),object 类中声明wait();
- 调用的要求不同: sleep() 可以在任何需要的场景下调用。wait() 必须使用在同步代码块或同步方法中;
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
经典问题:生产者/消费者问题
题目描述:
生产者(Prpocuctor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
➢ 生产者比消费者快时,消费者会漏掉一些数据没有取到。
➢ 消费者比生产者快时,消费者会取相同的数据。
参考代码:
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer1 = new Producer(clerk);
producer1.setName("生产者");
Consumer consumer1 = new Consumer(clerk);
consumer1.setName("消费者");
producer1.start();
consumer1.start();
}
}
class Clerk{
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if (productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
notify();//唤醒消费者
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if (productCount > 0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
notify();//唤醒消费者
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run(){
System.out.println(getName()+":开始生产产品!");
while(true){
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
//消费者
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run(){
System.out.println(getName()+":开始消费产品!");
while(true){
try {
sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
运行结果: