多线程:应用程序有多条执行路径。
进程:正在运行的应用程序。
线程:一条路径,进行的执行单元。
注意:线程是依赖于进程,而进程是操作系统直接创建的。
(掌握)Java语言是不能直接调用操作系统的属性的。C语言可以。
为什么要使用多线程?什么时候使用?
为了提高效率。
当要操作的代码比较多(耗时),循环次数比较多的时候就可以使用多线程。
多线程怎么实现呢? (掌握)
方式1:继承Thread类。
A:创建一个类(MyThread),集成Thread类。
B:重写Thread类中的run()方法。
C:启动并执行线程。
注意:这里是不能直接调用run()方法,需要调用start()方法。
start()方法做了两件事:
A:启动线程。
B:自动调用run()方法。
同一个线程对象start()两次,会报一个异常:IllegalThreadStateException(线程状态异常。)
Thread类的两个方法:
public final String getName(); 获取线程名称(Thread-编号,编号是从0开始)
public final void setName(String name); 设置线程名称。
public static void sleep(long mills); 让当天线程睡一段时间(根据传过来的毫秒值)
线程执行的随机性的原理:由于CPU在做着高效的切换。
程序的执行,其实就是在抢CPU资源。
方式2:实现Runnable接口。
A:创建一个类(MyRunnable)实现Runnable接口。
B:重写run()方法。
C:创建类(MyRunnable)的实例。
D:把类(MyRunnable)的实例,做为Thread类的构造参数传递。
注意事项:
因为MyRunnable这个类是实现了Runnable接口,所以在类中不能直接使用Thread类的getName()这个方法。
怎么用呢?
public static Thread currentThread(); 返回正在执行的线程的引用
(Thread.currentThread().getName())
既然有了第一种实现方式,为什么要有第二种实现方式?
A:避免了单继承的局限性。
B:实现Runnable接口的方式,只创建了一个资源对象,更好的实现了数据(my)和操作(start())的分离。
开发中,常用:实现Runnable接口的方式。
多线程的生命周期图:
A:新建(创建一个线程对象)
B:就绪(具备执行资格,不具备执行权)
C:有可能发生阻塞(不具备执行资格和执行权)
sleep(),wait()
notify()
D:运行(具备执行资格和执行权)
E:死亡(线程对象变为垃圾,等待垃圾回收器回收。)
多线程模拟买票出现了负数和重复值? 产生原因是什么? 怎么解决呢?
产生负数:
public class TicketRunnable implements Runnable {
public int tickets = 200;
@Override
public void run() {
while (true) {
//t1,t2,t3,t4都进来了
//最后一次tickets的值是1
if (tickets > 0) {
/*
* t1满足,进来了,
* t2进来了,
* t3,
* t4都进来了
* */
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/*
* t1醒了,抢到了资源,打印:第一张票 ,完了以后,tickets的值变成了0
* t2醒了,抢到了资源,打印:第0张票,完了以后,tickets的值变成了-1
* t3醒了,抢到了资源,打印:第-1张票,完了以后,tickets的值变成了-2
* t4醒了,抢到了资源,打印:第-2张票,完了以后,tickets的值变成了-3
* */
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
}
}
}
产生重复值:
关键点:ticket-- (ticket = ticket - 1)
A:获取ticket的值。
B:修改ticke的值。
C:把修改后的值,重新赋值给ticket。
当步骤A执行完后,还没来得及执行B,这个时候,别的线程抢到了资源,打印的就是重复值。
产生原因:
由于线程的随机性和延迟性,导致线程访问共享数据出现了问题。
解决方案:
找到可能出问题的代码,然后把可能出问题的代码用锁锁起来。
A:如何找可能出问题的代码?
1、是否有共享数据。
2、是否有多条语句操作共享数据。
3、是否在多线程环境中。
这三点也是多线程产生问题的原因。
B:怎么锁?
可以采用同步代码块的方式锁。
synchronized (锁对象)
{
要被锁的代码。
}
注意:
1、锁对象可以是任意对象。
2、多个线程的锁对象必须是同一个。不然可能会出现锁不住的情况。
锁的状态:开,关。
不同同步机制的锁对象分别是什么?
同步代码块:
可以是任意类型。
同步方法:把synchronized定义在方法的声明上。(写在方法的返回值的数据类型之前)
public synchronized void show();
this。
静态方式:
当前类的字节码文件对象。
类名.class
以后使用同步代码块还是同步方法?
看需求。但是同步的代码越少越好,所以一般使用同步代码块。
如果一个方法中所有的代码都被加锁了,那么,我们可以考虑使用同步方法。
面试题:
写一个死锁的代码。
线程间的通信问题:
线程通信:不同种类的线程间对共享数据的操作问题。
用学生类的案例来演示线程通信;
学生类: Student (资源类)
设置值: SetStudent (一个共享数据,实现了Runnable接口)
获取值: GetStudent(一个共享数据,实现了Runnable接口)
测试类:
正常的逻辑:
针对输出:( GetStudent)
判断是否有数据,如果有就输出数据,没有的话,就等待设置。
(喝水:判断水杯是否有水,如果有就喝,如果没有,就等待小蜜去接水)
针对设置;(SetStudent)
判断是否有数据,如果有就等待输出,如果没有,就设置。
(接水:判断水杯是否有水,如果有等待喝,如果没有,就接水。)
定义了一个标记: true代表有数据,false代表没有数据。
面试题:wait()方法和sleep()这个方法有什么区别?
wait():Object类下的方法,不需要传递参数。释放锁对象,释放资源。
sleep():Thread类,需要传递参数,不释放锁对象。
等待唤醒机制的原理:
Object类中的两个方法:
wait():让当前线程处于等待状态。
notify():唤醒等待的线程。
线程的优先级;
public final void setPriority(int newPriority); 设置线程的优先级
public final int getPriority(); 获取线程的优先级
线程默认的优先级是5,范围是1-10.
注意:
线程的优先级越高,并不代表该线程一定第一个执行。
线程优先级可以再一定程度上,让该线程获取较多的执行机会。
线程的拓展:
暂停线程:
public static void yield(); 暂停当前正在执行的线程对象,并执行其他线程。
什么时候用?
他可以让线程更和谐一点的运行,避免出现成片数据的情况。
注意:如果想要真正的实现数据依次输出,还得使用等待唤醒机制。
加入线程:
public final void join(); 等待该线程终止。
什么时候用?
当某个线程(A)需要在某个线程(B)执行结束后才执行,就可以使用“加入线程”。
例子:当线程B执行完后才能执行线程A,
守护线程:
public final void setDaemon(boolean on); 设置线程为守护线程,一旦前台线程(主线程)结束了,守护线程紧跟着就结束了。
main方法也是一个线程。
main方法也是一个线程。
|