13_线程安全

【线程安全概念】

当多个线程访问某一个类(或对象、方法)时,这个类始终都能表现出正确的行为,那么这个类就是线程安全的。

【synchronized】

可以在任意对象及方法上加锁,而加锁的这段代码被称为“互斥区”或“临界区”。

【不加synchronized和加synchronized的不同情况】

package com.higgin.part1;

public class MyThread extends Thread{

    private int count=5;

    @Override
    public void run(){   //分别synchronized加锁和不加锁运行
        count--;
        System.out.println(this.currentThread().getName() + ", count="+count);
    }

    public static void main(String[] args) {
        /**
         * 分析:
         * 当多个线程访问myThread的run方法时,以排队的方式进行处理(这里的排队是指CPU分配的先后顺序而定的),
         * 一个线程想要执行synchronized修饰的方法里的代码:
         *     1.尝试获得锁
         *     2.如果拿到锁,执行synchronized代码 体的内容;拿不到锁,这个线程机会不断的尝试获得这把锁,直到拿到为止(这里有多个线程竞争这把锁)
         *
         */
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt,"t1");
        Thread t2 = new Thread(mt,"t2");
        Thread t3 = new Thread(mt,"t3");
        Thread t4 = new Thread(mt,"t4");
        Thread t5 = new Thread(mt,"t5");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

【运行结果:不加synchronized锁(结果无法确定)】

【运行结果:加synchronized锁】

【多个线程多个锁的情况(区别对象锁和类锁)】

package com.higgin.part2;

public class MyThread {
    private static int num=0;

    /**
     * 分别用加static和不加static来运行
     * 不加static : 对象锁
     * 加上static : 类锁
     */
    public synchronized void printNum(String tag){
        try {
            if(tag.equals("a")){
                num=100;
                System.out.println("tag=‘a‘,set num OK !");
                Thread.sleep(1000);
            }else if(tag.equals("b")){
                num=200;
                System.out.println("tag=‘b‘,set num OK !");
            }
            System.out.println("【finally】tag = "+tag +", num = "+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //创建两个不同的对象
        final MyThread m1 = new MyThread();
        final MyThread m2 = new MyThread();

        Thread t1= new Thread(new Runnable() {
            @Override
            public void run() {
                m1.printNum("a");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m2.printNum("b");
            }
        });

        t1.start();  //如果线程安全,必须得等t1执行完run方法,释放锁之后,t2才能获得锁执行run方法
        t2.start();
    }
}

【运行结果:不加static,非静态方法,为对象锁,可以看到t1和t2两个线程时并发执行的】

【运行结果:加上static,静态方法,为类锁,t1和t2同步执行】

【对象的同步和异步】

同步synchronized:

  同步的概念就是共享,要牢记“共享”两个字,如果不是共享的资源,没有必要同步。

异步:asynchronized

  异步的概念就是独立,相互之间不受到任何制约。比如http请求中,在页面发起Ajax请求时,我们依然可以继续浏览或者操作页面内容,二者之间没有任何关系。

[ 线程安全的特性 ]

同步的目的就是为了线程安全,线程安全的两个特性:原子性(同步)、可见性。

package com.higgin.part3;

/**
 * 对象锁的同步和异步问题
 */
public class MyObject {
    private int num=0;

    public synchronized void setNum(int num){
        try {
            Thread.sleep(4000);
            this.num= num;
            System.out.println(Thread.currentThread().getName()+",set num = "+this.num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**     * 针对的是同一个对象的对象锁
     * 加上synchronized :当一个线程已经在执行setNum()方法的时候,另一个线程无法访问readNum方法
     * 不加synchronized :当一个线程已经在执行setNum()方法的时候,另一个线程可以访问readNum方法
     */
    public void readNum(){
        System.out.println(Thread.currentThread().getName()+", get Num = "+this.num);
    }

    public static void main(String[] args) {
        final MyObject mo = new MyObject();  //注意是一个对象!如果是两个对象,本身的锁已经不同

        /**
         * t1线程先持有对象的锁,t2线程可以异步的方式调用对象中的非synchronized修饰的方法
         * t1线程先持有对象的锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法,则需等待t1释放锁,也就是同步执行
         */
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.setNum(100);
            }
        },"t1"); 

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.readNum();
            }
        },"t2");

        t1.start();
        t2.start();
    }

}

【运行结果:readNum()不加synchrobized锁,t1持有setNum方法的锁,但t2不会去等待t1释放锁,直接去执行readNum方法】

【运行结果:readNum()加上synchronized锁,t1持有setNum方法的锁,t2必须等待t1释放锁才能获得readNum方法的锁】

