Java进阶内容(一)--并发编程(1)

线程基础

cpu核心和线程的关系

  • 通常情况下,cpu核心和线程是1:1的关系,在因特尔引入超线程技术之后,比例变成了1:2。

cpu时间片轮转机制(算法–RR调度–上下文切换)

  • 这种机制是一种操作系统内部的算法
  • cpu会让获得时间片的线程进行程序的执行,当时间片的时间到了之后(时间片的长度)线程将会暂停执行,使下一个获取到时间片的线程继续执行
  • 时间片的算法是完全随机的(其中有权重影响),意思就是会有线程多次连续获得时间片
  • 切换时间片也是有消耗的(保存当前线程执行的位置等等),一般时间为5k-2w个时钟周期,开发应该做到少切换,或者有计划切换
  • 当此线程阻塞之后,cpu也会进行时间片的切换
  • 假如一台计算机cpu是八核十六线程,那么cpu最多只能同时执行十六个线程的程序,一个进行(应用程序)可以对应多个线程甚至是超过十六个线程,但是cpu最多只能同时运行16个线程,其他的还是要靠时间片轮转机制来进行任务调度

进程和线程的关系

  • 程序运行进行资源分配的最小单位(一个进程运行时计算机会为它进行分配一部分内存等资源);线程是cpu调度的最小单位
  • 一个进程内部会有多个线程,这多个线程会共享这个进程所分配到的资源,线程自身是不会分到资源的
  • 进程与进程之间是相互独立的

并行和并发

  • 并行:同一时刻下,就是指一个事件有多少个执行器来执行;举例:食堂有八个窗口打饭,就是打饭这个事件由八个窗口同时执行(并行度是8)
  • 并发:相对于并行,增加了一个时间的概念,就是一段时间内,此事件可以支持执行器完成多少个任务;举例:食堂打饭需要30s,那么一分钟内,可以完成16次打饭(并发度是16)
  • 总结:并行是指在某一时刻下处理事件的能力,并发是指在单位时间内处理事件的能力

高并发编程的意义、好处和注意事项

  • 充分利用cpu的性能
  • 提高应用响应的时间
  • 实现模块的模块化和异步化(解耦)
  • 线程会共享进程资源会出现冲突,死锁等;太多线程会造成内存溢出

java本身就是一个多线程的

获取jvm中运行的所有线程

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TaskControlApplication.class})
public class test {
    @Test
    public void testThreadMXBean() {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println(threadInfo.getThreadId() + "-->" + threadInfo.getThreadName());
        }
    }
}
/* 
   打印结果
   5-->Attach Listener
   4-->Signal Dispatcher
   3-->Finalizer
   2-->Reference Handler
   1-->main
*/

启动一个新的线程的三种方法

