1.多线程三大特性?
原子性、可见性、有序性
(1)什么是原子性?
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分.
(2) 什么是可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
这是产生线程不安全最主要的问题,假如我们有A,B两个线程共享同一变量,变量放在方法区,也就是主内存,JVM体系结构中堆和方法区是线程共享的,A,B线程存放着主内存变量的副本,即各自对自己所在线程的主内存变量的副本读写,假如变量是10,A对变量进行减1,这时值为9,但是没来及刷新到主内存,B保存的是变量的副本10,B这时也进行减1,这时值也为9,导致数据不一致。
具体原理可以参考:java内存模型
(3)什么是有序性
程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。 如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。
2.Synchronized关键字的两种用法?
说明:Java提供了一种内置的锁机制来支持原子性
每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁
内置锁使用synchronized关键字实现,synchronized关键字有两种用法:
1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象
2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活
同步代码块synchronized
就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
就是同步代码块
synchronized(对象)//这个对象可以为任意对象
{
需要被同步的代码
}
if (flag) {
while (trainCount > 0) {
synchronized (oj) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (trainCount > 0) {
System.out
.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
trainCount--;
}
}
}
}
synchronized修饰方法
1.在方法上修饰synchronized 称为同步方法,使用的是this锁
2.方法上加上static关键字,使用synchronized 关键字修饰 称为静态同步方法,使用锁是当前类的字节码文件,可以用 getClass方法获取,也可以用当前 类名.class 表示。
证明同步方法使用的是this锁,this指样例中的SaleTickets对象
/**
* @author Administrator
*/
class SaleTickets implements Runnable {
private int trainTickets = 100;
private Object obj = new Object();
private boolean flag = true;
@Override
public void run() {
if (flag) {
while (trainTickets > 0) {
synchronized (obj) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (trainTickets > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainTickets + 1) + "张票");
trainTickets--;
}
}
}
} else {
while (trainTickets > 0) {
sale();
}
}
}
public synchronized void sale() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (trainTickets > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainTickets + 1) + "张票");
trainTickets--;
}
}
public static class Test {
public static void main(String[] args) throws Exception {
SaleTickets saleTickets = new SaleTickets();
Thread thread1 = new Thread(saleTickets, "1号窗口");
Thread thread2 = new Thread(saleTickets, "2号窗口");
thread1.start();
Thread.sleep(40);
saleTickets.flag = false;
thread2.start();
}
}
}
此时synchronized (obj),锁为任意对象;
此时synchronized (this),锁为this;
总结: 同步代码块为obj锁时发生线程不安全,为this锁时线程安全,证明同步方法为this锁
Volatile关键字用法?
可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。
在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存。
Volatile 保证了线程间共享变量的及时可见性,但不能保证原子性