实战Java高并发程序设计笔记第二章

2019-12-11  本文已影响0人  MisterDo

Java并行程序基础

2.1 线程必知

进程:

线程:

为什么要使用线程?

线程的生命周期

初始线程:线程的基本操作

新建线程

方法一:继承Thread

Thread t = new Thread(()->System.out.println("Hello,this is created by method1"));//需重载run()方法
t.start();

不要用run()启动线程,注意调用start()和run方法的区别

方法二:实现Runnable接口

实现原理:Thread.run()方法直接调用Runnable实现类对象的run()方法,静态代理

public class CreateThread implements Runnable{
  @Override
  public void run(){
    System.out.println("Hello,this is created by method2");
  }
  public static void main(String[] args){
    Thread t = new Thread(new   CreateThread());
    t.start();
   }
}

方法三:使用Callable和future创建带返回值的线程

创建并启动有返回值的线程的步骤如下:

(1)创建。创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
(2)封装。使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
(3)启动。使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
(4)获取返回值。调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

public class myThread3 implements Callable<String> {

    @Override
    public String call() throws Exception {
        return "hello";
    }

    public static void main(String[] args) {
        FutureTask<String> futureTask = new FutureTask<>(new myThread3());
        Thread t3 = new Thread(futureTask);
        t3.start();
        try {
            System.out.println(futureTask.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


终止线程

为什么呢?结束线程时,会直接终止线程,会引起数据的一致性问题。可以参考TCP的四次挥手机制中,服务端收到客户端的FIN后,并不会立即关闭自己的服务,而是先发送ACK,将自己这边还未发送的数据继续发送,然后再发送FIN
使用stop()而引起的数据不一致的问题:

public class StopThreadUnsafe{
    public static User u = new User();
    public static class User{
        private int id;
        private String name;
        public User(){
            id=0;
            name="0";
        }
        //省略get,set和toString方法
    }
    public static class ChangeObjectThread extends Thread{
        @Override
        public void run(){
            whie(true){
                synchronized(u){
                    int v = (int)(System.currentTimeMills()/1000);
                    u.setId(v);
                    try{
                        Thread.sleep(100);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    u.setName(v);
                    Thread.yield();
                }
            }   
        }
    }

    public static class ReadObjectThread extends Thread{
        @Override
        public void run(){
            whie(true){
                synchronized(u){
                    if(u.getId()!=Integer.parseInt(u.getName())){
                        System.out.println(u.toString());
                    }
                    Thread.yield();
                }
            }   
        }
    }
    public static void main(String[] args){
        new ReadObjectThread().start();
        while(true){
            Thread t = new ChangeObjectThread();
            t.start();
            Thread.sleep(150);
            t.stop();
        }
    }
}

如何正确停止线程呢?利用volatile修饰的标志位

public static class ChangeObjectThread extends Thread{
        volatile boolean stopme = false;
        public void stopMe(){
            stopme = true;
        }

        @Override
        public void run(){
            whie(true){
                if(stopme){
                    System.out.println("exit by stop me");
                    break;
                }

                synchronized(u){
                    int v = (int)(System.currentTimeMills()/1000);
                    u.setId(v);
                    try{
                        Thread.sleep(100);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    u.setName(v);
                    Thread.yield();
                }
            }   
        }
    }


线程中断

public void Thread.interrupt()//中断线程
public boolean Thread.isInterrupt()//判断是否被中断
public static boolean Thread.interrupted()//判断是否被中断,并清除当前中断状态
public static void main(String[] args){
        Thread t1 = new Thread();
        @Override
        public void run(){
            while(true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("Interrupted!");
                    break;
                }
            }

            try{
                Thread.sleep(2000);
            }catch (InterruptedException e){
                System.out.println("Interrupted when sleep");
                Thread.currentThread.interrupt();
            }
            Thread.yield();
        }
        t1.start();
        Thread.sleep(2000);//让当前线程休眠若干时间,抛出一个InterruptedException中断异常
        t1.interrupt();
    }

注意:Thread.sleep()方法由于中断而抛出异常,在捕获异常后,该中断标志位会被清除,因此要再次设置中断标志位,以让下一次循环的开始检测到中断

等待(wait)和通知(notify)

public final void wait() throws InterruptedException
public final native void notify() 

wait()和notify()是什么:wait()必须与synchronized搭配使用,当一个对象调用wait()方法,则这个对象所处的线程就会进入object对象的等待队列,在这个等待队列中,可能有多个线程都在等待同一个object对象,当object.notify()调用后,就会从等待队列中(完全)随机选择一个线程将其唤醒
wait()和notify()的工作流程


public class NotifyTest {
    public final  static Object object=new Object();
    public  static class T1 extends  Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(System.currentTimeMillis()+":T1 start");
                System.out.println(System.currentTimeMillis()+": T1 wait for object");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis()+": T1 end");
            }
        }
    }

    public static class T2 extends Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(System.currentTimeMillis()+":T2 start notify object");
                object.notify();
                System.out.println(System.currentTimeMillis()+":T2 end");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1=new T1();
        Thread t2=new T2();
        t2.start();
        t1.start();
    }
}

Object,.wait()和Thread.sleep()都能让线程等待若干时间,wait()方法需要被唤醒,在唤醒后hi释放目标对象的锁,而sleep()不会释放任何资源

挂起(suspend)和继续执行(resume)

如何实现一个可靠的suspend操作呢?利用wait()和notify()

notifyAll():唤醒等待队列中的所有线程,让他们竞争锁

等待线程结束(join)和谦让(yield)

join

yield

  public static void yield();

使当前线程让出CPU后继续争夺资源

volatile与Java内存模型(JMM)

volatile

分门别类的管理:线程组

驻守后台:守护线程(Daemon)

先干重要的事:线程优先级

线程安全的概念与synchronized

当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类时线程安全的

作用:实现线程间的同步
工作:对同步的代码加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全性

指定加锁对象:
直接作用于实例方法:
直接作用域静态方法:
synchronized实现线程安全的i++,保证复合操作的原子性

注意:使用Runnable让两个线程关注一个同一个对象锁
错误示范:

使用synchronized的第三种方法进行修正

程序中的幽灵:隐蔽的错误

出现异常的错误至少可以发现,没有异常的错误例如数据溢出就很难排查

无提示的错误案例

并发下的ArrayList

例子:

可能出现的三种情况

并发下诡异的HashMap

HashMap的源码解析
Jdk 8以前为什么 HashMap在多线程下的put操作容易导致链表成环

初学者常见问题:错误的加锁

public class BadLockOnInteger implements Runnable{
    public static Integer i=0;
    static BadLockOnInteger instance = new BadLockOnInteger();
    @Override
    public void run(){
        for(int j=0;j<10000000;j++){
            synchronized(i){
                i++;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t1 = new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}

为什么最终的结果不是20000000?

在Java中Integer属于不变对象,每次对Integer对象进行加法操作时,实际上是新创建了一个Integer对象来表示加完后的结果,因此i++的本质是创建了一个新的Integer对象,并将其引用赋给i,所以在多个线程间,并不一定能够看到同一个对象,代码中两个线程每次加锁可能都在了不同的对象实例上
如何修正?

synchronized(instance)
上一篇 下一篇

猜你喜欢

热点阅读