One——实现多线程的方法有三种
1)方式一:继承Thread类
A.自定义MyThread类继承Thread类
B.在MyThread类中重写Thread类中的run()方法
C.创建MyThread类对象。
D.启动线程对象
2)问题:
A.为什么要重写run()方法?
重写之后的run()方法里面封装着要被线程执行的代码,而Thread类中的run()没有意义
B.启动线程对象是用哪个方法?
start()
C.run()和start()方法的区别?
run()方法如果直接调用仅仅是一个普通方法,不会启动线程
start()则是启动线程的方法,然后由jvm调用run()方法
3)方式二:实现Runnable接口
A.自定义MyRunnable类实现Runable接口
B.在MyRunnable类中重写run()方法
C.创建MyRunnable类的对象
D.将这个对象作为初始化Thread类的对象的参数传递给Thread对象
4)问题:
A.有了方式一,为什么还要再来一个方式2?
a.可以有效避免java单继承带来的局限性
b.适合多个相同的程序去处理同一个资源的情况,把线程同程序的代码,数据,有效的分离,较好的体现了面向对象的思想(其实就是看重写run()那个类要被实例化几次,第一种方法里面,每次想创建一个新的线程就需要实例化一次MyThread类,至于为什么不可以my.start()两次前面有讲;而第二种方法,重写run()方法的MyRunnable类的对象只是作为参数传递,需要多次实例化的只是Thread类,而这个类是java给出的。)
Two——线程的生命周期是怎么样的?
一个线程有五个状态
1)新建状态:创建线程对象
2)就绪状态:线程对象调用start()方法,线程就绪,此时有执行资格,但是没有执行权
3)运行状态:获得了cpu的执行权,线程运行,此时有执行资格,有执行权。在运行状态也可能会回到就绪状态,一般是由于被其他线程抢到了cpu的执行权
4)阻塞状态:由于某些操作(sleep(),wait())使线程被阻塞,没有执行资格,没有执行权,但是可以通过一些操作(sleep()时间到,notify())把它激活,激活后处于就绪状态。
5)死亡状态:线程对象变成垃圾(run()结束,中断线程),等待被回收
如下线程状态图:
Three——多线程的安全问题
1.什么原因导致的线程安全问题
1)处于多线程环境中
2)存在共享数据
3)有多条语句在操作共享数据
我们知道cpu在执行过程中一次只能做一件事,也就是cpu的执行具有原子性,而多线程的运行状态却充斥着随机性,这也就是多线程安全问题的根本原因。具体代码有机会再补充
2.前两点是我们没办法改变的,因为这本来就多线程所必须的(当然第三点也是),但是java给出了让我们在第三点上改进的办法,那就是引入了synchronize(同步)关键字
Four——synchronized同步
1.把需要被同步的代码(比如多个线程操作共享数据的代码)包装成一个整体,使得某一个线程在执行这一部分的时候,其他线程被锁在外面,这就是synchronize同步提供解决多线程安全问题的办法——锁。
2.sychronized可以有两种使用方式
1)同步代码块
synchronize(锁对象){
需要同步的代码
}
A.这里的锁对象可以是任意对象,所以你可以自己创建对象来当锁,但是对于多个线程,必须使用同一把锁才可以实现同步,这也是非常直观的
2)同步方法
例如:
private synchronized void method(){
需要被同步的代码
}
A.同步方法并没有直接写出锁对象,但是也是有锁对象的,普通的的同步方法的锁对象是当前类(也就是this),静态的同步方法的锁对象是当前类的字节码文件(这个会在后面补充)
3.同步的好处和弊端
同步的好处是:同步的出现解决了多线程的安全问题
同步的弊端是:当线程很多时,每个线程都要上去判断同步的锁,这很耗费资源,会降低程序运行的效率
Five——JDK5中Lock锁的使用
1.虽然我们前面学习了同步方法和同步代码块通过锁对象来解决同步问题,但是我们并没有直观的看出在哪里上了锁,在哪里释放了锁(汗其实我觉得已经够直观了),为了更加直观的表达如何加锁和如何释放锁,JDK5以后提供了一种新的锁对象Lock。
2.通过查阅API我们知道,Lock是一个借口,那么很显然我们需要的实现了Lock接口后的实例对象,API给出了几个已知的实现了Lock接口的方法ReentrantLock, ReentrantReadWriteLock.ReadLock,
ReentrantReadWriteLock.WriteLock ,下面给出具体的代码实现,需要注意
1).使用Lock接口的实例对象作为锁对象的方法,不要忘记导包
2).lock.lock()是上锁,lock.unlock()是释放锁,所以上锁之后,如果出现异常,锁就打不开了,所以我们要加上一个try,finally块,使得一旦出现问题后,锁最终能够被释放
3.看上去锁对象比起synchronize要来的简单直观的多,但是为什么我们还是要重点讲synchronize呢?通过看API我们会发现,API中的同步几乎全部是用synchronize来实现的,这是其中一个重要的原因。
ackage cn.itcast_07;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} finally {
lock.unlock();
}
}
}
}
Six——同步的弊端:死锁
1.死锁指的是,两个或者两个以上的线程在执行过程中,因争抢资源而产生互相等待的情况
2.这里给出一个具体的案例
package cn.itcast_08;
/*
* 这是一个线程死锁的案例
*/
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
public void run() {
//1.这个死锁的案例,假设dl1抢到了执行权,那么它会进入这个if中
if (flag) {
//2.上了obja锁
synchronized (MyLock.obja) {
System.out.println("if obja");
//3.假设正好运行到这一步之前,被dl2抢去了执行权,dl1回到就绪状态
synchronized (MyLock.objb) {//6.无论谁抢到执行权,都要发现需要对方释放锁对象
//进入死锁状态
System.out.println("if objb");
}
}
} else {
//4.dl2进入到这个else语句中,上了objb锁
synchronized (MyLock.objb) {
System.out.println("if objb");
//5.走到这一步,发现需要释放obja锁被锁上了,需要等待obja锁被事释放才能往下执行
synchronized (MyLock.obja) {
System.out.println("if obja");
}
}
}
}
}