1.继承类Thread
public class testThread {
    public static void main(String[] args) {
        Thread thread = new ThreadOverride();
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
2.实现接口Runnable
public class testThread {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 打印此线程的线程名称
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
3.实现接口Callable
public class testThread {
    public static void main(String[] args) {
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("i am callable");
                return "result";
            }
        };
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            // 获取返回值一定要在start之后,否则会一直阻塞
            System.out.println(futureTask.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

停止一个线程的方法

  1. 线程正常执行完成
  2. 抛出异常
  3. 线程调用stop(),resume(),suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()容易导致死锁。
  4. java中正确中断线程的方式是调用interrupt()方法;interrupt()方法使线程进入中断状态,因为java中线程是协作式的,此方法只是把线程的中断标志位置为true,线程是否停止还是看线程本身
  5. isInterrupt()方法判断线程是不是处于一个中断状态
  6. static interrupted()方法判断线程是不是处于中断状态,并且在之后把状态改为false

编写一个安全的多线程的类需要注意的地方

  1. 在循环处添加isInterrupt方法判断;

  2. 当抛出java.lang.InterruptedException这个异常的时候,需要在捕捉异常时的catch里面再次调用interrupt方法(因为线程里面抛出这个异常会导致之前置位的中断标志位复位);

  3. 还有一种是在线程里面定义一个私有的boolean的变量和公共改变此boolean值的方法来控制线程内部跳出循环,这种方法和使用isInterrupt的区别是当线程被阻塞时,调用改变boolean值得方法不会立即生效;而调用线程isInterrupt()会立即抛出异常,线程会立刻有反应

    /**
     * java.lang.InterruptedException
     * 当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。有时候,一种方法可能希望测试当前线程是否已被中断,如果已被中断,则立即抛出此异常。
     * @author maolin yuan
     */
    public class TestThread {
    
        private static class NewThread extends Thread{
    
            @Override
            public void run() {
                setName("test");
                // 在循环条件上并上!isInterrupted()条件
                while (!isInterrupted()){
                    System.out.println(getName() + " run flag is " + isInterrupted() );
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // 必须再次调用interrupt()
                        System.out.println(getName() + " catch flag1 is " + isInterrupted());
                        interrupt();
                        System.out.println(getName() + " catch flag2 is " + isInterrupted());
                        e.printStackTrace();
                    }
                }
                System.out.println(getName() + " flag is " + isInterrupted());
            }
        }
    
    
    
        public static void main(String[] args) throws InterruptedException {
            NewThread newThread = new NewThread();
            newThread.start();
            Thread.sleep(410);
            newThread.interrupt();
        }
    }
    

线程的生命周期

线程的生命周期

  • sleep()方法和yield()方法的区别:cpu再下一次选择线程分配时间片时,sleep线程不会被选中,而yield线程会有可能性被选中

  • run()方法和start()方法的区别:线程类直接调用run()方法不会启动新的线程,直接是main线程调用这个方法;调用start()方法,会先让操作系统启动一个新的线程,然后这个新线程执行run()方法

  • 线程的优先级为1~10,缺省/默认为5,优先级高cpu会提高此线程获得时间片的几率,但不是一定会获得,优先级会根据操作系统的不同而有所差异,有的操作系统甚至会忽略线程的优先级

  • 守护线程是和主线程同生共死的,守护线程停止时,里面的finally语句不能保证一定会被执行

    /**
     * @author maolin yuan
     */
    public class TestThread2 {
        private static class NewThread extends Thread{
    
            @Override
            public void run() {
                setName("test");
                for (int i = 0; i < 3; i++) {
                    // 获取当前线程的名称
                    System.out.println(Thread.currentThread().getName() + " execute run ");
                }
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            NewThread newThread = new NewThread();
            newThread.setPriority(5); // 设置线程优先级
            newThread.setDaemon(true); // 设置此线程为守护线程
            newThread.run(); // main execute run
            newThread.start(); // test execute run 
            Thread.sleep(10);
        }
    }
    // 打印结果
    // main execute run 
    // main execute run 
    // main execute run 
    // test execute run 
    // test execute run 
    // test execute run 
    

线程之间的共享

  1. 使用synchronized(内置锁)来控制线程之间资源的共享

    • synchronized放到普通方法上,此时锁是一个对象this,别的线程调用此方法时保证是同一个对象时,即可互锁
    • synchronized放到静态方法上,此时锁是一个class对象,class对象在Java虚拟机中有且只有一个,因此不管是反射调用还是直接调用都可以互锁
    • 既能确保可见性,又能确保操作的原子性
    /**
     * @author maolin yuan
     */
    public class TestThread3 {
    
        // 测试对象锁
        private static class NewThread1 extends Thread{
    
            private TestThread3 tt;
    
            private String string;
    
            public NewThread1 (TestThread3 tt,String string){
                this.tt = tt;
                this.string = string;
            }
    
            @Override
            public void run() {
                while (!isInterrupted()){
                    tt.instanceLock(string);
                }
            }
        }
    
        // 测试类锁
        private static class NewThread2 extends Thread{
    
            private Class<?> clazz = TestThread3.class;
    
            private String string;
    
            public NewThread2 (String string) {
                this.string = string;
            }
    
            @Override
            public void run() {
                while (!isInterrupted()){
                    try {
                        clazz.getDeclaredMethod("classLock",String.class).invoke(null,string);
                    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        // 测试类锁
        private static class NewThread3 extends Thread{
    
            private String string;
    
            public NewThread3 (String string) {
                this.string = string;
            }
    
            @Override
            public void run() {
                while (!isInterrupted()){
                    TestThread3.classLock(string);
                }
            }
        }
    
        public synchronized void instanceLock(String string){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(string);
        }
    
    
        public synchronized static void classLock(String string) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(string);
        }
    
    
        public static void main(String[] args) {
            // 使用同一个对象 锁生效
            TestThread3 testThread3 = new TestThread3();
            NewThread1 newThread1 = new NewThread1(testThread3,"instance lock 1");
            NewThread1 newThread2 = new NewThread1(testThread3,"instance lock 2");
            newThread1.start();
            newThread2.start();
    
            // 使用不同对象 锁不生效
            NewThread1 newThread3 = new NewThread1(new TestThread3(),"instance lock 1");
            NewThread1 newThread4 = new NewThread1(new TestThread3(),"instance lock 2");
            newThread3.start();
            newThread4.start();
    
            // 使用同一个类对象 锁生效
            NewThread3 newThread5 = new NewThread3("class lock 1");
            NewThread3 newThread6 = new NewThread3("class lock 2");
            newThread5.start();
            newThread6.start();
    
            // 使用同一个类对象 锁生效 (反射调用)
            NewThread2 newThread7 = new NewThread2("class lock 1");
            NewThread3 newThread8 = new NewThread3("class lock 2");
            newThread7.start();
            newThread8.start();
    
        }
    }
    
    
  2. 使用volatile关键字修饰变量(最轻量的同步机制)

    • 只能确保变量的可见性,不能确保对这个变量操作的原子性
    • 使用场景最好是一个线程对这个变量写值,多个线程读取这个变量的值
    • 使用volatile关键字修饰的变量在主存中会被定义,每个线程使用这个变量时会从主存中读取这个值,操作完成之后会把这个值写回主存,当有多个线程操作这个值时,其原子性就不会被保证了
    /**
     * 验证volatile的非原子性
     * @author maolin yuan
     */
    public class TestThread4 {
    
        private volatile static int i = 0;
    
        private static class NewThread1 extends Thread{
    
            @Override
            public void run() {
                while (!isInterrupted()){
                    System.out.println(i ++);
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            NewThread1 newThread1 = new NewThread1();
            NewThread1 newThread2 = new NewThread1();
            NewThread1 newThread3 = new NewThread1();
            NewThread1 newThread4 = new NewThread1();
            NewThread1 newThread5 = new NewThread1();
            newThread1.start();
            newThread2.start();
            newThread3.start();
            newThread4.start();
            newThread5.start();
    
        }
    }
    // 打印结果
    // 0
    // 3
    // 2
    // 1
    // 4
    // 5
    // 7
    // 8
    // 5
    // 6
    // 9
    
  3. 使用ThreadLocal包装变量使各个线程都有此变量独立的副本,互不干扰

    • 其本质是一个map,等价于Map<Thread,Class>
    • 当使用ThreadLocal包装一个大对象时,线程较多会占用较多的内存
    /**
     * 使用ThreadLocal保证线程的安全性
     *
     * @author maolin yuan
     */
    public class TestThread5 {
    
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };
    
        private static class NewThread1 extends Thread {
            @Override
            public void run() {
                while (!isInterrupted()){
                    threadLocal.set(threadLocal.get() + 1);
                    System.out.println(threadLocal.get());
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            NewThread1 newThread1 = new NewThread1();
            NewThread1 newThread2 = new NewThread1();
            NewThread1 newThread3 = new NewThread1();
    
    
            newThread1.start();
            newThread2.start();
            newThread3.start();
    
    
        }
    }
    // 打印内容
    // 1
    // 1
    // 1
    // 2
    // 2
    // 2
    // 3
    // 3
    // 3
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值