小白必备java知识点之java高并发一

目前主要看的是 马士兵的,这里还有对应的代码:https://gitee.com/jcon/thread/tree/master/src/thread
下一篇博客的地址:https://blog.csdn.net/qq_41115379/article/details/115496657
按照Java核心技术 先知道一下
1.什么是线程
和进程区分看来,不能独享资源,但是执行cpu的最小单位
2.线程的执行
调用Thread.start方法,会创建一个执行run方法的新线程
3.基本的线程同步
就是调用synchronized 是一种内部锁,如果上了锁之后,可以把方法保护起来,调用对应的方法就需要获得内部的对象锁

先了解一些 synchronized 
public class T {
    /**
     * 对synchronized 关键字的书写
     */
    private int count=20;
    public void m(){
        synchronized (this){//除了 this 还可以直接 Object o=new Object() 然后用0来充当,就是有点没必要
            //要注意 这里是有个中括号的 也就是所谓的包在了锁里面
            //然后再里面执行一些操作
            count--;
            System.out.println("count = " + count);
        }
    }
}

从这里可以知道 synchronized是锁了一个对象,而不是一个方法或者代码
简便写法

/**
 * 更简单的写法 如果这个代码是 调用了this  也就是开始就锁上了 结束才解锁的话可以进行简写
 *静态的话 static 是没有new一个对象的 也就没有this 所以,要把这个修改为 synchronized(T.class)
 */
public synchronized void m1(){
    count--;
    System.out.println("count = " + count);
}

这里还有个实际的应用

public class T implements Runnable{
    private int count=10;

    @Override
    public /*synchronized*/  void run() {
        //实现这个run方法
        count--;
        //Thread.currentThread().getName() 就是调用当前thread(线程)对象的名字 process是进程
        System.out.println(Thread.currentThread().getName()+"count= "+count);

    }

    public static void main(String[] args) {
        //产生五个线程来调用
        T t=new T();
        for (int i=0;i<5;i++){
            //新创建一个线程 然后直接启动
            new  Thread(t,"Thread " +i).start();
        }
    }
}

脏读的现象

import java.util.concurrent.TimeUnit;

/**
 * 对写方法进行加锁 而不对读方法加锁
 * 这样可能会产生脏读
 */
public class T {
    String name;
    double balance;

    /**
     * 因为这个方法被锁住了 但还是有可能会被非锁定方法所访问
     * @param name
     * @param balance
     */
    //这里对写进行了上锁
    public synchronized void set(String name, double balance){
        this.name = name;
      /*  try {
            //让进程sleep一下
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        //这一段try 是放大了 name和balance之间的时间差
        this.balance = balance;
    }

    //对get方法也进行了上锁
    //这段代码会存在问题 在this.name 和this.balance 之间可能会被getBalance所访问
    //所以 如果在中间就被访问了 就有可能使得balance为默认值0 (因为还没到 this.balance=balance)
    //这就是 脏读现象
    public /*synchronized*/ double getBalance(String name){
        return this.balance;
    }

    public static void main(String[] args) {
        T demo08 = new T();
        //新建一个线程调用写的方法
        new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //  前面这个try的意思 用来等待1秒钟用的 这里的sleep也是调用了Thread的sleep
        //然后调用 获取这个钱
        System.out.println(demo08.getBalance("zhangsan"));

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //这里的try 也就是让时间暂停两秒 再拿zhangsan的钱
        System.out.println(demo08.getBalance("zhangsan"));
    }
}

可重入现象,也就是获得锁之后,可以再获得一遍

import java.util.concurrent.TimeUnit;

/**
 * 对写方法进行加锁 而不对读方法加锁
 * 这样可能会产生脏读
 */
public class T {
    synchronized void test1(){
        System.out.println("test1 start.........");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //这边在锁里面了
        test2();
    }
    //这边也再上了一次锁
    synchronized void test2(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test2 start.......");
    }

    public static void main(String[] args) {
        T demo09 = new T();
        demo09.test1();
    }
}

线程在执行过程中,出现异常的话锁就会被释放

import java.util.concurrent.TimeUnit;

/**
 * 程序在执行过程中,如果出现异常,默认情况锁会被释放
 * 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
 * 比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适
 * 在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据
 * 因此要非常小心的处理同步业务逻辑中的异常
 * @author Jcon
 * 这里如果进行 try catch 的话 就可以实现不抛出锁 而一直执行ti操作
 * 不进行 try catch的话 就会停止 从而进行t2操作
 */
public class T {

    int count = 0;
    synchronized void test(){
        System.out.println(Thread.currentThread().getName() + " start......");
        while (true) {
            count ++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
          /*try {*/
                if (count == 5) {
                    int i = 1 / 0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
                }
         /* }catch (Exception e){
                e.printStackTrace();
            }*/
        }
    }

    public static void main(String[] args) {
        T demo11 = new T();

        Runnable r = new Runnable() {

            @Override
            public void run() {
                demo11.test();
            }
        };

        new Thread(r, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r, "t2").start();
    }
}

volatile 这个关键字让一个变量在多个线程之间可见 体现可见性
这里还有些话说,因为直接运行这个代码,发现没有体现出volatile的可见性
网上说的是:

因为计算机的速度实在是太快了,你仅用两个线程几乎不可能看到被volatile修饰的变量和不被volatile修饰的变量有什么不一样的表现。

  1. 保证被volatile修饰的变量对所有其他的线程的可见性
  2. 使用volatile修饰的变量禁止指令重排优化。
import java.util.concurrent.TimeUnit;

/**
 * volatile 关键字,是一个变量在多个线程间可见
 * @author Jcon
 *
 */
public class T {

    //啊这 可是我没有volatile也是可以直接调用成功的呀
    /*volatile*/ boolean running = true;

     void test(){
        System.out.println("test start.......");
        while (running) {
            //但是出现了这个就可以 那就是说明 他之前很有可能都没有到这个running这一步 true了之后就直接变成了false
         try {
            TimeUnit.SECONDS.sleep(10);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }

        }
        System.out.println("test end........");
    }

    public static void main(String[] args) {
        T demo12 = new T();
        //T这个类 new一个线程 然后调用test方法
        new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //暂停两秒之后  把他设置为false 让test暂停
        //原本 如果没有volatile关键字的话 直接这样设置是没有用的
        demo12.running = false;
    }
}

一个关于volatile和synchronize有什么区别的问题:

得背网上的答案模板,但是大致可以理解为
volatile保证可见性,而synchronize既保证可见性,又保证原子性,但是由此synchronize的效率要比volatile低不少。所以在只需要保证可见性的时候就用volatile
这也是能理解的

/**
 * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,
 * 也就是说volatile不能替代synchronize
 * 运行下面的程序,并分析结果
 * @author Jcon
 *
 */

/**
 * 正常来说 这个运行结果会是十万 但是确实每次都是不同的数值 主要是因为 count这个值 很有可能被其中任意一个count所取走
 * volatile只保证了可见性 但是不能保证原子性 会导致写的时候冲突
 */
public class T {
    //这个是可见的
    volatile int count = 0;
    //正常是加很多
    public void test(){
        for (int i = 0; i < 10000; i++) {
            count ++;
        }
    }

    public static void main(String[] args) {
        T demo13 = new T();

        List<Thread> threads = new ArrayList<Thread>();
        //创建了10个线程
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(demo13::test, "thread-" + i));
        }
        //每一个都启动
        threads.forEach((o)->o.start()); //JDK1.8新特性

        //然后这里等待线程结束
        threads.forEach((o)->{ //JDK1.8新特性
            try {
                o.join(); //等线程执行完毕之后才执行主线程main
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        //打印count值
        System.out.println(demo13.count);
    }
}

原子类AtomXXX类:
同时可以证明 AtomXXX类的多个方法不构成原子性

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 解决同样的问题的更高效的方法,使用AtomXXX类
 * AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性
 * @author Jcon
 *
 */
public class T {
    //int count = 0;
    //就是原子性操作的Integer类型 可以保证不能让多个线程或者多个方法同时连续调用
    AtomicInteger count = new AtomicInteger(0);

    public /*synchronized*/ void test(){
        for (int i = 0; i < 10000; i++) {
            //count ++;
            //效率比synchronize高很多,是系统底层的 相当于就是count++
            //这里有个很细节的地方  原子性只体现在 方法的调用内部 而不能在两个方法之间 在两个方法之间 依然会存在线程的竟然与
            //比如我再对count进行别的操作 这样线程就会冲入 也就是不能保证多个方法连续调用是原子性
      /*      if(count.get()<1000) {*/
                count.incrementAndGet(); //count++
          /*  }*/
        }
    }
//主函数还是同样的操作
    public static void main(String[] args) {
        T demo15 = new T();

        List<Thread> threads = new ArrayList<Thread>();

        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(demo15::test, "thread-" + i));
        }

        threads.forEach((o)->o.start()); //JDK1.8新特性

        threads.forEach((o)->{ //JDK1.8新特性
            try {
                o.join(); //等线程执行完毕之后才执行主线程main
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        System.out.println(demo15.count);
    }
}

原子类AtomXXX类体现可见性:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * volatile 关键字,是一个变量在多个线程间可见
 * @author Jcon
 *
 */
public class T {

    //啊这 可是我没有volatile也是可以直接调用成功的呀

   /* volatile*/  AtomicBoolean running =new AtomicBoolean(true);

    void test(){
        System.out.println("test start.......");
       while (running.get()) {
            //但是出现了这个就可以 那就是说明 他之前很有可能都没有到这个running这一步 true了之后就直接变成了false
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println("test end........");
    }

    public static void main(String[] args) {
        T demo12 = new T();
        //T这个类 new一个线程 然后调用test方法
        new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //暂停两秒之后  把他设置为false 让test暂停
        //原本 如果没有volatile关键字的话 直接这样设置是没有用的
        demo12.running.set(false);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值