目录
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() {
}
}
- NEW:新建状态。线程刚刚被声明并创建。
- RUNNABLE:可运行状态。新线程调用start()之后处于可运行状态
- BLOCKED:阻塞状态。当线程被挂起就进入了阻塞状态
- WAITING:等待状态。
- TIMED_WAITING:计时等待状态。
- 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;
}
}
}
}
}
这里面引申出了如下几个问题:
- 为什么多了一个
notify()
和wait()
,两个线程就像是商量好了,从乱序变成有序了?、 wait()
和sleep()
的区别是什么?
一个个来看,对于问题一来说:
- 首先我们看到,只要条件满足就直接执行了
notify()
函数,唤醒一个线程,假如线程A在执行,会唤醒线程B,但是此时线程A获取了锁,线程B没法进入方法体。 - 接着线程A打印完成之后调用
wait()
释放了锁,这样线程B就有机会获取了锁,进而线程B会打印数字,接着一样调用wait()
释放锁。 - 这样阻塞着的A线程就又可以获取锁,进而步骤1和2轮流交替执行。这样就达到了交替打印数字的效果。
问题二:
看了上面的就很容易发现,wait()
就是可以释放当前线程所获取到的锁,而sleep()
并不会释放锁,而是仅仅等待而已。调用sleep()
别的线程并不能拿到当前线程的锁。
再来总结一下上面的几个方法:
wait()
:一旦调用,当前线程释放同步监视器,直接被阻塞。
notify()
:一旦执行此方法,就会唤醒一个被wait()
的线程。如果有多个线程被wait()
,就唤醒优先级高的那个
1、这些方法只能在同步方法或者同步代码块中执行。lock锁不可以这样干。
2、这三个方法的调用者必须是同一个同步监视器,否者会抛出 java.lang.IllegalMonitorStateException
异常