为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程运行的代码。
实现方式和继承方式有什么区别?
实现方式的好处:避免了单继承的局限性
在定义线程时,建议使用实现方式(Runnable接口)
两种方式区别:
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable,线程代码存放在接口的子类的run方法中。
多线程的安全问题:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。
另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程执行完。在执行过程中,其他线程不可以参与执行。
java对于多线程的安全问题提供了专业的解决方式。
同步代码块
synchronized(对象)
{
需要同步的代码块
}
对象如同锁,持有锁的线程可以再同步中执行。
没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。
同步的前提:
1、必须由两个或两个以上的线程。
2、必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程都需要判断锁,较为消耗资源。
如何找多线程中的安全问题:
1、明确哪些代码是多线程运行代码。
2、明确共享数据
3、明确多线程运行代码中哪些语句是操作共享数据的
如果同步函数被静态修饰,锁是什么呢?
通过验证,发现不是this,因为静态方法中也不可以定义this。
静态进内存是内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是Class。
静态的同步方法:使用的锁是该方法所在类的字节码文件对象。类名.class。
注:此代码不能解决这个问题,由于内存模型的寻址问题,它在一些编译器上是无序写入的。所以应使用饿汉式
双重检查锁定
为处理清单 3 中的问题,我们需要对 instance 进行第二次检查。这就是“双重检查锁定”名称的由来。将双重检查锁定习语应用到清单 3 的结果就是清单 4 。
清单 4. 双重检查锁定示例
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
双重检查锁定背后的理论是:在 //2 处的第二次检查使(如清单 3 中那样)创建两个不同的 Singleton 对象成为不可能。假设有下列事件序列:
线程 1 进入 getInstance() 方法。
由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
线程 1 被线程 2 预占。
线程 2 进入 getInstance() 方法。
由于 instance 仍旧为 null,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。
线程 2 被线程 1 预占。
线程 1 执行,由于在 //2 处实例仍旧为 null,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance。
线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。
线程 1 被线程 2 预占。
线程 2 获取 //1 处的锁并检查 instance 是否为 null。
由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。
双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
回页首
无序写入
为解释该问题,需要重新考察上述清单 4 中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 instance 来引用此对象。这行代码的问题是:在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的。
什么?这一说法可能让您始料未及,但事实确实如此。在解释这个现象如何发生前,请先暂时接受这一事实,我们先来考察一下双重检查锁定是如何被破坏的。假设清单 4 中代码执行以下事件序列:
线程 1 进入 getInstance() 方法。
由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。
线程 1 被线程 2 预占。
线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。
线程 2 被线程 1 预占。
线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
此事件序列发生在线程 2 返回一个尚未执行构造函数的对象的时候。
为展示此事件的发生情况,假设为代码行 instance =new Singleton(); 执行了下列伪代码: instance =new Singleton();
mem = allocate(); //Allocate memory for Singleton object.
instance = mem; //Note that instance is now non-null, but
//has not been initialized.
ctorSingleton(instance); //Invoke constructor for Singleton passing
//instance.
这段伪代码不仅是可能的,而且是一些 JIT 编译器上真实发生的。执行的顺序是颠倒的,但鉴于当前的内存模型,这也是允许发生的。JIT 编译器的这一行为使双重检查锁定的问题只不过是一次学术实践而已。
为说明这一情况,假设有清单 5 中的代码。它包含一个剥离版的 getInstance() 方法。我已经删除了“双重检查性”以简化我们对生成的汇编代码(清单 6)的回顾。我们只关心 JIT 编译器如何编译 instance=new Singleton(); 代码。此外,我提供了一个简单的构造函数来明确说明汇编代码中该构造函数的运行情况。
死锁:同步中嵌套同步
wait();
notify();
notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中的线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说等待和唤醒必须是同一个锁。
而锁可以使任意对象,所以可以被任意对象调用的方法定义在Object类中。
多线程之等待唤醒机制代码部分
class Res
{
String name;
String sex;
boolean flag =false;
}
class Input implements Runnable
{
private Res r;
input(Res r)
{
this.r=r;
}
public void run()
{
int x =0;
while(true)
{
synchronized(r)
{
if(r.flag)
try{r.wait();}catch(Exception e){}
if(x==0)
{
r.name="mike";
r.sex="man";
}
else
{
r.name="丽丽";
r.sex="女女女女";
}
x=(x+1)%2;
r.flag=true;
r.notify();
}
}
}
}
class Output implements Runnable
{
private Res r;
Out(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
synchronized(r)
{
if(!r.flag)
try{r.wait();}catch(Exception e){}
System.out.println(r.name+"......."+r.sex);
r.flag =false;
r.notify();
}
}
}
}
class InputOutputDemo
{
public static void main(String[] args)
{
Res r=new Res();
Input in =new Input(r);
Output out = new Output(r);
in.start();
out.start();
}
}