目录
一、synchronizzed同步方法和同步块
-
首先看下多线程造成的线程不安全案例一
package com.daiy.syn; //不安全的多线程买票 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket, "张三").start(); new Thread(buyTicket, "李四").start(); new Thread(buyTicket, "王五").start(); } } class BuyTicket implements Runnable { //一共10张票 private int num = 20; boolean flag = true; @Override public void run() { while (flag) { try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void buy() throws InterruptedException { //判断是否还有票 if (num <= 0) { flag = false; return; } Thread.sleep(100); System.out.println(Thread.currentThread().getName() + "买了第" + num-- + "张票"); } }
上面案例为一个买票案例,从结果看出,当3个人同时买票,会出现-1或者票多次卖的问题,此时在买票方法上加上synchronized,则会解决这个问题
- 多线程造成的线程安全案例二(多个对象)
package com.daiy.syn; //线程不安全的并发取钱 public class UnsafeBank { public static void main(String[] args) { Account account = new Account(200, "招商账户"); Bank zs = new Bank(account, 50, "张三"); Bank ls = new Bank(account, 100, "李四"); zs.start(); ls.start(); } } class Account { int money; String name; public Account(int money, String name) { this.money = money; this.name = name; } } //synchronized默认锁的时this类本身 class Bank extends Thread { Account account;//账户 int drawMoney;//取的钱 int comeToHandMoney;//到手的钱 public Bank(Account account, int drawMoney, String name) { super(name); this.account = account; this.drawMoney = drawMoney; } @Override public void run() { //取钱时先判断钱是否够 if (account.money - drawMoney < 0) { System.out.println(Thread.currentThread().getName() + "钱不够,不可以取!"); return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //钱够了就扣除钱,得到当前卡内余额 account.money = account.money - drawMoney; //到手的钱 comeToHandMoney = comeToHandMoney + drawMoney; System.out.println(account.name + "卡里的余额为:" + account.money); System.out.println(Thread.currentThread().getName() + "到手里得到钱:" + comeToHandMoney); } }
此案例为两人同时对同一账户进行取钱操作,可以看出100的钱取出来150的问题,造成了线程不安全
由于取钱方法是在Bank类中,但是取钱方法中实际是对Account对象进行操作,由于synchronizzed默认锁的时this类本身,我们在取钱方法上直接使用synchronizzed是不行的,需要用到synchronizzed块进行操作 【synchronizzed锁的对象是变化的量,需要增删改的对象】
//synchronized默认锁的时this类本身 class Bank extends Thread { Account account;//账户 int drawMoney;//取的钱 int comeToHandMoney;//到手的钱 public Bank(Account account, int drawMoney, String name) { super(name); this.account = account; this.drawMoney = drawMoney; } @Override public void run() { /****************************将代码放synchronized块中************************************/ synchronized (account) { //取钱时先判断钱是否够 if (account.money - drawMoney < 0) { System.out.println(Thread.currentThread().getName() + "钱不够,不可以取!"); return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //钱够了就扣除钱,得到当前卡内余额 account.money = account.money - drawMoney; //到手的钱 comeToHandMoney = comeToHandMoney + drawMoney; System.out.println(account.name + "卡里的余额为:" + account.money); System.out.println(Thread.currentThread().getName() + "到手里得到钱:" + comeToHandMoney); } } }
此时,就没有100块钱取出150的问题
二、死锁
package com.daiy.syn; //死锁测试 多个线程互相拥抱着对方的资源,形成僵局,造成死锁 public class DeadLock { public static void main(String[] args) { Learn zs = new Learn(1, "张三"); Learn ls = new Learn(2, "李四"); zs.start(); ls.start(); } } //书本 class Book { } //笔 class Pen { } class Learn extends Thread { static Book book = new Book(); static Pen pen = new Pen(); int choose;//选择 String name;//姓名 public Learn(int choose, String name) { this.choose = choose; this.name = name; } @Override public void run() { try { GoodGoodStudy(); } catch (InterruptedException e) { e.printStackTrace(); } } //互相持有对方的锁,需要拿到对方的资源 public void GoodGoodStudy() throws InterruptedException { if (this.choose == 1) { synchronized (book) {//获得书本的锁 System.out.println(this.name + "获得书本的锁"); Thread.sleep(1000); synchronized (pen) {//获得笔的锁 System.out.println(this.name + "获得笔的锁"); } } } else { synchronized (pen) {//获得笔的锁 System.out.println(this.name + "获得笔的锁"); Thread.sleep(1000); synchronized (book) {//获得书本的锁 System.out.println(this.name + "获得书本的锁"); } } } } }
运行上代码,可以看到服务没有停止,代码已经僵持住了不会继续往下走了,这就造成了死锁
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
修改上方代码如下,让资源得到释放而不是互相持有对方需要的锁
package com.daiy.syn; //死锁测试 多个线程互相拥抱着对方的资源,形成僵局,造成死锁 public class DeadLock { public static void main(String[] args) { Learn zs = new Learn(1, "张三"); Learn ls = new Learn(2, "李四"); zs.start(); ls.start(); } } //书本 class Book { } //笔 class Pen { } class Learn extends Thread { static Book book = new Book(); static Pen pen = new Pen(); int choose;//选择 String name;//姓名 public Learn(int choose, String name) { this.choose = choose; this.name = name; } @Override public void run() { try { GoodGoodStudy(); } catch (InterruptedException e) { e.printStackTrace(); } } //互相持有对方的锁,需要拿到对方的资源 public void GoodGoodStudy() throws InterruptedException { if (this.choose == 1) { synchronized (book) {//获得书本的锁 System.out.println(this.name + "获得书本的锁"); Thread.sleep(1000); } synchronized (pen) {//获得笔的锁 System.out.println(this.name + "获得笔的锁"); } } else { synchronized (pen) {//获得笔的锁 System.out.println(this.name + "获得笔的锁"); Thread.sleep(1000); } synchronized (book) {//获得书本的锁 System.out.println(this.name + "获得书本的锁"); } } } }
三、lock锁
案例一的买票,通过lock锁 也可以解决线程安全问题
package com.daiy.lock; import java.util.concurrent.locks.ReentrantLock; public class TestLock { public static void main(String[] args) { BuyTicket2 buyTicket = new BuyTicket2(); new Thread(buyTicket, "张三").start(); new Thread(buyTicket, "李四").start(); new Thread(buyTicket, "王五").start(); } } class BuyTicket2 implements Runnable { //一共10张票 private int num = 10; boolean flag = true; private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (flag) { try { lock.lock(); //加锁 buy(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock();//释放锁 } } } public void buy() throws InterruptedException { //判断是否还有票 if (num <= 0) { flag = false; return; } Thread.sleep(100); System.out.println(Thread.currentThread().getName() + "买了第" + num-- + "张票"); } }
四、synchronized与lock对比
lock是显式锁,需要手动开启和关闭锁,synchronized是隐式锁,出了作用域自动释放
lock只有代码块锁,synchronized锁有方法锁和代码块锁
使用lock锁,jvm将花费较少的时间来调度线程,性能更好
优先使用顺序,lock>同步代码块>同步方法
五、生产者消费者模型
1、wait()
1、wait()是Object里面的方法,而不是Thread里面的。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。 2、wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常,wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁。
package com.daiy.TestPc;
/** wait方法测试
* 1.wait()是Object里面的方法,而不是Thread里面的。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。
* 2.wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常。
* wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁。
*/
public class TestWait implements Runnable {
private final Object object = new Object();
@Override
public void run() {
synchronized (object) {
System.out.println("线程执行开始。。。");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行结束。。。");
}
}
public static void main(String[] args) {
TestWait testWait = new TestWait();
Thread thread = new Thread(testWait);
thread.start();
}
}
从上图我们可以看出线程调用了wait()方法后一直在等待,不会继续往下执行。wait()一旦执行,除非接收到唤醒操作或者是异常中断,否则不会继续往下执行。
2、notify()
1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行, 调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁, 如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。 2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。
package com.daiy.TestPc;
public class TestWait2 {
public static void main(String[] args) throws InterruptedException {
Testnotify testnotify = new Testnotify();
Thread thread1 = new Thread(testnotify);
thread1.start();
Thread.sleep(1000);
testnotify.setFlag(false);
Thread thread2 = new Thread(testnotify);
thread2.start();
}
}
class Testnotify implements Runnable {
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
testwait();
} else {
testnotify();
}
}
public void testwait() {
synchronized (this) {
try {
System.out.println("线程开始执行。。。");
Thread.sleep(1000);
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行结束。。。");
}
}
/**
* 1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行,
* 调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁,
* 如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。
* 2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。
*/
public void testnotify() {
synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notify();
}
}
}
从上图可以看出,在调用notify()方法之后,线程又继续了
3、notifyAll()
notifyAll是唤醒所有等待的线程
package com.daiy.TestPc;
public class TestWait3 {
public static void main(String[] args) {
TestNotifyAll testNotifyAll = new TestNotifyAll();
Thread thread1 = new Thread(testNotifyAll, "线程1");
thread1.start();
Thread thread2 = new Thread(testNotifyAll, "线程2");
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
testNotifyAll.setFlag(false);
Thread thread3 = new Thread(testNotifyAll, "线程3");
thread3.start();
}
}
class TestNotifyAll implements Runnable {
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
testwait();
} else {
testnotify();
}
}
public void testwait() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + "-->开始执行。。。");
Thread.sleep(1000);
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->执行结束。。。");
}
}
public void testnotify() {
synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notifyAll();
}
}
}
4、消费者生产者模型
package com.daiy.TestPc.test;
/**
* 生产者消费者模型 货物类 goods 商场商品库存类shop 生产者类producer 消费者类consumer
*/
//测试类
public class TestPc {
public static void main(String[] args) {
Shop shop = new Shop();
new Thread(new Producer(shop), "生产者").start();
new Thread(new Consumer(shop), "消费者").start();
}
}
//货物类 goods
class Goods {
//货物id
private int id;
public Goods() {
}
public Goods(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
//商场货物库存类shop
class Shop {
Goods goods;
boolean flag;
//生产者生产货物
public synchronized void producerSave(Goods goods) throws InterruptedException {
//flag为true时,商场货物库存充足,不需要生产者生产,应该令生产者等待,通知消费者可以消费
if (flag) {
System.out.println("货物库存充足,欢迎购买...");
this.wait();
}
//库存不充足,生产者应当生产货物存到商场中
System.out.println(Thread.currentThread().getName() + ">>在生产货物,货物id为" + goods.getId());
//生产货物了,设置flag为true
this.goods = goods;
flag = true;
//取消线程等待,通知消费者可以消费了
this.notifyAll();
}
//商场出售商品,消费者消费
public synchronized void consumerBuy() throws InterruptedException {
//flag为false时,商场货物库存不足,消费者不可以直接消费,等待
if (flag == false) {
System.out.println("货物库存暂时不足,请稍后购买...");
this.wait();
}
System.out.println(Thread.currentThread().getName() + ">>在购买货物,货物id为" + goods.getId());
//货物卖出去了,设置商品类为空,flag为false
this.goods = null;
flag = false; // 已经没有商品了,可以通知生产者了(生产者在等待)
this.notifyAll(); // 通知所有生产者无需等待,没有商品了,需要生产商品了
}
}
//生产者
class Producer implements Runnable {
private Shop shop;
public Producer(Shop shop) {
this.shop = shop;
}
@Override
public void run() {
// 循环放货物到shop商场里
int i = 0;
while (i++ < 10) {
try {
// 生产货物,存到商场
Thread.sleep(1000);
this.shop.producerSave(new Goods(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable {
private Shop shop;
public Consumer(Shop shop) {
this.shop = shop;
}
@Override
public void run() {
// 循环取从shop商场里取货物
int i = 0;
while (i++ <= 10) {
try {
// 购买货物
Thread.sleep(2000);
this.shop.consumerBuy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行上代码,结果显示
5、线程池
package com.daiy.demo9;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//线程池测试
public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池 参数为线程池大小
ExecutorService executorService = Executors.newFixedThreadPool(10);
//执行
executorService.execute(new ThreadTest());
executorService.execute(new ThreadTest());
executorService.execute(new ThreadTest());
executorService.execute(new ThreadTest());
//关闭连接
executorService.shutdown();
}
}
class ThreadTest extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->在玩线程池");
}
}