/**
* 同步代码块:synchronized(锁对象){ 被同步的代码}
* 锁对象可以是任何对象,但是必须唯一,可以理解为锁的钥匙必须是同一把,这样别的线程才能打开
*/
public class TicketDemo2 {
public static void main(String[] args) {
TicketMethod ticket= new TicketMethod();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
private int count=10;
Object o=new Object();
@Override
public void run() {
synchronized (o){//若是这种情况,那么任何一个线程
//抢占了CPU会一直循环直到票卖完
while (count>0){
System.out.println(Thread.currentThread().getName()+"开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("当前票数为"+count);
}
}
}
}
static class Ticket2 implements Runnable{
private int count=10;
Object o=new Object();//只有创建一个Ticket2对象,因此只有一个Object对象,可以当作锁
//若将o创建在run方法中则会创建3次,不满足唯一性,无法作为锁(或者说是一把形同虚设的锁)
@Override
public void run() {
while (true){
if (count>0){
synchronized (o) {//但是将同步代码块放在这里会导致到-2才停止
//因为此时三个线程都通过了count>0要争抢锁,当count=0时占用CPU的那个线程
//执行else跳出循环,run方法执行完毕线程死亡,但是另外两个线程依然处于count>0代码的后面,
//因此这两个线程会执行同步代码块的语句导致count=-2
System.out.println(Thread.currentThread().getName()+"开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("当前票数为"+count);
}
}
else {
break;
}
}
}
}
static class Ticket3 implements Runnable{
private int count=10;
@Override
public void run() {
while (true){
synchronized (this) {//正确
if (count>0){
System.out.println(Thread.currentThread().getName()+"开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("当前票数为"+count);
}
else {
break;
}
}
}
}
}
static class TicketMethod implements Runnable{
private int count=10;
@Override
public void run() {
while (true){
//需要特别注意的是method方法使用的锁为this,如果此时在此编写一个同步代码块线程进入此代码块,则method方法也被锁住无法执行,因为它们共享同一把锁,相当于同一把锁锁住了两扇门
boolean trueOrFalse= method();
if (!trueOrFalse){
break;
}
}
}
private synchronized boolean method() {
//同步方法的锁默认为this
//若为静态方法则锁为TicketMethod.class
if (count>0){
System.out.println(Thread.currentThread().getName()+"开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("当前票数为"+count);
return true;
}
return false;
}
}
/*
同步代码块和同步方法都是隐式锁,Lock为显式锁
*/
static class TicketLock implements Runnable{
private int count=10;
private Lock lock=new ReentrantLock();//显示锁lock
@Override
public void run() {
while (true){
lock.lock();
if (count>0){
System.out.println(Thread.currentThread().getName()+"开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("当前票数为"+count);
}
else {
break;
}
lock.unlock();
}
}
}
}
公平锁讲究先来后到(排队),不公平锁则无需排队谁争抢到就上CPU运行(以上均为不公平锁)ReentryLock构造器中传入true就表示该锁为公平锁.
线程死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻 塞,因此程序不可能正常终止。
死锁必须具备以下四个条件:
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁? 只要破坏产生死锁的四个条件中的其中一个就可以
- 破坏互斥条件 这个条件无法破坏,因为用锁本来就是想让他们互斥的(临界资源需要互斥访问)
- 破坏请求与保持条件 一次性申请所有的资源。
- 破坏不剥夺条件 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
- 锁排序法:(重点) 指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下, 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避 免死锁。这是解决死锁很好的一种方法。
- 使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁
线程的通信
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
static class Food{
private String name;
private String taste;
public void setNameAndSaste(String name,String taste){
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
public void get(){
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
}
}
}
两个线程都在操作共同资源(Food),给food的get与set方法设置为synchronized只能确保菜的名字与味道对应,但是无法保证上菜的顺序,可能连续几次都是初始抢到CPU,那做好的菜都无法端走,因为synchronized是不公平锁。因此需要线程的通信
static class Food{
private String name;
private String taste;
//true 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag) {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
sleep() 方法和 wait() 方法异同
- sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到 了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁(如果有的话)。wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify 或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠 时,会释放互斥锁。
- sleep 方法不释放锁,而 wait 方法释放了锁 。
- sleep 通常被用于暂停执行,wait 通常被用于线程通信
- sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏 醒。wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法 相同 两者都可以暂停线程的执行。
两者都可以暂停线程的执行。