进程:是一个正在执行中的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。CPU在不同程序之间做快速的切换,但切换的速度极快,快到人无法感知的程度,
所以,在我们看来这些程序是在同时执行的。
线程是进程中的内容,是程序的控制单元,或者叫执行路径。
多线程:进程当中有多个线程,生活中下载程序就是多线程的。
多线程可以提高效率。
创建多线程有两种方法:
第一种方式,继承thread类,成为他的子类,并且覆盖run方法。
覆盖run方法就是为了将自定义代码存储在run方法中,让线程运行自定义代码。
调用线程的start,启动线程,调用run方法。run方法用于存储线程需要运行的代码。
多线程的一个特性,随机性。
start方法开启以后,程序不一定能执行,如果不能被执行就牌临时状态,也叫阻塞状态。
Thread类中的run()方法用于存储线程需要运行的代码。
start方法开启一个新的线程,去调用自己覆盖run方法。
只执行自己的run方法就不再是多线程了,因为没有调用start。只有一个线程。
static Thread currentThread():获取当前线程对象。
getName():获取线程名称;
setName():设置线程名称。
线程被创建以后不一定能马上执行,可能处于临时状态中,具备执行资格,但没有执行权。
局部变量在每一个线程区域中都有自己独立的一份。
一个线程对象启动以后,不能再第二次启动。比如:t1.start(),t1.start();不能调用两次。
/*
* 第一种方式,继承thread类,成为他的子类,并且覆盖run方法。
覆盖run方法就是为了将自定义代码存储在run方法中,让线程运行自定义代码。
调用线程的start,启动线程,调用run方法。run方法用于存储线程需要运行的代码。
*/
package com.thread;
public class Thread_1 extends Thread
{
public void run()
{
for(int x=0; x<100; x++)
{
System.out.println("hello world");
}
}
public static void main(String[] args)
{
Thread_1 t = new Thread_1();
t.start();
new Demo().start();
for(int x=0; x<100; x++)
{
System.out.println("hello world.......");
}
}
}
class Demo extends Thread
{
public void run()
{
for(int x=0; x<100; x++)
{
System.out.println("Demo run....");
}
}
}
-----------------------------------------------------------------------------------------------
创建线程的第二种方式:实现Runable接口
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法
将线程要运行的代码存放在该run方法中
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去指定指定对象的run方法。就必须明确该run方法所属的对象。
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
实现方式和继承方式有什么区别?
实现方式好处在于避免了单继承的局限性。一般在定义线程时建议使用实现方式。
也就是说,如果采用继承方式,那么去继承Thread的这个类就被限定了,因为JAVA是单继承的,
所以,继承了Thread以后就不能再继承其他类了,这样一来就限制了该类。
而Runnable刚好避免了这种局限性
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类的run方法中。
线程是随机的
多线程容易出现安全问题,所以写多线程的时候要注意安全问题。
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,
其他线程不可以参与执行。
对于这样的安全问题有一个解决方式,就是同步代码块。
synchronized(对象)
{
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取CPU执行
也进不去,因为没有获取锁
synchronized中对象也就是俗称的锁,当然在各种的翻译中有不同的说法,
有叫监视器的,也有叫锁旗标,也有叫同步的。这些都只是一个名称。
同步的前提:
1.必须要有两个以上的线程。
2.必须是多个线程使用同步一个锁,必须保证同步中只能有一个线程在运行。
好处:解决多线程的安全问题,
弊端:多个线程都需要判断锁,较为消耗资源。
火车上的卫生间就是标准的锁,意思就是说同一时间,只能有一个人在卫生间里面
判断代码是否需要被同步,看看这个代码是否是共享数据。
如何找问题:
1.明确哪些代码是多线程运行代码
2.明确共享数据。
3.明确多线程运行代码中哪些语句是操作共享数据的。
同步有两种表现形式,分别是同步代码块和同步函数。
一般标识出来的异常都不是Runtime异常。
一般成员都是共享数据
同步的两个表现形式,
同步代码块,synchronized(对象){ 需要同步的代码 }
同步函数
public synchronized void method(){ }
同步函数用的是哪一个锁呢,或者说用的哪一个对象呢?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this
所以同步函数使用的锁或者说对象就是this,本类所属对象。
线程开启不一定立刻执行。
如果同步函数被静态static修饰使用的锁是什么呢?
通过验证,发现不再是this,因为静态方法中也不可以定义this.
静态进内存时,内存中没有本类对象,但是一定该类对应的字节码文件对象。
类名.class (类名点class) 该对象的类型是大写的Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象, 类名.class
这个对象是唯一的,因为字节码文件也是唯一的。
单例设计模式
饿汉式与赖汉式的区别,
赖汉是类的延迟加载,在多线程的时候容易出现安全问题,为了解决这个问题,
可以加同步代码块或者同步函数,加同步函数时所用的锁,是该类所属的字节码文件对象
类名.class
所以写单例设计模式的时候,一般建议写饿汉式,饿汉式不会在多线程时出现安全问题。
//饿汉式单例设计模式
class Single
{
public static final Single s = new Single();
private Single()
{
}
public static Single getInstance()
{
return s;
}
}
//面试一般容易面这种问题
//赖汉式单例设计模式
//赖汉式在多线程时会产生安全问题,
//所以要加同步代码块或者同步函数,但加了同步会比较低效
//加同步的时候用的是该类所属的字节码文件对象 类名.class
class Single2
{
public static Single2 s = null;
private Single2()
{
}
public static /*synchronized*/ Single2 getInstance()
{
if(s==null)
{
synchronized(Single2.class)
{
if(s==null)
s = new Single2();
}
}
return s;
}
}
死锁:
同步中嵌套同步
写程序时尽量避免死锁。
/*
死锁程序
*/
package com.thread;
public class DeathLock implements Runnable
{
private boolean flag;
DeathLock(boolean flag)
{
flag = flag;
}
public void run()
{
if(flag)
{
sychronized(Lock.obja)
{
System.out.println("if obja");
sychronized(Lock.objb)
{
System.out.println("if objb");
}
}
}
else
{
sychronized(Lock.objb)
{
System.out.println("else objb");
sychronized(Lock.obja)
{
System.out.println("else objb");
}
}
}
}
public static void main(String[] args)
{
}
}
class Lock
{
static Object obja = new Object();
static Object objb = new Object();
}
等待唤醒机制:
一般不建议用静态static 修饰成员,因为静态成员生命周期长,耗资源。
wait() notify(),notifyAll()属于Object中的成员,Thread继承了。
都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用
在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
不可以对不同锁中的线程进行唤醒,换句话说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。