Java基础 | 多线程同步和通信

Java线程分类

对于Java程序而言,其线程分为普通线程和守护线程。不同于Linux程序,Java的守护线程在主线程退出之后也会紧跟着退出,设置一个线程为守护线程也很简单。

MyThread1 thread1=new MyThread1();
thread1.setDaemon(true);

线程生命周期

对于Java线程,其内部直接用了一个枚举类定义了其生命周期的所有状态。Thread.State

    public static enum State {
        NEW,                 
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;

        private State() {
        }
    }
  1. NEW:新建状态。线程刚刚被声明并创建。
  2. RUNNABLE:可运行状态。新线程调用start()之后处于可运行状态
  3. BLOCKED:阻塞状态。当线程被挂起就进入了阻塞状态
  4. WAITING:等待状态。
  5. TIMED_WAITING:计时等待状态。
  6. TERMINATED:死亡或者被终止状态。
    线程状态

线程同步

直接编程多线程程序时,如果碰到了对共享资源的访问、修改。那么就很容易出现线程同步问题,导致资源访问错误。此处与懒汉式单例模式出现的线程安全问题是一样的。

为了解决线程同步安全问题,我们引入了以下两种方式:

1、同步代码块

对于同步代码块来说,因为线程创建方式不同,对实现Runnable和继承Thread类两种方式也有不同实现。

同步代码块来保证实现Runnable方式的线程安全

synchronized(同步监视器)
{
	//需要被同步的代码
}

所谓同步代码,就是在操作共享数据的代码。而同步监视器,就是锁,任何一个类的对象都可以充当锁。要求多个线程必须要共用一把锁。

class Window  implements Runnable{
    private int ticket=100;
    Object object=new Object();

    @Override
    public void run() {
        while(true)
        {
            synchronized(object)
            {
                if(ticket>0)
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                    ticket--;
                }
                else
                {
                    break;
                }
            }

        }
    }
}

同步代码块来保证继承Thread类方式的线程安全

2、同步方法

同步方法来保证实现Runnable方式的线程安全

同步方法来保证继承Thread类方式的线程安全

线程同步的死锁

事实上,对于除了上述的情况,即使线程是安全地访问一个共享变量,仍然可能出现问题,这里介绍一下操作系统中非常有名的死锁这个概念。

什么是死锁?

死锁就是在多线程环境下,数个线程互相以对方持有的资源为前驱条件,导致线程们无法正确获取资源运行,互不想让,进而导致各个线程之间死锁。比如A线程想运行需要B线程的资源C,B线程想运行需要A线程的资源D。但是C、D资源对于这俩线程而言都是需要的。这样A、B线程一旦运行,将直接导致死锁

如何解决死锁问题?

其实这个方式的解决办法,和上面几个差不多

线程通信

1、交替打印数字问题

线程通信有一个著名问题,就是要求两个线程交替打印数字。原先我们在编写多线程程序的时候,我们只关注安全性,并不考虑线程之间有没有顺序关系。这里我们就接到要交替打印的需求,需要考虑线程之间的通信了。代码如下:

public class ComunicationTest
{
    public static void main(String[] args) {
        Number number = new Number();

        Thread t1=new Thread(number);
        Thread t2=new Thread(number);
        t1.setName("线程A");
        t2.setName("线程B");

        t1.start();
        t2.start();
    }
}

class Number implements Runnable
{
    private int num=1;
    @Override
    public void run() {
        while(true)
        {
            synchronized(this)
            {
                notify();          //和普通多线程程序不同之处在于,这里有个notify()方法
                if(num<=100)
                {
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;
                    try {
                        wait();  //这里有个wait方法
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                else
                {
                    break;
                }
            }
        }
    }
}

这里面引申出了如下几个问题:

  1. 为什么多了一个notify()wait(),两个线程就像是商量好了,从乱序变成有序了?、
  2. wait()sleep()的区别是什么?

一个个来看,对于问题一来说:

  1. 首先我们看到,只要条件满足就直接执行了notify()函数,唤醒一个线程,假如线程A在执行,会唤醒线程B,但是此时线程A获取了锁,线程B没法进入方法体。
  2. 接着线程A打印完成之后调用wait()释放了锁,这样线程B就有机会获取了锁,进而线程B会打印数字,接着一样调用wait()释放锁。
  3. 这样阻塞着的A线程就又可以获取锁,进而步骤1和2轮流交替执行。这样就达到了交替打印数字的效果。

问题二:
看了上面的就很容易发现,wait()就是可以释放当前线程所获取到的锁,而sleep()并不会释放锁,而是仅仅等待而已。调用sleep()别的线程并不能拿到当前线程的锁。

再来总结一下上面的几个方法:

wait():一旦调用,当前线程释放同步监视器,直接被阻塞。
notify():一旦执行此方法,就会唤醒一个被wait()的线程。如果有多个线程被wait()就唤醒优先级高的那个

1、这些方法只能在同步方法或者同步代码块中执行。lock锁不可以这样干。
2、这三个方法的调用者必须是同一个同步监视器,否者会抛出 java.lang.IllegalMonitorStateException异常

2、生产者消费者问题

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值