Java并发基础

Java并发基础

多线程的出现是要解决什么问题的? 本质什么

CPU、内存、I/O设备的速度是有极大差异的,为了合理利用CPU的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:
CPU 增加了缓存,以均衡与内存的速度差异;// 导致可见性问题。
操作系统增加了进程、线程,以分时复用CPU,进而均衡CPU与I/O设备的速度差异;// 导致原子性问题。
编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致有序性问题。

Java是怎么解决并发问题的

理解的第一个维度:核心知识点
JMM本质上可以理解为,Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括:
volatile、synchronized和final三个关键字
Happens-Before规则
理解的第二个维度:可见性,有序性,原子性
原子性
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。 请分析以下哪些操作是原子性操作:

x = 10;        //语句1: 直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中
y = x;         //语句2: 包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
x++;           //语句3: x++包括3个操作:读取x的值,进行加1操作,写入新的值。
x = x + 1;     //语句4: 同语句3

上面4个语句只有语句1的操作具备原子性。
也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。

从上面可以看出,Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronizedLock来实现。由于synchronizedLock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

可见性
Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

另外,通过synchronizedLock也能够保证可见性,synchronizedLock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

有序性
在Java里面,可以通过volatile关键字来保证一定的“有序性”。

另外可以通过synchronizedLock来保证有序性,很显然,synchronizedLock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。当然JMM是通过Happens-Before 规则来保证有序性的。

线程安全有哪些实现思路

互斥同步
synchronized 和 ReentrantLock
非阻塞同步
互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。
1、CAS:随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是: 比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有3个操作数,分别是内存地址V、旧的预期值A和新值B。当执行操作时,只有当V的值等于A,才将V的值更新为B。
2、AtomicInteger:J.U.C包里面的整数原子类AtomicInteger,其中的compareAndSet()和getAndIncrement()等方法都使用了Unsafe类的CAS操作。
无同步方案
要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。
1、栈封闭:多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
2、线程本地存储(Thread Local Storage):如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。

如何理解并发和并行的区别

并发是指一个处理器同时处理多个任务
在这里插入图片描述
并行是指多个处理器或者是多核的处理器同时处理多个不同的任务
在这里插入图片描述

线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?

在这里插入图片描述
新建(New):创建后尚未启动。
可运行(Runnable):可能正在运行,也可能正在等待 CPU 时间片。包含了操作系统线程状态中的 Running 和 Ready。
阻塞(Blocking):等待获取一个排它锁,如果其他线程释放了锁就会结束此状态。
无限期等待(Waiting):等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
在这里插入图片描述
限期等待(Timed Waiting):无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
在这里插入图片描述
死亡(Terminated):可以是线程结束任务之后自己结束,或者产生了异常而结束。

通常线程有哪几种使用方式

有三种使用线程的方法:
实现 Runnable 接口

class Dog implements Runnable { //通过实现Runnable接口来实现
    int count =  0;
    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("你好,兮动人-" + (++count) + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);// 休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}
public class ThreadTest01 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog.start(); //这里不能调用start方法
        //创建了Thread对象,把dog对象(实现了Runnable),放入了Thread
        Thread thread = new Thread(dog);
        thread.start();
    }
}

通过Thread来执行Runnable任务,底层使用了【代理模式】:

class Animal {}
class Tiger extends Animal implements Runnable {
    @Override
    public void run() {
        System.out.println("老虎...");
    }
}
//线程代理类,模拟了一个极简的Thread类
class ThreadProxy implements Runnable { //可以把Proxy类当做 Thread
    private Runnable target = null; // 属性类型是Runnable
    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定(运行类型是Tiger)
        }
    }
    public ThreadProxy(Runnable target) {
        this.target = target;
    }
    public void start() {
        start0();//这个方法是真正实现多线程的方法
    }
    public void start0() {
        run();
    }
}
public class ThreadTest02 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

实现 Callable 接口

