Skip to content

Java笔记——多线程 #1

@XiangNick

Description

@XiangNick

线程的概念
cpu的执行资格
cpu的执行权

线程的生命周期

 start()

创建--------运行---------休眠
I sleep(times)
stop()I notify()
I wait()
消亡

如何将多个线程操作同一个共享数据:

比如去一个银行存钱,操作的都是同一个银行,如果采用继承Thread类的方法,每new一个Thread的子类建造线程,都会出来一个银行,因为银行这个对象是封装在你创建的Thread子类里。
方法一:将银行对象静态化static,这样就变成共有数据。但是不利于后期创建其他对象,就这样static化了,万一后期需要用到银行,创建其他银行不需要共享呢?
所以有方法二:
将某一类用implements实现Runnable接口,而不是继承Thread
然后把这个类的子类对象作为“任务”扔到 Thread t1 = Thread(子类对象)里去,这样尽管创建了多个Thread线程对象t1.t2.t3....,但是操作的任务都只是一个。(所谓任务,就是线程任务,也就是run方法内的代码)

线程出现安全隐患的原因:

判断步数:
步数1.查看是否有共享数据
步数2.如果有,查看操作这个共享数据的代码是不是不只一条,如果有多条则有隐患(比如之前的一个ticket_num先判断是否>0再输出,这是2个操作,一个判断,一个输出,可能判断完了,票数是大于0,有票,但是其他线程获得了cpu执行权,判断完,票数--,再回来,输出的就可能是0票或者负票,错误,或者像银行存钱,先sum = sum+num存钱,再输出,这也是2个操作,可能会有隐患)
是否有多条线程操控同一个共享数据。(这句话很抽象,可以结合上面的)

同步锁的理解问题:

线程的任务中,如果出现了安全隐患,则需要用同步锁来解除隐患,但是初学者可能会有这么一种想法:就是用同步锁之前,任务是CPU随机处理的,可能一会是A线程,还没处理完就去处理B线程了,但是加了同步锁后,A线程没处理完出来,B线程就不能进去处理。
首先来一句总体的易懂解释:
就是同步锁的出现,仅仅是锁住有安全隐患的任务“部分代码”!
比如

public void add(int num){
        synchronized (obj) {
        sum += num;
    System.out.println(Thread.currentThread().getName()+"浦发银行目前进账+"+sum);
                   }
    System.out.println("这里还是add函数哦");
}

我们用同步锁仅仅锁住了对于安全隐患点sum的2个操作,而任务函数的其他地方并没有锁,所以并不是执行完A线程的add任务再执行B线程,而是保证A线程执行到“sum的2个操作时不被干扰”!!!,等他执行完,你CPU随便执行其他线程,因为下面还有一个输出语句,所以这也算是把A线程执行一半就丢下去执行B线程了。
任务确实是CPU随机处理,而且一会是A线程,可能只处理一半,就跑到B处了,这句话是重点,因为如果A,B线程处理的是共享数据,同时共享数据在任务中被操作2次(eg:先赋值,后输出)就会有安全隐患,比如A先赋值,本来输出A的赋值数,可是却转到B线程,B赋值完再转回来,这样A线程就输出了B线程操作后的同一共享数据的值。这就是安全隐患,使用同步锁,可以保证A线程的任务环节之一:操作共享数据时不被打扰,其他环节,你想打扰就打扰,所以不存在变成“使用同步锁,多线程变成单线程了”,问题纠结的原因就在你同步代码块,封装的是任务函数部分代码,还是整个任务函数!

同步函数与同步代码块的锁区别

同步代码块的锁是自己定义放置的,任意的,一般为Object的对象obj
而同步代码块的锁是this,即哪个对象调用了用synchronized修饰的同步函数,他就是同步函数的锁。所以一般情况,同步代码块和同步函数的锁不一致,如果把同步函数的锁,即那个调用同步函数的对象放到同步代码块内,则两个锁一致,会同步。外部线程无法进入已经有线程进入的同步锁。
建议使用同步代码块,同步函数是同步代码块的简化,如果把锁选择用调用的对象,那么同步代码块就可以简化成同步函数。

静态同步函数的锁 : 是该函数所属字节码文件对象,可以用getClass方法获取(this.getClass()),也可以用当前类名.class获取
在TicketDemo.java文件中,Ticket t = new Ticket(); t调用了线程,t所属的类就是当前类,就用Ticket.Class获取

一般由synchronized修饰过的同步函数,都会持有this关键字,this也就是同步函数的锁,但是!由静态static修饰过的静态同步函数,不会有this关键字,那么它的锁是什么呢?可以确定的是,锁肯定是对象,不会是类或者其他,而static函数是类一加载就有的,不会有对象啊?其实,每一个class文件加载的时候,都会有一个Class.class文件加载,也就是都会有一个Class对象,所以,静态同步函数的锁,就是一开始的Class对象,如果想要用同步代码块和静态同步函数共用一个锁,则可以使用this.getClass()或者是该类名.class,比如Ticket.class,任何类名都有一个静态的属性,是小写class的属性,用类名.class可以获得他的字节码文件对象。2个都可以作为同步代码块的锁。(一个是this.getClass(),一个是类名.class)注意一个是大写的Class,一个是小写的class

多线程下的单例

//饿汉式
class Single
{
    private static final Single s = new Single();
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }
}
```java


//懒汉式

加入同步为了解决多线程安全问题加入双重判断是为了解决效率问题。
```java
class Single
{
    private static Single s = null;
    private Single(){}
    public static Single getInstance()
    {
        if(s==null)
        {
        synchronized(Single.class)      
           {
                     if(s==null)
            s = new Single();
            }
        }
        return s;
    }
}

0,1线程进来new了对象以后,s!=null ,后面的线程就可以不用判断锁,直接获得return s,所以加入同步为了解决多线程安全问题。加入双重判断是为了解决效率问题。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions