首页 > 编程语言 >java -- 线程(二)

java -- 线程(二)

时间:2023-04-14 21:01:03浏览次数:42  
标签:java -- void 线程 println new public out

死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争同步锁而产生的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁。

死锁的案例 : 同步代码块的嵌套
创建锁对象:

public class Lock {
    public static final Lock lockA = new Lock();
    public static final  Lock lockB = new Lock();
}

测试类:

public class DeadLockTest {
    public static void main(String[] args) {
        while(true){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (Lock.lockA){
                        System.out.println("getlockA...");
                        synchronized (Lock.lockB){
                            System.out.println("getlockB...");
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (Lock.lockB){
                        System.out.println("getlockB...");
                        synchronized (Lock.lockA){
                            System.out.println("getlockA...");
                        }
                    }
                }
            }).start();
        }
    }
}

生产者与消费者

创建2个线程,一个线程表示生产者,另一个线程表示消费者

import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Object o = new Object();
        // 生产者
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        if (list.size() > 0) {
                            try {
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        list.add("aaaa");
                        System.out.println(list);
                        // 唤醒消费线程
                        o.notify();
                    }
                }
            }
        }).start();

        // 消费者
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        if (list.size() == 0) {
                            try {
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        list.remove(0);
                        System.out.println(list);
                        o.notify();
                    }
                }
            }
        }).start();

    }
}

线程方法sleep和wait的区别

  • sleep()是Thread类静态方法,不需要对象锁。
  • wait()方法是Object类的方法,被锁对象调用,而且只能出现在同步中。
  • 执行sleep()方法的线程不会释放同步锁。
  • 执行wait()方法的线程要释放同步锁,被唤醒后还需获取锁才能执行。

案例性能问题

wait()方法和notify()方法, 本地方法调用OS的功能,和操作系统交互,JVM找OS,把线程停止. 频繁等待与唤醒,导致JVM和OS交互的次数过多.

Condition接口

java.util.concurrent.locks.Condition 是一个接口类, 因此要使用其实现类创建对象
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用

// Condition常用方法:
public void await() // 线程等待
public void signal() // 唤醒一个等待的线程
public void singalAll() // 唤醒所有等待的线程
// 使用其实现类 ReentrantLock 的 newCondition方法获取 Condition
public Condition newCondition()
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        // 创建Lock
        Lock l = new ReentrantLock();

        // 获取 Condition 对象
        Condition con = l.newCondition();

        new Thread(new Runnable() {
            @Override
            public void run() {
                l.lock();
                System.out.println("开始等待");
                try {
                    con.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    l.unlock();
                }
            }
        }).start();

        Thread.sleep(2000);

        System.out.println("准备唤醒");
        l.lock();
        con.signal();
        l.unlock();
    }
}

Condition接口方法和Object类方法比较

  • Condition可以和任意的Lock组合,也就是实现了线程的分组管理。
    • 一个线程的案例中,可以使用多个Lock锁,每个Lock锁上可以结合Condition对象
    • synchronized同步中做不到线程分组管理
  • Object类wait()和notify()都要和操作系统交互,并通知CPU挂起线程,唤醒线程,效率低。
  • Condition接口方法await()不和操作系统交互,而是让线程释放锁,并存放到线程队列容器中,当被signal()唤醒后,从队列中出来,从新获取锁后在执行。
  • 因此使用Lock和Condition的效率比Object要快很多

生产者和消费者案例改进

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class OverWriteWakeUpWaiting {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Lock l = new ReentrantLock();

        // 对线程进行分组管理
        Condition con1 = l.newCondition(); // 生产线程, 对象监视器
        Condition con2 = l.newCondition(); // 消费线程, 对象监视器

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    l.lock();
                    if (list.size() > 0) {
                        try {
                            con1.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    list.add("abc");
                    System.out.println(list);
                    con2.signal();
                    l.unlock();
                }
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    l.lock();
                    if (list.size() == 0) {
                        try {
                            con2.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    list.remove(0);
                    System.out.println(list);
                    con1.signal();
                    l.unlock();
                }
            }
        }).start();
    }
}

java并发编程的三大特性

原子性

原子性,即一个操作或多个操作,要么全部执行并且在执行的过程中不被打断,要么全部不执行
下面具有原子性的操作有?

x = 1;
// x = 1,是一个单纯的赋值操作,满足原子性。
y=x;
// 实际是两个操作,分别是 读取x变量 ,将x赋值给y,这两个操作分别来看都是原子性的,但是合起来就不是了
x++;
// 实际是三个操作 ,先读取变量 ,在进行+1操作 ,再赋值给x,不满足原子性
x=x+1;
// 同上,不满足原子性

JAVA提供了原子性的技术保障有如下:

1、synchronized (互斥锁)
2、Lock(互斥锁)
3、原子类(CAS)
synchronized 和 Lock 都是通过互斥锁实现,即同一时刻只允许一个线程操作该变量,保障了原子性

原子类AtomicInteger

/*
    java.util.concurrent.atomic.AtomicInteger
    构造方法
        public AtomicInteger()创建具有初始值 0 的新 AtomicInteger。
        public AtomicInteger(int initialValue) 创建具有给定初始值的新 AtomicInteger。
      方法
        int incrementAndGet()  以原子方式将当前值加 1。
        int getAndIncrement()  以原子方式将当前值加 1。

        int decrementAndGet()  以原子方式将当前值减 1。
        int getAndIncrement()  以原子方式将当前值减 1。


        int getAndAdd(int delta)  以原子方式将给定值与当前值相加。
        int addAndGet(int delta)  以原子方式将给定值与当前值相加。
        int get() 获取当前值。
 */
public class Test02 {
    public static void main(String[] args) {
        AtomicInteger  ai = new AtomicInteger(1);

        ai.incrementAndGet(); //++ai    2
        ai.getAndIncrement(); //ai++    3


        System.out.println(ai.get());// 3
        System.out.println(ai.getAndIncrement()); // 3
        System.out.println(ai.get()); // 4
        System.out.println(ai.incrementAndGet()); // 5
        System.out.println(ai.get()); //5     
    }
}

CAS无锁机制

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。当多条线程尝试使用CAS同时更新同一个变量时,只有其中一条线程能更新变量的值,而其他线程都失败,失败的线程并不会被挂起,而是告知这次竞争失败,并可以再次尝试.
CAS的缺点:

CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

  1. 循环时间长开销很大。

    CAS 通常是配合无限循环一起使用的,如果 CAS 失败,会一直进行尝试。如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销。
    
  2. 只能保证一个变量的原子操作。

    当对一个变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个变量操作时,CAS 目前无法直接保证操作的原子性。
    
  3. ABA问题。

    第一条线程获取到V位置的值  假设是 1
    第二条线程获取到V位置的值  也是1
    第一条线程cas成功 将值改为 0
    第一条线程又cas成功 将值改回 1
    这时第二条线程cas 发现值没变 还是1 cas成功   
    实际上当第二条线程cas时 V位置的值已经从 1-0-1
    这就是ABA问题 
    如何解决 每次获取V位置的值时,带上一个版本号.这样就可以避免ABA问题 java中AtomicStampedReference这个类在cas时就是通过版本号来解决的
    

可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程应该能够立即看得到修改的值

public class Test {
    public static  boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1号线程启动....执行while循环");

                long num = 0;
                while(flag){
                    num++;
                }

                System.out.println(num);
            }
        }).start();

        Thread.sleep(2000);

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("2号线程启动....修改flag的值为false,停止循环");
                flag = false;
            }
        }).start();

    }
}

通过如上案例 发现修改flag 的值并没有使循环结束

1.加锁,比如使用synchronized.

JMM关于synchronized的两条规定:
  1)线程解锁前,必须把共享变量的最新值刷新到主内存中
  2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值
public class Test {

    //使用同步方法获取flag的值
    public static synchronized boolean getFlag(){
        return flag;
    }

    public static  boolean flag = true;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1号线程启动....执行while循环");
                long num = 0;
                    /*
                        线程调用getFlag方法时 先获取锁 也就是加锁
                        这时会先清空本地内存中共享副本的值,那么在使用值就需要从
                        主内存中重新获取 ,线程释放锁时,也就是解锁,会把共享变量flag
                        的值重新更新到主内存中
                     */
                    while(getFlag()){
                        num++;
                    }
                System.out.println(num);
            }
        }).start();

        Thread.sleep(2000);

        new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println("2号线程启动....修改flag的值为false,停止循环");
                flag = false;
            }
        }).start();
    }
}

2.使用volatile关键字保证可见性

public class Test {
    public static volatile boolean flag = true;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1号线程启动....执行while循环");
                long num = 0;

                    while(flag){
                        num++;
                    }
                System.out.println(num);
            }
        }).start();
        Thread.sleep(2000);
        new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println("2号线程启动....修改flag的值为false,停止循环");
                flag = false;
            }
        }).start();
    }
}

volatile缓存可见性实现原理

底层实现主要是通过汇编lock前缀指令,会锁住这块区域的缓存,并写回主内存.

1.会将当前处理器缓存的行数据立即写回系统内存

2.这个写回内存的操作导致CPU的缓存该内存地址的数值失效(MESI协议)

volatile只能保证可见性,但是不能保证原子性,如果要保证原子性,请使用锁

有序性

一般来说,程序的执行顺序按照代码的先后顺序执行.但是处理器为了提高程序的效率,可能会对代码的执行顺序进行优化,它不保证程序中各个语句的执行先后顺序一致,但是保证程序的最终结果和代码顺序执行的结果一致.

int a = 10; 	//语句1
int b = 20;     //语句2
int c = 20;     //语句3
c= a + b;  //语句4

CPU可能会对没有依赖关系的语句进行重排,比如 2134,3124 但是不会对有依赖关系的数据进行重排比如 3和4 改为4和3 这样就会对结果造成影响.这种重排对单线程是没有任何影响的,但是如果是多线程就可能会出现问题.

验证CPU是否会进行指令重排:

public class Test {

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 500000; i++) {
            Test.State state = new Test.State();

            ThreadA t1 = new ThreadA(state);
            ThreadB t2 = new ThreadB(state);

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

        }

    }


    static class ThreadA extends Thread{
        private final Test.State  state;
        ThreadA(Test.State state){
            this.state =state;
        }


        public void run(){
            state.a=1;
            state.b=1;
            state.c=1;
            state.d=1;


        }

    }
    static class ThreadB extends Thread{
        private final Test.State  state;
        ThreadB(Test.State state){
            this.state =state;
        }


        public void run(){

            if( state.b== 1 && state.a ==0){
                System.out.println("b= " + state.b);
            }

            if(state.c == 1 &&(state.b==0|| state.a ==0)){
                System.out.println("c = " + state.c);
            }

            if(state.d==1 &&(state.a==0||state.b==0||state.c==0)){
                System.out.println("d " + state.d);
            }
        }

    }

    static  class  State{
        int a = 0;
        int b = 0;
        int c = 0;
        int d = 0;
    }
}
/*
c = 1
说明,CPU进行了重排,让c在b或者a前面进行了赋值.
改变顺序可能导致执行结果不同,因此需要禁止重排序。
*/

使用volatile关键字后 就不会出现刚才的情况

static  class  State{
        volatile int  a = 0;
        volatile int b = 0;
        volatile int c = 0;
        volatile int d = 0;
 }

由此可见:volatile关键字有两个作用1.保证可见性.2禁止重排序

单例设计模式

设计模式 : 不是技术,是以前的人开发人员,为了解决某些问题实现的写代码的经验.
Java的设计模式有23种,分为3个类别,创建型,行为型,功能型
单例代表单个实例,保证一个类的对象永远只有一个!

饿汉式

优点: 简单 多线程下没有任何问题
缺点:

  • 当类加载时 对象就会被直接创建
  • 若不被使用 对象就白创建了
public class danliDemo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Single1.getInstance());
                }
            }).start();
        }
    }
}
class Single1 {
    private static Single1 s = new Single1();

    public Single1() {
    }

    public static Single1 getInstance(){
        return s;
    }

}

懒汉式

优点: 延迟加载 什么时候调用方法 什么时候创建对象
缺点: 多线程时 代码有问题

public class danliDemo2 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Single2.getInstance());
                }
            }).start();
        }
    }
}
class Single2 {
    private static Single2 s;
    public Single2() { }

    public static Single2 getInstance(){

        if (s == null) {
            s = new Single2();
        }
        return s;
    }
}

安全问题

一个线程判断完变量 s=null,还没有执行new对象,被另一个线程抢到CPU资源,同时有2个线程都进行判断变量,对象创建多次

性能问题

第一个线程获取锁,创建对象,返回对象. 第二个线程调用方法的时候,变量s已经有对象了,根本就不需要在进同步,不要在判断空,直接return才是最高效的.
双重的if判断,提高效率 Double Check Lock(DCL)
DCL双检查锁机制单例,效率高,线程安全,多线程操作原子性。

class Single2 {
    private static Single2 s;
    public Single2() { }

    public static Single2 getInstance(){

        if (s == null) {
            synchronized (Single2DCL.class) {
                if (s == null) {
                    s = new Single2DCL();
                }
            }
        }
        return s;
    }
}

面试题

DCL单例是否需要使用volatile关键字?
需要,单例的模式, 不使用volatile关键字,可能线程会拿到一个尚未初始化完成的对象(半初始化)

标签:java,--,void,线程,println,new,public,out
From: https://www.cnblogs.com/paopaoT/p/17319628.html

相关文章

  • 字符串哈希
    算法简介字符串哈希是将字符串映射为数字的算法,它通常用来解决快速判断两个字符串是否相等的问题。时间复杂度\(O(n+m)\)实现原理1.构造原理字符串哈希运用了进制的思想,将字符串变为p进制的数字。如:""可以映射为:\((X_1*P^{n-1}+X_2*P^{n-2}+\cdots+X^{n-1......
  • 内网穿透
    NEO-regeorg隐蔽隧道搭建由python写的一个http代理工具基本使用1.设置一个连接密码python.\neoreg.pygenerate-k连接密码-o指定输出目录例如python.\neoreg.pygenerate-kpasswd2.再将生成出来的木马文件上传到目标服务器上3.连接目标脚本会自动生成一个sock......
  • 494. 目标和
    classSolution{public:intf[25][2010];//体积范围从-1000~1000intfindTargetSumWays(vector<int>&nums,inttarget){intn=nums.size(),offset=1000;//价值总和不超过1000,因此偏移量设置1000即可f[0][0+offset]=1;for(inti=1;i<=n;......
  • kuangbin专题一 简单搜索 翻转(POJ-3279)
    FliptileTimeLimit:2000MS MemoryLimit:65536KDescriptionFarmerJohnknowsthatanintellectuallysatisfiedcowisahappycowwhowillgivemoremilk.HehasarrangedabrainyactivityforcowsinwhichtheymanipulateanM×Ngrid(1≤M≤15;1≤......
  • 兔子产子问题
    有一对兔子,从出生后的第三个月起每个月都生一对兔子。小兔子长到第三个月后每个月又生一对兔子,假设所有兔子都不死,问30个月内每个月的兔子总数有多少?通过分析可知每个月兔子的总数为1,1,2,3,5,8,13......由规律可知,从第三个月开始,本月的兔子数为前俩个月的兔子数之和,这样我们就可......
  • OpenSSL s_client测试子命令
    载第三方的最新的PEM(privacy-enhancedmail)格式的可信证书库wget--no-check-certificatehttps://curl.haxx.se/ca/cacert.pemopenssls_client-CAfilecacert.pem-connectwww.baidu.com:443-msg显示证书链openssls_client-CAfilecacert.pem-connectwww.baid......
  • Nacos笔记(二):Nacos的应用
    Nacos官网:https://nacos.io/zh-cn/index.html。1、注册中心1.1、项目搭建创建新项目,项目结构如下:  父项目下有两个子项目nacos-9001、nacos-9002。1、POM依赖父项目POM文件:<parent><groupId>org.springframework.boot</groupId><artifactId......
  • m基于matlab的图像方块编码仿真,输出编码后PSNR图像质量指标
    1.算法仿真效果matlab2022a仿真结果如下:   2.算法涉及理论知识概要       BTC编码又称方块编码,是一种有效,快速,简单的有损灰度图像数字压缩技术,具有性能高,信道容错力高等特点,在实时图像传输方面具有很高的应用价值,由美国普渡大学的Mitchell和Delphi教授提......
  • 动词过去式、过去分词不规则变化词表
    一般来说,英语中使用过去时、完成时等时态时,动词要变为过去式或过去分词的形式。 那么动词过去式、动词过去分词如何变化呢? 一般情况下,规则动词的过去式、过去分词的构成规则相同。 规则动词变化形式有: ▪直接在动词原型后面+ed构成过去式,如called; ▪以不发音e结尾......
  • 借书方案知多少问题
    一、问题描述:5本书借给A,B,C三名小朋友,每个人只能借一本,问有多少种借书的方案二、设计思路:直接利用三层循环暴力解除完事,条件判断联立两个等式三、程序流程图:  四、代码实现:#include<iostream>代码一:三层循环,效率有点低#include<iostream>using namespace std;......