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 处理器
- 1.并发编程—— 线程安全(一)
- 系列文章:并发编程原则与技术(一)——线程安全
- 浅谈Java并发编程系列(一)—— 如何保证线程安全
- 【Java并发编程实践】— 线程安全
- java并发编程实战(二)—线程安全
- java并发编程(一) 线程安全(1)
- 并发编程学习系列:第一篇,线程安全(一)
- Java 并发编程(一)浅谈线程安全
- Java并发编程实战(一)线程安全
- 【Java并发编程一】线程安全
- Java并发编程(一)——线程
- Java并发编程中——线程安全
- Java并发编程的艺术(十二)——线程安全
- Java并发编程(十一)——线程安全
- java并发(一)线程安全概念
- Java并发(一)线程安全
- Java并发编程实践笔记(二)——chapter1(线程安全)
- 并发编程の线程安全
- 前端学习历程
- 几种进程间的通信方式
- Java数组
- 热管理设计Taitherm (ex-Radtherm) v12.1.1 Win64 & Linux64 2CD
- BZOJ 1009-GT考试(kmp+矩阵快速幂+DP)
- 1.并发编程—— 线程安全(一)
- 网页爬虫-用PHP的拓展库curl实现模拟登录慕课网
- 并发
- 视图控制器的生命周期方法
- Qt绘图程序
- classpath、path、JAVA_HOME的作用及JAVA环境变量配置
- hadoop计算单词出现次数
- 跟我一学linux基础(第四天)
- 剑指offer:跳台阶