1.并发编程—— 线程安全(一)

来源:互联网 发布:linux 内核版本 编辑:程序博客网 时间:2024/05/16 07:24

synchronized关键字和线程安全

代码示例

JAVA中实现多线程有两种方式,第一种是继承Thread类,第二种是实现Runnable接口。

先看一个例子,例子使用继承Thread类实现。

package com.demo;public class MainThread extends Thread {    private int number = 5;    @Override    public  void run() {        this.number = this.number -1;//[1]        System.out.println(this.currentThread().getName()+" number="+number);//[2]    }    public static void main(String[] args) {        MainThread thread = new MainThread();        Thread t1 = new Thread(thread, "t1");        Thread t2 = new Thread(thread, "t2");        Thread t3 = new Thread(thread, "t3");        Thread t4 = new Thread(thread, "t4");        Thread t5 = new Thread(thread, "t5");        t1.start();        t2.start();        t3.start();        t4.start();        t5.start();    }}

代码说明

例子中MainThread继承Thread类后重写run方法,在run方法中对成员变量number进行“-1”操作,然后打印当前线程名和number的值。main函数里面首先创建了一个名叫thread的MainThread对象,然后利用同一个MainThread对象实例化了五个线程,接着开启这五个线程。这段代码很简单,无非就是我有五个线程{t1,t2,t3,t4,t5},这五个线程开启时访问MainThread的run方法,然后对number进行“-1”操作。

运行结果

t3 number=2t5 number=0t2 number=2t4 number=1t1 number=2

原因分析

造成这种现象的原因是计算机CPU的资源有限,{t1,t2,t3,t4,t5}分别抢占CPU资源,并发执行的情况下它们没有执行先后顺序,谁抢到谁就执行。一些线程刚刚进入run方法,CPU就被抢走,而其它线程可能刚刚执行[1]或者执行完[2]CPU也被抢走,还有就是eclipse打印存在延时,从而造成了这种乱象。(有兴趣的可以去看看操作系统相关的书,JVM是虚拟的操作系统,情况和操作系统相同,推荐《计算机操作系统》汤小丹版)这时MainThread不是线程安全的。

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

解决办法

同步:同步的概念就是共享,如果没有资源共享就没有必要同步,同步的目的是线程安全,线程安全有两个特征{原子性,可见性}

并发打破了程序执行的顺序性、封闭性、可再现性,但是有一些操作我们希望它能一次连续执行完,然后再释放CPU。为了解决线程不正当竞争JVM的CPU资源,JAVA可以synchronized来实现线程同步,上述例子具体操作就是就是在run方法的void前加synchronized修饰,同步使得线程抢到CPU后,run方法时要么不执行要么一次性执行完。

synchronized:可以修饰任意的类、方法、代码块,被它修饰的这段代码称为互斥区或者临界区

加synchronized之后

    @Override    public  synchronized void run() {        this.number = this.number -1;        System.out.println(this.currentThread().getName()+" number="+number);    }

运行结果

t2 number=4t1 number=3t4 number=2t3 number=1t5 number=0

新的问题:并发过大,比如1000并发,一个线程释放资源后,其它999个线程去抢夺资源,此时CPU利用率急剧上升,可能导致机器宕机,程序开发过程中应该尽量避免这种问题,后面的博文会继续讨论这个问题。

对象锁和类锁

synchronized不是针对当前代码块加锁,而是针对对象或者类(静态方法前加synchronized)加锁

代码示例

    package com.demo;public class MainThread {    private int number = 0;    public synchronized void setNumber(int number, String name) {        try {            this.number = number;            System.out.println("thread " + name + " set number");            if ("m1".equals(name)) {                Thread.sleep(1000);            }            System.out.println("thread " + name  + ", number=" + this.number);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public static void main(String[] args) {        MainThread m1 = new MainThread();        MainThread m2 = new MainThread();        Thread t1 = new Thread(new Runnable() {            @Override            public void run() {                m1.setNumber(100, "m1");            }        });        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {                m2.setNumber(200, "m2");            }        });        t1.start();        t2.start();    }}

代码说明

这段代码中的MainThread有一个成员变量number和一个加synchronized的成员函数setNumber,setNumber给成员变量number赋值,然后进行一些简单操作,main方法中先创建了两个MainThread对象m1、m2和两个线程t1、t2,然后分别在线程的run方法中执行m1、m2的setNumber方法。

运行结果

thread m1 set numberthread m2 set numberthread m2, number=200thread m1, number=100

原因分析

synchronized只是针对对象加锁,多线程中有多少对象就有多少锁。t1和t2是两个线程,t1执行m1的setNumber,t2执行m2的setNumber,两个对象的setNumber相互独立,互不影响。线程t1先运行,打印出thread m1 set number,然暂停了一秒钟,此时线程t2运行,打印thread m2 set number和thread m2, number=200,接着一秒钟过去了,线程t1继续执行,打印thread m1, number=100。

解决办法

如果需要t1执行完setNumber,t2才能执行,此时需要加类锁,即把setNumber变成静态方法。

    private static int number = 0;    public synchronized static void setNumber(int num, String name) {        try {            number = num;            System.out.println("thread " + name + " set number");            if ("m1".equals(name)) {                Thread.sleep(1000);            }            System.out.println("thread " + name  + ", number=" + number);        } catch (InterruptedException e) {            e.printStackTrace();        }    }

运行结果

thread m1 set numberthread m1, number=100thread m2 set numberthread m2, number=200

总结

1.多线程有利于应用性能的提升,但是它打破了程序执行的顺序性、封闭性、可再现性,在访问一些共享资源时必须加锁;

2.synchronized可以修饰任意的类、方法、代码块;

3.synchronized只是针对对象加锁,多线程中有多少对象就有多少锁;synchronized修饰静态方法是一种特殊情况,此时为类锁。

今日英语:         multithreading 多线程         synchronize    同步         asynchronize   异步         progress       进程         thread         进程         processor      处理器
原创粉丝点击