/**
* 并发编程
* 什么是并发编程?
* 并发编程是为了提高程序的执行速度,在宏观上使得多个任务同时执行,则需要启动多个线程
* ,但事实启动多个线程之后,不管针对单核cpu还是多核cpu线程进行上下文切换(cpu 通过)
* 给每一个线程分配时间片,只有说拿到时间片的线程才可以执行,通常时间片很短,所以才会
* 感觉到多个线程在并行操作,存在线程不安全性。一个正确执行的并发程序,满足并发编程
* 的三大特性,原子性、可见性、有序性
* 1) 原子性
* 所谓原子性是指一次或者多次操作中,要么所有的操作全部执行要么所有的操作都不执行
* 原子操作是不可分割的操作,一个原子操作中间是不会被其他线程所打断的
* int a = 10; //1 10赋值给线程工作内存中的变量a 原子操作
* a++; //2 拿a 进行a+1 赋值a 非原子操作
* int b = a; //3 拿a b=a 非原子操作
* a = a+1; //4 拿a 进行a+1 赋值a 非原子操作
*
* 2)可见性
* 如果在一个线程对共享变量做了修改,那么另外的线程立即可以看到修改后的最新值
* 3)有序性
* 有序性指的程序代码在执行过程中的先后顺序,由于编译器或计算机的优化,导致代码
* 的执行未必是开发者编写代码时的顺序
* int x = 10; 1 1->3->2
* int y = 0; 2
* x++; 3
*
* x++在int y=0之前得到执行,这种情况叫做指令重排序
* 在单线程环境中,无论怎么重排序,都不会影响最终的结果
* 在多线程环境中,如果有序性得不到保证,最终结果也可能与预期不符
*
* 线程同步问题
* 1)为什么需要同步?
* 一块资源被多个线程同时操作,如果没有任何的同步操作,就会发生冲突,因为无法保证每个
* 线程的执行结束时机,所以就无法控制最终结果
* 2)临界资源:同一时刻只允许一个线程访问的资源,临界是不可剥夺资源
* 临界区:访问临界资源的代码快
* 临界区特点: 提供线程独占式访问,也就是说若有一个线程正在访问该代码段,其他线程
* 想要访问,只有等待当前线程离开该代码段才可访问,这样就保证了线程安全
* 3)银行叫号系统
* a.某个号码被略过
* 线程A: index = index++ index=65++ -> 66 cpu将使用权转给了B
* 线程B: index = index++ index=66++ -> 67 sout(67) 线程A再也没有得到执行,最终66被略过
* b.某个号码重复
* 线程A:index = index++, 执行65++ -> 66,cpu将使用权给了B
* 线程B: index = index++, 执行65++ -> 66, sout(66), cpu将使用权给A
* c.超过给定的最大值 500
* 线程A: index=499, cpu将使用权给了B
* 线程B: index=499++,sout(500),线程再次判断,最终导致号码超过给定的最大值
*
* 出现以上问题的原因为原先代码并没有满足并发编程三大特性
private static int value = 0;
public static void main(String[] args) {
//改为线程安全
new Thread("A"){
@Override
public void run() {
int localValue = value;
while(localValue < 5){
if(localValue != value){
System.out.println("the value is updated to "+value);
localValue = value;
}
}
}
}.start();
new Thread("B"){
@Override
public void run() {
int localValue = value;
while(localValue < 5){
System.out.println("the value will be changed to "+(++localValue));
value = localValue;
//短暂睡眠,使得A线程进行输出
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
synchronzied关键字
* 提供一种排他机制,在同一时间内去操作synchornized封装的代码块或者方法
*
* 用法:
* 1)同步方法
* public synchornized void func(){
*
* }
*
* public synchornized static void func(){
*
* }
* 2)同步代码块
* private final Object lock = new Object();
* public void func(){
* synchornized(lock){
* ...
* }
* }
*
* 重写叫号程序
*
这张图是synchronized底层实现的两个重要部分
* 1)同步方法
* 常量池中多了ACC_SYNCHRONIZED标识符,标识当前的方法是一个同步方法,当方法被调用,调用指令会检查方法ACC_SYNCHRONIZED标识符是否被设置,如果设置,执行线程会先去获取monitor,获取成功之后才会去执行方法题,方法体执行完成之后或释放monitor。
* 2)同步代码块
* monitorenter
* 每一个对象都与一个monitor相关联,一个monitor的lock只能被一个线程在同一时间所
* 拥有,在一个线程尝试获取monitor的使用会发生以下事情:
* a.如果monitor的entry count为0,意味monitor的lock还没有被获取,某个线程获取
* 之后会对entry count+1,从此该线程就是这个monitor的所有者了
* b.如果一个已经拥有该monitor使用权的线程再次进入,会导致monitor的entry count+1
* (可重入锁)
* c.如果monitor已经被其他线程所拥有,其他线程尝试获取该monitor的额所有权时,会被陷入阻塞,直到monitor的entry count为0,才能再次尝试去获取
* monitorexit
* 释放对monitor的使用权,要释放对某个对象的monitor的使用权前提是首先获取该monintor的所有权,将monitor的entry count-1,如果entry count为0,那就表示该线程不再拥有该monitor的使用
回顾:
* 并发知识点
* 1、并发指的是多个线程针对同一个资源,不是同时操作,而是交替操作
* 2、临界资源 同一时间只允许一个线程访问
* 临界区 访问临街资源的代码
* 3、并发编程的三大特性
* 1)原子性 一个操作是不可分割,那就是一个原子操作,就说这个操作具有原子性
* 2)可见性 一个变量被多线程共享,如果一个线程修改了这个变量的值,其他线程
* 能够立即得知修改,我们称这个修改具有可见性
* 3)有序性 所有操作的执行有序的
* 4、线程安全
* 不考虑耗时和资源消耗,在单线程执行和多线程执行的情况下,最终得到的结果是
* 相同的,那么称这样的操作是线程安全的
*
* synchronized关键字
* 1)方法
* 注意:调用不同的对象的同步方法,非线程安全的
* 2)代码块
* synchronized(){
*
* }
- 课后练习:
- 启动两个线程,顺序输出5,4,3,2,1,一个线程输出完成之后第二个线程输出
public class TestDemo7 {
//同步方法
//synchronzied修饰成员方法,synchornized获取this对象,this对象代表
//当前对象
public synchronized void test1(){
获取test的monitor lock
int i=5;
while(i >=1){
System.out.println(Thread.currentThread().getName()+"::"+i--);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
将test的monitor lock释放
}
public void test2(){
//同步代码块
synchronized (this){
获取test的monitor lock
int i=5;
while(i >=1){
System.out.println(Thread.currentThread().getName()+"::"+i--);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
test的monitor lock释放
}
}
public static void main(String[] args) {
TestDemo7 test = new TestDemo7();
new Thread("A"){
@Override
public void run() {
//test.test1();
test.test2();
}
}.start();
new Thread("B"){
@Override
public void run() {
//test.test1();
test.test2();
}
}.start();
}
验证可重入锁
public static void main(String[] args) {
Object obj = new Object();
//验证可重入锁
new Thread(){
@Override
public void run() {
synchronized (obj){
System.out.println("entry + 1");
synchronized (obj){
System.out.println("entry + 1 again");
}
}
}
}.start();
synchornized的使用场景
* 1、两个线程同时访问同一个对象的同步方法 安全
* 2、两个线程同时访问两个对象的同步方法 不安全 test1.func1() test2.func1()
* 3、两个线程同时访问(一个或两个)对象的静态同步方法 安全
* 4、两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法 不安全
* 5、两个线程访问同一个对象中的同步方法,同步方法又调用另外一个非同步方法 不安全
* 6、两个线程同时访问同一个对象的不同的同步方法 安全
* 7、两个线程同时访问静态synchronized和非静态synchornized方法 不安全
* 8、同步方法抛出异常,JVM会自动释放锁
- synchornized的练习
* 课堂练习:
* synchornized同步锁实现3个线程循环打印数字,使用线程1,打印1,2,3,4,5.
* 线程2,打印6,7,8,9,10.线程3,打印11,12,13,14,15.依次循环打印,直到
* 打印至60(提示:会使用到wait/notify/notifyAll方法)
- 1、两个线程同时访问同一个对象的同步方法 安全
public synchronized void fun1(){
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
}
public static void main(String[] args) {
TestDemo8 test1 = new TestDemo8();
new Thread("A"){
@Override
public void run() {
test1.fun1(); //unlock
}
}.start();
new Thread("B"){
@Override
public void run() {
test1.fun1(); //lock
}
}.start();
2、两个线程同时访问两个对象的同步方法 不安全 test1.func1() test2.func1()。
一个对象一个锁,因为两个对象 所以换锁就线程不安全
public synchronized void fun1(){
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
}
public static void main(String[] args) {
TestDemo8 test1 = new TestDemo8();
TestDemo8 test2 = new TestDemo8();
new Thread("A"){
@Override
public void run() {
test1.fun1(); //unlock
test2.fun1();
}
}.start();
new Thread("B"){
@Override
public void run() {
test1.fun1(); //lock
test2.fun1();
}
}.start();
- 3、两个线程同时访问(一个或两个)对象的静态同步方法 安全
public synchronized static void fun2() throws Exception {
//类锁 class对象
System.out.println(Thread.currentThread().getName()+":: 静态同步方法func2开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 静态同步方法func2结束");
}
public static void main(String[] args) {
XianCheng_safe test1 = new XianCheng_safe();
new Thread("A"){
@Override
public void run() {
try {
test1.fun2();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
new Thread("B"){
@Override
public void run() {
try {
test1.fun2();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
4、两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法 不安全
public synchronized void fun1(){
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
}
public void fun3(){
System.out.println(Thread.currentThread().getName()+":: 非同步方法func3");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 非同步方法func3");
}
public static void main(String[] args) {
TestDemo8 test1 = new TestDemo8();
TestDemo8 test2 = new TestDemo8();
new Thread("A"){
@Override
public void run() {
test1.fun1(); //unlock
test2.fun3();
}
}.start();
new Thread("B"){
@Override
public void run() {
test1.fun1(); //lock
test2.fun3();
}
}.start();
5、两个线程访问同一个对象中的同步方法,同步方法又调用另外一个非同步方法 不安全。
6、两个线程同时访问同一个对象的不同的同步方法 安全
public synchronized void fun1(){
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
}
public synchronized void fun4(){
System.out.println(Thread.currentThread().getName()+":: 同步方法fun4开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 同步方法func4结束");
}
public static void main(String[] args) {
XianCheng_safe test1 = new XianCheng_safe();
new Thread("A") {
@Override
public void run() {
test1.fun1();
}
}.start();
new Thread("B") {
@Override
public void run() {
test1.fun4();
}
}.start();
}
7、两个线程同时访问静态synchronized和非静态synchornized方法 不安全
静态synchronized 锁的是class对象
非静态synchornized 锁的是你创建的对象。
8、同步方法抛出异常,JVM会自动释放锁
public synchronized void fun1(){
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
}
public static void main(String[] args) {
XianCheng_safe test1 = new XianCheng_safe();
Thread threadc = new Thread("C"){
@Override
public void run() {
test1.fun1();
}
};
threadc.start();
threadc.interrupt();
new Thread("D"){
@Override
public void run() {
test1.fun1();
}
}.start();