一、线程间的通信
1. 两个线程间的通信
- 什么时候需要通信?
a. 多个线程并发执行时,在默认情况下CPU是随机切换线程的;
b. 如果我们希望它们有规律的执行,就可以使用通信,例如每个线程执行一次打印。 - 怎么通信?
a. 如果希望线程等待,就调用wait();
b. 如果希望唤醒等待的线程,就调用notify();
c. 这两个方法必须在同步代码中执行,并且使用同步锁对象来调用。
针对Java_多线程(一)中同步代码块部分的代码,如果我们希望:打印一行程序员,打印一行架构师,打印一行程序员…,我们可以对里面类Printer的代码进行修改:
//等待唤醒机制
class Printer{
private int flag = 1;
public void print1() throws InterruptedException{
synchronized(this){
if(flag != 1){
this.wait(); //当前线程等待
}
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
flag = 2;
this.notify(); //随机唤醒单个等待的线程
}
}
public static void print2() throws InterruptedException{
synchronized(this){
if(flag != 2){
this.wait();
}
System.out.print("架");
System.out.print("构");
System.out.print("师");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
}
2. 三个或三个以上线程间的通信
多个线程通信的问题:
a. notify()方法时随机唤醒一个线程;
b. notifyAll()方法是唤醒所有的线程;
c. jdk5之前无法唤醒指定的一个线程;
d. 如果多个线程之间通信,需要使用 notifyAll()通知所有线程,用while来反复判断条件。
3. 线程间通信需注意的问题
- 在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法。
- 为什么wait方法和notify方法定义在Object这个类中?
因为锁对象可以是任意对象,Object是所有类的基类,所以wait方法和notify方法需要定义在Object这个类中。 - sleep方法和wait方法的区别?
a. sleep方法必须传入参数【参数就是时间】,时间到了自动醒来;wait方法可以传入参数,也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待。
b. sleep方法在同步函数或同步代码块中,不释放锁【CPU一直在这里耗着】;wait方法在同步函数或者同步代码块中,释放锁。
二、JDK1.5的新特性–互斥锁
-
同步
使用ReentrantLock类的lock()【获取锁】和unlock()【释放锁】方法进行同步 -
通信
a. 使用ReentrantLock类的newCondition()方法可以获取Condition对象;
b. 需要等待的时候使用Condition的await()方法,唤醒的时候用signal()方法;
c. 不同的线程使用不同的Condition,这样就能区分唤醒的时候找哪个线程了。
void await() :造成当前线程在接到信号或被中断之前一直处于等待状态
void signal() :唤醒一个等待线程
对于在上面实现的两个线程间的通信,我们可以利用这个新特性实现。代码如下:
//等待唤醒机制
class Printer{
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private int flag = 1;
public void print1() throws InterruptedException{
r.lock();
if(flag != 1){
c1.await(); //当前线程等待
}
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
flag = 2;
c2.signal();
r.unlock();
}
public static void print2() throws InterruptedException{
r.lock();
if(flag != 2){
c2.await();
}
System.out.print("架");
System.out.print("构");
System.out.print("师");
System.out.print("\r\n");
flag = 1;
c1.signal();
r.unlock();
}
}
三、线程组的概述和使用(了解)
1. 线程组概述
- Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
- 默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup() //通过线程对象获取它所属于的组
public final String getName() //通过线程组对象获取线程组的名字
我们通过实现Runnable接口【MyRunnable (implements Runnable)】的方法创建线程对象,验证:默认情况下,线程属于主线程组。
public static void main(String[] args){
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"张三");
Thread t2 = new Thread(mr,"李四");
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg1.getName());
System.out.println(tg2.getName());
}
运行结果如下:得以验证。
- 我们也可以给线程设置分组:
a. ThreadGroup(String name),创建线程组对象并给其赋值名字;
b. 创建线程对象;
c. Thread(ThreadGroup group, Runnable target, String name);
//分配新的Thread对象,以便将target作为其运行对象,将指定的name作为其名称,并作为group所引用的线程组的一员
d. 设置整组的优先级或者守护线程。
2. 案例演示
实现自定义线程组。代码实现如下:
public static void main(String[] args){
ThreadGroup tg = new ThreadGroup("我是一个新的线程"); //创建新的新的线程组
MyRunnable mr = new MyRunnable(); //创建Runnable的子类对象
Thread t1 = new Thread(tg,mr,"张三"); //将线程t1放入组中
Thread t2 = new Thread(tg,mr,"李四"); //将线程t2放入组中
System.out.println(t1.getThreadGroup().getName()); //获取组名
System.out.println(t2.getThreadGroup().getName());
}
运行结果如下:
四、线程的五种状态【生命周期】
- 新建:创建线程对象
- 就绪:线程对象已经启动了,但是还没有获取到CPU的执行权
- 运行:获取到了CPU的执行权
- 阻塞:没有CPU的执行权,回到就绪
- 代码执行完毕,线程死亡