// 创建一个实现Callable的实现类, 可以通过设置泛型,指定call方法返回的类型
class CallableThread implements Callable<Integer> {
    // 实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                sum += i;
            }
        }
        return sum;
    }
}
public class TestCallable {
    public static void main(String[] args) {
        // 创建Callable接口实现类的对象
        CallableThread callableThread = new CallableThread();
        // 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask<Integer> futureTask = new FutureTask<>(callableThread);
        // 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        // 获取Callable中call方法的返回值(因为会等待线程结束后再获取,所以可以当作闭关锁使用)
        // get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
        try {
            Integer sum = futureTask.get();
            System.out.println("计算sum = " + sum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

继承 Thread 类

public class ThreadByExtend extends  Thread{
    public static volatile int  count = 0;
    //Thread真正执行的代码段
    public void run() {
        for(int i=0;i<50;i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "sold "+(i+1)+" tickets total Sold:" + (++count));
        }
    }
    public static void main(String[] argv) throws InterruptedException{
        ThreadByExtend myThread1 = new ThreadByExtend();
        ThreadByExtend myThread2 = new ThreadByExtend();
        myThread1.start();
        myThread2.start();
        myThread1.join();
        myThread2.join();
    }
}

线程池实现

public class ThreadPoolCallable implements Callable {
    private static int count = 0;
    public String call() {
        for(int i=0;i<50;i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "sold "+(i+1)+" tickets total Sold:" + (++count));
        }
        return "sale out";
    }
    public static void main(String[] argv) throws InterruptedException, ExecutionException {
        ExecutorService ex = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 4; i++) {
            ex.submit(new ThreadPoolCallable());
        }
        ex.shutdown();
    }
}

public class ThreadPoolRunnable implements Runnable{
    private static volatile AtomicInteger count = new AtomicInteger(0);
    public void run() {
        for(int i=0;i<50;i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " sold "+(i+1)+" tickets total Sold:" + count.incrementAndGet());
        }
    }
    public static void main(String[] argv) throws InterruptedException, ExecutionException {
        ExecutorService ex = Executors.newFixedThreadPool(4);

        for (int i = 0; i < 4; i++) {
            ex.submit(new ThreadPoolRunnable());
        }
        ex.shutdown();
    }
}

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

为什么必须调用Thread start方法,不能直接调用Thread的run方法创建

Thead的run方法相当于主线程的main方法,创建线程必须由系统通过start方法来调用。
start方法是一种native方法,即本地方法,start方法通过系统调用注册新的线程,安排好上下文, 才会执行run方法。
如果直接执行run方法,则仍然是在主线程中,将run作为一个普通的方法调用,返回后仍然是在主线程。
每一个Thread对象的start方法只能调用一次。
在这里插入图片描述

覆写Runnable接口相比继承Thread类的好处

1、覆写Runnable接口实现多线程可以避免单继承局限,因为我们的线程工作对象可能是别的类的子类,那么强制必须是Thread类的子类就会带来很大的麻烦。
2、通过runnable方式,多个线程可以共享同一个runnable对象,大家可以对比一下,我们在继承Thread的方式中,线程之间因为是不同的对象,那么共享数据就只能用静态变量的方式才能使得多个线程之间可见。而在runnable方式的实现中,因为不同的thread的构造都是同一个runnable对象,所以runnable中的数据是共享的,不需要静态。

基础线程机制有哪些

Executor:Executor管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。
主要有三种Executor:
CachedThreadPool:一个任务创建一个线程
FixedThreadPool:所有任务只能使用固定大小的线程;
SingleThreadExecutor:相当于大小为1的FixedThreadPool;
Daemon
守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main()属于非守护线程。使用setDaemon()方法将一个线程设置为守护线程。
sleep()
Thread.sleep(millisec)方法会休眠当前正在执行的线程,millisec单位为毫秒。
sleep() 可能会抛出InterruptedException,因为异常不能跨线程传播回main()中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。
yield()
对静态方法Thread.yield()的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

线程的互斥同步方式有哪些? 如何比较和选择

Java提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的synchronized,而另一个是JDK实现的ReentrantLock。
1.锁的实现:
synchronized是JVM实现的,而ReentrantLock是JDK实现的
2.性能:
新版本Java对synchronized进行了很多优化,例如自旋锁等,synchronized与ReentrantLock大致相同
3.等待可中断:
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。ReentrantLock可中断,而synchronized不行
4.公平锁:
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。synchronized中的锁是非公平的,ReentrantLock默认情况下也是非公平的,但是也可以是公平的
5.锁绑定多个条件:
一个ReentrantLock可以同时绑定多个Condition对象

线程之间有哪些协作方式

join():在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

public class JoinExample {
    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }
    private class B extends Thread {
        private A a;
        B(A a) {
            this.a = a;
        }
        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }
    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }
    public static void main(String[] args) {
    	JoinExample example = new JoinExample();
    	example.test();
	}
}

虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
wait()、notify()、notifyAll()
1、wait()的作用是使当前执行wait()方法的线程等待,在wait()所在的代码行处暂停执行,并释放锁,直到接到通知或中断。
2、notify()方法用来通知那些可能等待该锁的其他线程,如果有多个线程等待,则按照执行wait()方法的顺序发出一次性通知(一次只能通知一个!),使得等待排在第一顺序的线程获得锁。
需要说明的是,执行notify方法后,当前线程并不会立即释放锁,要等到程序执行完,即退出synchronized同步区域后。
3、它们都属于Object的一部分,而不属于Thread只能用在同步方法或者同步控制块中使用,否则会在运行时抛出IllegalMonitorStateExeception。
4、wait/notify在调用前一定要获得相同的锁,如果在调用前没有获得锁,程序会抛出异常,也就调用不了wait/notify;另外,如果获得的不是同一把锁,notify不起作用
代码示例:

public class Service {
	Object lock = new Object();
	public void waitMethod() {
		synchronized(lock) {
			try {
				System.out.println(Thread.currentThread().getName()+"执行了wait方法,释放了锁");
				lock.wait();
				System.out.println(Thread.currentThread().getName()+"被唤醒了");
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	public void notifyMethod() {
		synchronized(lock) {
			try {
				System.out.println(Thread.currentThread().getName()+"执行了notify方法");
				lock.notify();;
				System.out.println(Thread.currentThread().getName()+"继续执行notify后的代码,完事后才释放锁");
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
public class ThreadA extends Thread {
	private Service service;
	public ThreadA(Service service) {
		this.service = service;
	}
	@Override
	public void run() {
		super.run();
		service.waitMethod();
	}

}
public class ThreadB extends Thread {
	private Service service;
	public ThreadB(Service service) {
		this.service = service;
	}
	@Override
	public void run() {
		super.run();
		service.notifyMethod();
	}
}
public class Test {
	public static void main(String[] args) throws Exception {
		Service service = new Service();
		ThreadA threadA= new ThreadA(service);
		ThreadB threadB= new ThreadB(service);
		threadA.setName("A");
		threadB.setName("B");
		threadA.start();
		threadB.start();		
	}
}
// 执行结果:
A执行了wait方法,释放了锁。
B执行了notify方法。
B继续执行notify后的代码,完事后才释放锁。
A被唤醒了。
// 1、线程ThreadA启动后,获得lock锁,成功执行了wait方法,在wait方法所在行暂停,并且释放了锁
// 2、线程ThreadB获得ThreadA释放的锁,成功执行了notify方法并且通知到了ThreadA,告诉它做好苏醒准备,但ThreadB并没有马上释放锁,它继续执行,直到退出synchronzied 代码块
// 3、ThreadB退出synchronized 代码块后释放锁,ThreadA获得锁,从暂停的地方往后执行,直到程序执行完毕

wait()和sleep()的区别
wait()是Object的方法,而sleep()是Thread的静态方法。
wait()会释放锁,sleep()不会。
await()、signal()、signalAll()
java.util.concurrent类库中提供了Condition类来实现线程之间的协调,可以在Condition上调用await()方法使线程等待,其它线程调用signal()或signalAll()方法唤醒等待的线程。相比于wait()这种等待方式,await()可以指定等待的条件,因此更加灵活。
代码示例:

public class SweepAndMopDispacherNew {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private boolean isSweeping = false;
    public void sweeped() {
        lock.lock();
        try {
            isSweeping = true;
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
    public synchronized void Mopped() {
        lock.lock();
        try {
            isSweeping = false;
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
    public void waitForSweeped() throws InterruptedException {
        lock.lock();
        try {
            while (isSweeping == false) {
                condition.await();
            }
        } finally {
            lock.unlock();
        }
    }
    public void waitForMopped() throws InterruptedException {
        lock.lock();
        try {
            while (isSweeping == true) {
                condition.await();
            }
        } finally {
            lock.unlock();
        }
    }
}
public class SweepRunner implements Runnable {
    private SweepAndMopDispacherNew dispacher;
    public SweepRunner(SweepAndMopDispacherNew dispacher) {
        this.dispacher = dispacher;
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println(Thread.currentThread().getName() + "扫地..");
                TimeUnit.MILLISECONDS.sleep(800);
                dispacher.sweeped();
                dispacher.waitForMopped();
            }
        } catch (InterruptedException e) {
            System.out.println("exit by interrupted");
        } finally {
            System.out.println("SweepRunner exit;");
        }
    }
}
public class MopRunner implements Runnable {
    private SweepAndMopDispacherNew dispacher;
    public MopRunner(SweepAndMopDispacherNew dispacher) {
        this.dispacher = dispacher;
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println(Thread.currentThread().getName() + "拖地..");
                TimeUnit.MILLISECONDS.sleep(800);
                dispacher.Mopped();
                dispacher.waitForSweeped();
            }
        } catch (InterruptedException e) {
            System.out.println("exit by interrupted");
        } finally {
            System.out.println("MopRunner exit;");
        }
    }
}
public class TestMain {
    public static void main(String[] args) {
        SweepAndMopDispacherNew dispacher = new SweepAndMopDispacherNew();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(new SweepRunner(dispacher));
        executorService.submit(new MopRunner(dispacher));
        try {
            TimeUnit.SECONDS.sleep(5);
            executorService.shutdownNow();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Synchronized由什么样的缺陷?Java Lock是怎么弥补这些缺陷的?

synchronized的缺陷
效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时。
不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活。
无法知道是否成功获得锁:相对而言,Lock可以拿到状态。
Lock类这里不做过多解释,主要看里面的4个方法:
lock():加锁。
unlock():解锁。
tryLock():尝试获取锁,返回一个boolean值。
tryLock(long,TimeUtil):尝试获取锁,可以设置超时。

Synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合解决了这个问题。
Synchronized在多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。
ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。

Synchronized在使用时有何注意事项?

锁对象不能为空,因为锁的信息都保存在对象头里。
作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错。
避免死锁
在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错。

Synchronized修饰的方法在抛出异常时,会释放锁吗?

会。

多个线程等待同一个Synchronized锁的时候,JVM如何选择下一个获取锁的线程?

非公平锁,即抢占式。

synchronized是公平锁吗?

synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象。

volatile关键字的作用是什么

1、防重排序
我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。
代码示例:

public class Singleton {
    private static volatile Singleton singleton;
    // 构造函数私有,禁止外部实例化
    private Singleton() {};
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

现在我们分析一下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:
1、分配内存空间。2、初始化对象。3、将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
1、分配内存空间。2、将内存空间的地址赋值给对应的引用。3、初始化对象。
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。

2、实现可见性
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。volatile关键字能有效的解决这个问题,代码示例:

public class TestVolatile {
    private static boolean stop = false;
    public static void main(String[] args) {
        // Thread-A
        new Thread("Thread A") {
            @Override
            public void run() {
                while (!stop) { }
                System.out.println(Thread.currentThread() + " stopped");
            }
        }.start();
        // Thread-main
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println(Thread.currentThread() + " after 1 seconds");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stop = true;
    }
}
输出:
Thread[main,5,main] after 1 seconds
// Thread A一直在loop, 因为Thread A由于可见性原因看不到Thread Main已经修改stop的值
// 如果通过在stop变量前面加上volatile关键字则会真正stop
输出:
Thread[main,5,main] after 1 seconds
Thread[Thread A,5,main] stopped

32位机器上共享的long和double变量的为什么要用volatile?

因为long和double两种数据类型的操作可分为高32位和低32位两部分,因此普通的long或double类型读/写可能不是原子的。因此,鼓励大家将共享的long和double变量设置为volatile类型,这样能保证任何情况下对long和double的单次读/写操作都具有原子性。
目前各种平台下的商用虚拟机都选择把64位数据的读写操作作为原子操作来对待,因此我们在编写代码时一般不把long和double变量专门声明为volatile多数情况下也是不会错的

如何理解private所修饰的方法是隐式的final?

类中所有private方法都隐式地指定为final的,由于无法取用private方法,所以也就不能覆盖它。

public class Base {
    private void test() {
    }
}
public class Son extends Base{
    public void test() {
    }
    public static void main(String[] args) {
        Son son = new Son();
        Base father = son;
        //father.test();
    }
}
// Base和Son都有方法test(),但是这并不是一种覆盖,因为private所修饰的方法是隐式的final,也就是无法被继承,所以更不用说是覆盖了,在Son中的test()方法不过是属于Son的新成员罢了,Son进行向上转型得到father,但是father.test()是不可执行的,因为Base中的test方法是private的,无法被访问到。

说说final类型的类如何拓展?

比如String是final类型,我们想写个MyString复用所有String中方法,同时增加一个新的toMyString()的方法,应该如何做?
使用外观模式

/**
* @pdai
*/
class MyString{
    private String innerString;
    // ...init & other methods
    // 支持老的方法
    public int length(){
        return innerString.length(); // 通过innerString调用老的方法
    }
    // 添加新方法
    public String toMyString(){
        //...
    }
}

final方法可以被重载吗?

父类的final方法是不能够被子类重写的,那么final方法可以被重载吗? 答案是可以的,下面代码是正确的。

public class FinalExampleParent {
    public final void test() {
    }
    public final void test(String str) {
    }
}

父类的final方法能不能够被子类重写?

不可以。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值