【问:当一个线程进入一个对象的synchronized的方法时,其他线程是否可以进入此对象的其他方法?】

答:

1.如果是创建的不同对象(如myObj1、myObj2对象),那么两个对象执行各自的方法不会相互影响,为异步的。

2.如果是同一个对象(只有一个myObj对象),如果一个线程进入一个对象的synchronized修饰的方法,如果其他的那个方法是非sychronized方法,那么是可以访问的。

3.如果是同一个对象(只有一个myObj对象),如果一个线程进入一个对象的synchronized修饰的方法,如果其他的那个方法是sychronized方法,那么是不可以访问的。

时间: 2024-11-05 14:37:34

13_线程安全的相关文章

各科基础详实

一. Java基础部分 1. JAVA的基本数据类型有哪些 ?  String 是不是基本数据类型 ? 2. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 3. Java有没有goto? 7 4. 说说&和&&的区别. 7 5. 在JAVA中如何跳出当前的多重嵌套循环? 7 6. switch语句能否作用在byte上,能否作用在long上,能否作用在String上? 8 7. short s1 = 1; s1 = s1 + 1;有什么

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 1 2 3 4 5 6 7 new Thread(new

201709018工作日记--线程状态的转换

先来张图: 线程在一定条件下,状态会发生变化: 1.新建状态(New):新创建了一个线程对象 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权. 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码. 4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行.直到线程进入就绪状态,才有机会转到运行状态.阻塞的情况分三种:   (一).等

POSIX 线程详解(经典必看)

总共三部分: 第一部分:POSIX 线程详解                                   Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  2000 年 7 月 01 日 第二部分:通用线程:POSIX 线程详解,第 2部分       Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  20

线程的控制和线程池

一.WaitHandle: ”.Net 中提供了一些线程间更自由通讯的工具,他们提供了通过"信号"进行通讯的机制 可以通过ManualResetEvent,AutoResetEvent(他是在开门并且一个 WaitOne 通过后自动关门)来进行线程间的通讯 waitOne:    等待开门 Set:           开门 Reset:       关门 static void Main(string[] args) { ManualResetEvent mre = new Manu

内存池、进程池、线程池

首先介绍一个概念"池化技术 ".池化技术 一言以蔽之就是:提前保存大量的资源,以备不时之需以及重复使用. 池化技术应用广泛,如内存池,线程池,连接池等等.内存池相关的内容,建议看看Apache.Nginx等开源web服务器的内存池实现. 起因:由于在实际应用当中,分配内存.创建进程.线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作.           因此,当程序中需要频繁的进行内存申请释放,进程.线程创建销毁等操作时,通常会使用内存池.进程池.

线程高级

例题,哲学家用餐: 在一张餐桌上坐着五个哲学家,但是却只有五根筷子,每个哲学家只有一根筷子,所以当一个哲学家要夹菜的时候需要用他旁边那个哲学家的筷子,被拿走筷子的哲学家则只能等待那个哲学家吃完放下筷子他才能夹菜. 示意图:  设计思路: 首先编写一个筷子类,每根筷子都是一个对象,这个类里有拿起.放下两个方法:当一个哲学家线程来调用拿起方法时,下一个哲学家线程就要进入等待状态,然后这个哲学家线程调用放下方法后,就激活那个等待中的哲学家线程,以此循环,轮流使用筷子. 代码示例:  接着设计哲学家类,

缓冲池,线程池,连接池

SSH:[email protected]:unbelievableme/object-pool.git   HTTPS:https://github.com/unbelievableme/object-pool.git 缓冲池 设计要点:包含三个队列:空缓冲队列(emq),装满输入数据的输入的队列(inq),装满输出数据的输出队列(outq),输入程序包括收容输入(hin),提取输入(sin),输出程序包括收容输出(hout)和提取输出(sout). 注意点:输入程序和输出程序会对缓冲区并发访

能够在子线程绘画的View SurfaceView

转载请注明出处:王亟亟的大牛之路 近期两天都没有写文章,一方面是自己在看书.一方面不知道写什么,本来昨天想写Glide或者RxAndroid的东西结果公司的"狗屎"网怎么都刷不好Gradle我也是无语了(FQ也没用).准备今天背着笔记本 回家搞.真是服了.. 抱怨的话不说了,来看下这一篇要讲的主角 SurfaceView,关于SurfaceView的文章事实上在别的一些网站上也有,由于我之前没写过,所以也就一直没整这部分的内容(别人写的好坏反正找好的点自己吸收吧,嘿嘿) 问题:Surf