------- android培训、java培训、期待与您交流! ----------
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫控制单元。
线程:是进程中的一个独立的控制单元,线程在控制着进程的执行。
一个进程中至少有一个线程。
例:
Java VM 启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
多线程的意义:可以使多部分代码同时执行。(cpu在某一时刻内只能执行一个线程,但由于cpu在线程间的快速切换,造成了多部分代码看上去在同时执行的效果。)
创建线程两种方法:
创建线程第一种方法:将类声明为Thread的子类,重写run方法。
class Demo extends Thread{//定义类继承Thread。
public void run(){//复写Thread类中的run方法。
System.out.println(“demo run”);
}
}
class ThreadDemo{
public static void main(){
Demo d = new Demo();//创建好一个线程。
d.start();//启动线程,并调用run方法。
}
}
多线程的一个特性:随机性:多个线程在抢夺cpu的执行权,谁抢到谁执行,至于执行多长,cpu说了算。
为什么要覆盖run方法?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
线程状态:
1、 被创建
2、 运行:start();或者等待时执行notify();
3、 冻结(睡眠、等待):sleep(time);或者wait();
4、 消亡:stop();或者run方法结束。
5、 *临时状态:线程在start()或者notify()以后,不一定运行,而是在一个临时状态上,等待cpu的执行权。(具备运行资格,但没有执行权)
Thread线程对象常用方法:
1、getName():获得线程名称,默认名称为”Thread-编号”,该编号从零开始。
自定义线程名称:
class Test extends Thread{
Test(String name){
super(name);//第一种方式:调用父类Thread的构造方法,给线程名称赋值。
//第二种方式:setName(name)l
}
}
2、Thread.currentThread():static方法,获取当前执行的线程对象。
创建线程第二种方法:声明实现Runnable接口的类,实现run方法。
class Ticket implements Runnable{//定义类实现Runnable接口
public void run(){//覆盖run方法
//线程执行代码
}
}
class TicketDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);//通过Thread类的Thread(Runnable target)构造方法创建线程,将Runnable接口的子类对象作为实际参数传递给Thread构造方法。
t1.start();//启动线程。
}
}
两种线程创建方式(实现方式和继承方式)的区别:
1、由于java类只支持单继承,所以实现方式避免了单继承的局限性。
在定义线程时,建议使用实现方式。
2、继承方式线程代码存放在Thread子类run方法中,实现方式线程代码存在接口的子类run方法中。
多线程的安全问题:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不卡可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式:同步代码块。
synchronized(对象){
//需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步的前提:
1, 必须要有两个或者两个以上的线程。
2, 必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程都需要判断锁,较为消耗资源。
如何找出多线程的安全问题?
1, 明确哪些代码是多线程运行代码。
2, 明确共享数据。
3, 明确多线程运行代码中哪些语句是操作共享数据的。
同步函数:
public synchronized void add(){
//同步代码
}
同步函数的锁是this:函数需要被对象调用。那么函数都有一个上所述对象引用,就是this。
如果同步函数被静态修饰后,使用的锁是该方法所在类的字节码文件对象(类名.class)。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象,就是类名.class,该对象的类型是Class。
单例设计模式。
一、饿汉式:
class Single{
private static final Singel s = new Singel();
private Singel(){}
public static Single getInstance(){
return s;
}
}
二、懒汉式:
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s == null)
s = new Singel();//当s==null时,可能会出现多个线程同时执行此行代码,出现安全隐患。
return s;
}
}
此懒汉式在多线程时会产生安全隐患,所以在多线程时,需要将getInstance()方法加上同步。但如果将getInstance()方法变为同步函数,则每次调用都需要判断锁,占用了系统资源,比较低效,因此将进行如下修改:
public static Single getInstance(){
if(s == null){
syschronized(Singel.class){
If(s == null)
s = new Single();
}
}
}
此懒汉式可以降低判断锁的次数,但是由于麻烦,因此在实际项目中,多用饿汉式实现单例模式。
面试问题:
懒汉式和饿汉式有什么不同?
懒汉式的特点在于实例的延迟加载。
懒汉式的延迟加载有没有问题?
有,如果多线程访问时,会出现安全问题。
怎么解决此问题?
可以用加同步的方式解决,用同步函数和同步代码块都可以,但是会有些低效,但用双重判断的形式可以解决效率问题。
加同步的时候,使用的锁是哪个?
该类所属的字节码文件对象。
同步的弊端:死锁:
同步中嵌套同步,而锁却不同。
死锁示例:
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
This.flag = flag;
}
public void run(){
if(flag){
synchronized(MyLock.locka){
System.out.println(“if locka”);
synchronized(MyLock.lockb){
System.out.println(“if lockb”);
}
}
}else{
synchronized(MyLock.lockb){
System.out.println(“else lockb”);
synchronized(MyLock.locka){
System.out.println(“else locka”);
}
}
}
}
}
class MyLock{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLockTest{
public static void main(String[] args){
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
在我们的程序中一定要避免死锁的出现。