多线程(线程的状态)
多线程技术的一些特点:
有一个并发的特点,而且出现了一个随机性,是因为cpu不断的切换造成的
创建并不代表运行,你必须要start一次过后,它才会有资格去运行
创建线程的第二种方式—实现Runnable接口)
/*
创建线程的第一种方式:继承Thread类。
创建线程的第二种方式:实现Runnable接口。
1,定义类实现Runnable接口。
2,覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3,通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。
所以要在线程对象创建时就必须明确要运行的任务。
4,调用线程对象的start方法开启线程。
实现Runnable接口的好处:
1,将线程的任务从线程的子类中分离出来,进行了单独的封装。
按照面向对象的思想将任务的封装成对象。
2,避免了java单继承的局限性。
所以,创建线程的第二种方式较为常用。
*/
class Demo implements Runnable//extends Fu //准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行。
//通过接口的形式完成。
{
public void run()
{
show();
}
public void show()
{
for(int x=0; x<20; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
// Demo d1 = new Demo();
// Demo d2 = new Demo();
// d1.start();
// d2.start();
}
}
多线程(第二种方式的细节)
class Thread
{
private Runnable r;
Thread()
{
}
Thread(Runnable r)
{
this.r = r;
}
public void run()
{
if(r!=null)
r.run();
}
public void start()
{
run();
}
}
class ThreadImplimplements Runnable
{
public void run()
{
System.out.println("runnablerun");
}
}
ThreadImpl i = newThreadImpl();
Thread t = new Thread(i);
t.start();
class SubThread extendsThread
{
public void run()
{
System.out.println("hahah");
}
}
//SubThread s = newSubThread();
//s.start();
多线程(第二种方式的好处)
多线程(卖票示例)
下面输出的结果是Thread---0.Thread---1,Thread----2.Thread-----3都卖了100张票,为什么会现这个问题呢,下面图解解答困惑
这里面本来可以使用staticint num = 100来解决问题的也,用数据共享,但是为什么不这样做也,有可能现在这四个线程卖100张票,另外四个线程麦另外100张票,很有可能现在2个100张票啊,那如果是8个是不是100张啊
再有另外一种解决方法
这个运行会抛出异常:信息
原因分析:
//一个线程不能开启两次,会抛出无效线程状态异常
因为一开启,已经具备了运行资格,或者具备阻塞资格,你已经开启了为什么还要开启也
都发生异常了,你后面怎么还在执行也,,这个吧,你得先弄清楚这个里面有几个线程
说说这上面这个注释的有几个线程啊,2个吧
我一开始想错了,我以为是5个也,什么创建线程也Ticket t1 = new Ticket();这个才叫做创建线程,t1.start();只是开启吧,只是开启四次而已,你就以为有四个路径了是吧,你这是什么选手也
这个异常发生异常了,是不是在主线程发生的啊,怎么看呢,上面这个有一个38行,这里面再一调t1.start();是不是发生异常了,你是不是抛到主线程中来了啊,主线程调用的吗,对吧,可不是它出事了吧,而t1的代码在哪儿呢是不是在publicvoid run(){这里面边啊,要在这里面发生的异常才是t1的},在38行发生的异常是不是主线程的代码啊,主线程代码发生异常当然是在主线程上了,一般异常包含四部分信息,一是包含所属的线程,二是异常名称,三是异常信息,四是异常的位置
现在解决问题:为什么不继承,用实现Runnable接口都能解决问题也:我也没有听明白,就说了,我只封装线程任务,封装完过后,我只封装资源就是这个票的数据
t2.start();
t3.start();
t4.start();
解释:
Ticket t = new Ticket();//创建一个线程任务对象
Thread t1 = new Thread(t);//创建线程,线程一初始化是不是有任务(t)
多线程(线程安全问题的现象)
上面这个小程序存在着安全隐患。见图:
简单说明一下:最后num数值减到为1了,然后线程0去判断num.>0,然后也执行到这个里面了,因为cpu执行是随机的吗,这个时候也还没有执行到输出语句,没有减1这个时候也就去执行线程1去了,判断满足吧,然后线程2又去判断,满足,然后线程三去判断满足,这个时候也,0,1,2,3都执行到了,if这里面来了,这个时候再去执行输出语句,执行是不是就有问题了也,是的
为了能看到这个问题也:下面也我们这个线程也睡10毫秒,这里面有一个知识点,有时候这个异常信息只能trycatch不能抛,为什么呢,因为父类的run的方法没有抛,所以我们覆盖的时候也不能抛,这就是为什么不能抛的原因,只能处理异常,下面就是上面出现例外的情况
多线程(线程安全问题产生的原因)
线程安全问题产生的原因:
1,多个线程在操作共享的数据。
2,操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。
就会导致线程安全问题的产生。
多线程(同步代码块)
解决思路;
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,
其他线程时不可以参与运算的。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码 ;
}
在要执行的多条操作共享数据的代码加上synchronized就解决问题了,见下面代码:
多线程(同步的好处和弊端)
现在有一个疑问它是怎么解决的呢,看原理是加上了synchrorized包上了过后也,只能进来一个,别的进不来,那么它是怎么实现的呢:,接下为我们就把这个事说一说
这个obj就相当于一个对象的锁,当有人进来 的时候就加锁,当出去的时候也就解锁就完了,这就是线程同步解决安全隐患的原因,就是多个锁
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
多线程(同步的前提)
现在的问题来了,我变换一下代码:
为什么还会出现这个问题也,为什么把Object obj = new Object();放在num=100下面就没事呢,放在run里面就有事呢
分析:原因在这儿每个线程开启过后是不是都有自己的run方法,每个run方法里面是不是都有一个局部变量obj对象,意味着每个线程都有自己的锁,一旦有了自己的锁过后也,就不再同步了,一个同步代码块里面只有一个线程在运行,我有四个,因为我用的是四把锁,那为什么放在上面就没事呢,这个时候这个obj就变成了成员变量,是在Tickett这个对象中,跟num一样,这个obj在内存中是不是唯一的一个啊,如果你写在下面的话也是不是在堆内存中有四个啊
多线程(同步函数)
接下来我们准备用一个事例,来把刚才我们说的那些同步内容呢,简单的去练习一下,怎么在一个事例中如果存在多线程的情况下去分析它的安全隐患在哪儿,该如何去解决:
问这一个例子有没有案例隐患:怎么去分析:
分析:
要想知道这个程序的安全隐患在哪儿,你必须要遵循刚才我们所讲的,造成线程安全隐患的原因,原因里边也涉及到这几个安全要素,在线程运行的代码中,是否有共享数据,这个b就是共享,多个线程是不是在操作同一个b啊,必须的吗,同一个银行吗,这涉及安全隐患不,不涉及,b.add(100);是不是一句话啊,,你得先搞清楚线程的代码有哪些,不就是run方法这些吗,这种认识啊太父潜了,这个b调用了add方法吗,是不是run方法进栈以后,这个线程调用add方法,是不是add方法也进栈了啊,是不是进的同一个线程栈,是的,add方法也属于线程的代码
因为它被我们所定义的线程运行到了就这么简单,sum是不是也是共享数据啊,第一个条件满足了,好的,对共享数据的操作是不是不止一条啊,,是的,一条加法,一条输出,,现在也假设啊,sum为100,然后执行到sum = sum+num,sum=100吧,下面也还没有等输出也,这个时候这个线程就挂这儿了,另外一个线程进来了是不是又传了100啊,是不是又加上了,这个时候就是200了,另外一个输出多少也是不是也是200啊,现在为了看到效果了,在sum=sum+num加上代码,运行起来看
这样是不是出问题了啊,怎么办,把它封装起来吧,加上封装代码:
你发现了没有sum =sum + num;这个是不是函数代码:是啊,你发现了没有,函数本身是一种封装吧是哈,它带大括号的吧,同步代码块是不是也是封装体,他们两个都是封装体,一个是封装,另外一个是带有特性的封装,我们现在让函数具备同步性是不是ok了啊
如果我们把这个函数变成了同步以后,保证了我们的线程安全问题不再发生,这个对象没有用上吧,同步代码块是不是有锁啊,那到了同步函数的锁呢,同步函数的锁到底是谁呢,请听下面讲解
多线程(验证同步函数的锁)
现在说一现象:见代码:
这里面是不是只有0号线程啊,这就不明白为什么1,2,3线程不出来,是不是谁抢到谁运行啊,不一定每次都是0吧,问题在哪儿呢,你不是加同步了吗,什么该同步,什么不该同步,你没有搞清楚,其实我们都知道该同步的是不是if上面这儿啊,你在run方法加同步意味着什么啊,这里面来了0线程,1线程,2线程,0线程只要一进run方法已经进同步里面来了啊,它只要不出去,谁也进不来,1 2 3 有没有获取资源,有没有执行权,0线程sleep了,是不是1,2,3线程有执行权了啊,是得到执行权了,只是进不来而已,0线程把票卖完了,结果其他线程也没有进来,0不出去啊,0这辈子也不出去了,为啥啊,while(true)这是不是无限的啊,就是票卖完了,它还在这里面转也,这种方式转变为同步函数是不是不靠谱啊,。因为这个while(true)根本就不需要同步
告诉我怎么封装同步函数啊,把这个代码拿出来用一个函数封装一下是不是就完了啊
见代码:
下面就用这个例了来说一下这个函数的同步锁是哪一个
同步函数的使用的锁是this;
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
建议使用同步代码块。
事例的演示是:SynFunctionLockDemo
多线程(单例模式涉及的多线程问题)
现在讲一个之前我们已经学过的知识点,这有一部分与多线程技术相关,没有说完,所以我们现在把这个知识点重新拿起来再说一次
现在思考在多线程情况下有没有安全隐患
如果这个getInstance()它要被加载到了run方法中,是不是意味着要被多线程所执行,多线程执行的时候这个里面涉及到了共享数据,s就是共享数据,存不存在安全问题呢,饿汉式是不是不存在啊,谁来了是不是直接返回啊,饿汉式是没有问题的
接下来看看懒汉式中,这个getInstance放到了run方法中,就意味着可以被多线程所执行啊,一分析发现,如果这些if做为多线程运行的代码话也,在代码中有没有共享数据,有吧,s就是,有没有多条语句在操作这个s啊,有没有问题验证一下就知道了,一般的分析方法就是在第一条语句执行完过后,线程挂起了,然后另外一个线程进来了
我们先在函数上加synchroziced,这样解决问题不,分析,就算是在if过后线程挂起了,其他的线程对象进不来,解决问题了,但是另外一个问题就是,所以每次拿这个对象都要判断锁,是不是效率低,为了提高效率,把代码改写一下,改成加同步代码块,
小知识点this==对象名.class,
第一个if判断是解决效率问题,synchronized是解决线程安全问题
面试的时候都面这个
多线程(死锁示例)
下面我们准备用一个程序来看看示例DeadLockDemo
简单点来说,就是你的有我的锁,我有你的锁
死锁:常见情景之一:同步的嵌套
现在我们写一个死锁程序
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
while(true)
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"..if locka....");
synchronized(MyLock.lockb) {
System.out.println(Thread.currentThread().getName()+"..if lockb....");
}
}
}
else
{
while(true)
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..else lockb....");
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"..else locka....");
}
}
}
}
}
class MyLock
{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
上面这个加while(true)是为了看到效果,其实可以不用加的,看运气了,加while(true)是为了多试几次
郁闷答疑
为什么这个Objectobj = new Ojbect();放在外面跟放在里边差别怎么这么大,见图解