调用多线程的关键是要重写run()方法,所以继承thread类和实现runnable接口本质上是一样的,都要重写run(),只不过继承只能有一个父类,更推荐使用实现接口的方式
几个小问题:
启动线程应该是start()而不是直接去run()
线程的相关调度完全看CPU
在代码中设置优先级似乎没用,主要看CPU调度
sleep方法,Thread.sleep(1000),当前线程睡眠1秒。需要知道的是,1秒后,线程是回到可执行状态,并不是执行状态,什么时候执行那是由cpu来决定的。所以sleep(1000)并不是在睡眠1秒后立即执行
yield方法,yield()方法只是把线程的状态由执行状态打回准备就绪状态,所以,执行这个方法后,有可能马上又开始运行,有可能等待很长时间。实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程,
关于stop和interrupt
interrupt()方法是给处于阻塞的线程,如sleep(),抛出异常,使线程退出阻塞,中断发生,但线程仍然在进行,有个好处是能在异常中给出通知。
stop方法就是强行关闭线程,后遗症就多,最好不用
在启用多线程后,首先要解决同步问题,然后要解决线程间通信问题~
先引入窗口卖票的例子来说明下同步的问题
某电影院正在上映《速度与激情7》,共有100张票。它又三个售票窗口正在售票。请设计一个应用程序来模拟该电影院的售票
主程序:
窗口:
走起,有问题
Ticket在run内定义的,实际上互不影响了
拿出来
正常了一些~
利用继承去实现,则需要直接new三个窗口
实现接口的时候,是由一个类 new出来的,是同一个类的引用,这就把业务和数据分开了,数据只是在类里面(Window),而业务由线程完成,实质上票数是共享的;
而继承的方法,直接new出三个线程,数据也在线程中,三个窗口独立,互不干涉,所以出现同一张票卖多次
这是声明runnable的另一个好处,数据与业务的分离,前面提到一个好处是继承只能有一个父类
这个时候应该加一个static,声明静态共享
另外再加一个需求,实际处理中肯定是要操作时间的,数据也要返回服务器,所以要加一个sleep,但是runnable中没有这个,加currentThread引用。注意,runnable只有一个方法,那就是run(),别的一概没有,想要用就加引用
但是实际上,还是会出现同一张票卖多次的情况,自减这一步实际上是两步,先取值,再自减,很有可能CPU在这个间隔内把卖票的线程挂起,没来得及自减,而这时另一个线程进来,正好可以运行,票数没来得及变,这也会造成最后一张票出现负数的情况
线程安全问题
有3个先决条件:多线程、共享、非原子操作
其中前两者不可避免,非原子操作,也即一个可以分成多步的操作,这个间隙会出问题。实质就是多个线程同时获得对某个对象的操作权。
解决方法
同步机制。使用关键字synchonized。使得资源获得锁,在一个线程使用这个对象的时候,即使其他线程也可以调用,被锁着的对象无法再次被操作。
Synchonized把代码段包起来,这样没有实际效果,因为他的参数,内部类实现的锁对象是在run方法内,此时相当于每一个进程都有自己的一把锁,这就等于没有
把锁对象拿出来实现,再传回去,但此时有新问题,别人进不来,一直被锁着
锁应该在循环内,但又有一个问题,当ticket=1的时候,三个线程都能进来,导致最后一张票出了问题
锁内再判断一下
同步锁的好处很明显,缺点就是开销大,消耗资源~
然后可以规整一下,把sellTicket作为一个方法拿出来,在run里调用,另外它的object就是一个锁对象,至于是什么对象,可以自己单独定义。
方法2:也可以把synchronized作为类似一个类定义的方式写出来,是一个同步块,成了一个同步函数,此时没有object了,锁从哪来的?虚拟机会自己找一个对象,此例中,由于是同一个window引用的,这里的锁实际上是调用了this。
OK,完整代码
主程序:
<span style="font-size:14px;">public class SellTicketDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Window window= new Window();
Thread window1 = new Thread(window,"window1:--");
Thread window2 = new Thread(window,"window2:----");
Thread window3 = new Thread(window,"window3:-------");
window1.start();
window2.start();
window3.start();
}
}
</span>
线程窗口:
<span style="font-size:14px;">public class Window implements Runnable {
int ticket = 100;
MyClass obj = new MyClass();
@Override
public void run() {
while(ticket > 0){
sellticket();
}
}
synchronized void sellticket(){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+" 正在卖第"+ (ticket--) +"张票");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else{
System.out.println("获取到了锁,但是没有票了");
}
}
}
//这个object 让系统判断是不是一个类,也就是说一把锁
class MyClass{
}
</span>
以上为实现接口的方式,用继承thread类看看有什么不同
由于继承,直接new出两个线程,相当于分别获得ticket,所以锁对象应该加一个static
但是很多时候代码较多,看着不方便,看不见在哪上锁,在哪解锁
Java后来提供了更方便的 ReentrantLock类和lock方法,Lock实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
OK,在代码较多的时候用这个很方便
ž 同步弊端
› 影响效率
› 如果出现了嵌套锁,容易产生死锁
ž 死锁问题及其代码
› 死锁是指两个以上的线程在执行过程中,因为争夺资源而产生的一种相互等待的现象
复制,再添加一个线程,走起
在新建另一个锁的时候,new两个对象实现两个锁,现在不实例化这个类,直接用成员变量
变成静态的,加上static,可以使用 类名.变量名来使用 省事
模拟需要两个锁
嵌套锁,有了A锁后还需要B锁
甲拿了A锁,想要B锁,但是B锁目前又在乙那,可以利用flag变量来实现这个场景
怎么给两个线程传两个参数?
可以初始化的时候重载两个构造函数,现在使用的默认的无参构造函数
如何用到线程上了,初始化的时候一个给true,一个给false
理想情况下,线程1先拿到A锁,然后再拿到B锁,结束,释放;然后线程2进来,拿B再拿A。
走起,停住了,死锁产生
第一次线程0顺利拿到A B锁,第二次拿到A后,B锁被线程1拿去
虽然有锁,但是那一时刻只是拿了A锁,这时CPU调度,线程1发现锁2能用,所以就访问了另一个代码块~
这时候CPU看线程0不能用,0在等待,又去调度1,但是1也只有等待,从而互相等待,死锁了~
完整代码
main:
<span style="font-size:14px;">public class MyDeadLockDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread thread1 =new MyThread(true);
MyThread thread2 =new MyThread(false);
thread1.start();
thread2.start();
}
}
</span>
thread
<span style="font-size:14px;">public class MyThread extends Thread {
private static int ticket =100;
boolean flag = false;
static ReentrantLock lock = new ReentrantLock();
//无参构造函数
public MyThread() {
// TODO Auto-generated constructor stub
}
//有参
public MyThread(boolean b) {
// TODO Auto-generated constructor stub
flag=b;
}
@Override
public void run() {
while(true){
if(flag){
synchronized (MyLock.ObjA) {
System.out.println(getName()+ " I got lock A in if");
synchronized (MyLock.ObjB) {
System.out.println(getName()+ " I got lock B if");
System.out.println(getName()+ " Now i can do my job..");
}
}
}else{
synchronized (MyLock.ObjB) {
System.out.println(getName()+ " I got lock B in else");
synchronized (MyLock.ObjA) {
System.out.println(getName()+ " I got lock A in else");
}
}
}
}
}
}
</span>
lock
<span style="font-size:14px;">public class MyLock {
public static Object ObjA= new Object();
public static Object ObjB= new Object();
}
</span>