1.线程安全
何时出现:当同一资源被多个线程同时进行读写操作,数据遭到破坏,使得运行结果不准确。
竞争状态:多个线程以一种冲突的方式读写公共资源,这种状态称为竞争状态,如果一个类的对象在多线程程序中没有导致竞争状态,则可称这个类是线程安全的。
解决办法:同步。避免多个线程同时进入程序的某一部分(称这部分为临界区),即一次只允许一个线程进入这个临界区。
2.线程同步之synchronized
这是一个关键字。用于给临界区上锁。当第一个线程进入临界区后,给临界区锁上,其它线程进不来,只能等候第一个线程执行完成并释放锁,然后下一个获得锁,进入临界区并锁住临界区,其它线程只能等待它执行完成后并释放锁,以此类推。更形象的例子,我们上洗手间,进入,关门,其他人在外等候,等待前一个人处理完成打开门出来了,第二个人进入,试想如果大家都一起去,结果自己想象,此时线程安全问题就出现了。
synchronized之同步,同步方法和同步语句块。
//同步方法示例
public synchronized void method() {
}
//同步语句块,不仅可通过this对当前对象加锁,还可以对任何对象加锁。
synchronized(expr){ //expr为对象引用,当前对象通常用this。
}
//任何的同步方法都可以转换为同步语句块如,如上面的同步方法可改为:
public void method() {
synchronized(this){
}
}
3.synchronized同步分析
代码1:不同步
public class SyncTest {
//方法1
public void method1() {
System.out.println("进入method1方法");
try {
System.out.println("执行method1方法");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method1方法执行完了");
}
//方法2
public void method2() {
System.out.println("进入method2方法");
try {
System.out.println("执行method2方法");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2方法执行完了");
}
public static void main(String[] args) {
SyncTest test = new SyncTest();
//线程1
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
//执行结果,方法1和方法2同时执行
/*
进入method1方法
执行method1方法
进入method2方法
执行method2方法
method2方法执行完了//此处也可能是method1先执行完
method1方法执行完了
*/
代码2:synchronized同步普通方法
public class SyncTest {
//方法1,注意synchronized的位置,在返回值前
public synchronized void method1() {
System.out.println("进入method1方法");
try {
System.out.println("执行method1方法");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method1方法执行完了");
}
//方法2
public synchronized void method2() {
System.out.println("进入method2方法");
try {
System.out.println("执行method2方法");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2方法执行完了");
}
public static void main(String[] args) {
SyncTest test = new SyncTest();
//线程1
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
//执行结果,方法1执行完成后才会执行方法2,产生了同步效果
/*
进入method1方法
执行method1方法
method1方法执行完了
进入method2方法
执行method2方法
method2方法执行完了
*/
代码3:synchronized同步代码块
public class SyncTest {
//方法1
public void method1() {
System.out.println("进入method1方法");
try {
synchronized(this) {
Thread.sleep(1000);
System.out.println("执行method1方法");
}
} catch (Exception e) {
}
System.out.println("method1方法执行完了");
}
//方法2
public void method2() {
System.out.println("进入method2方法");
try {
synchronized(this) {
Thread.sleep(1000);
System.out.println("执行method2方法");
}
} catch (Exception e) {
}
System.out.println("method2方法执行完了");
}
public static void main(String[] args) {
SyncTest test = new SyncTest();
//线程1
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
//执行结果,方法1中同步块中的执行了才会执行方法2中的同步块。
代码4:synchronized同步静态方法
public class SyncTest {
//静态方法1
public synchronized static void method1() {
System.out.println("进入method1方法");
try {
System.out.println("执行method1方法");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method1方法执行完了");
}
//静态方法2
public static synchronized void method2() {
System.out.println("进入method2方法");
try {
System.out.println("执行method2方法");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2方法执行完了");
}
public static void main(String[] args) {
SyncTest test1 = new SyncTest();//对象1
SyncTest test2 = new SyncTest();//对象2
//线程1
new Thread(new Runnable() {
@Override
public void run() {
test1.method1();
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
test2.method2();
}
}).start();
}
}
//执行结果,虽然对象test1和对象test2是不同的对象,但是它们同属一个类。方法1执行完成后才会执行方法2.
/*
进入method1方法
执行method1方法
method1方法执行完了
进入method2方法
执行method2方法
method2方法执行完了
*/
4.Lock类
4.1 Lock与synchronized的区别:
- a: Lock是一个类,在java 1.5版本中提供;synchronized是一个关键字。因此Lock的功能比synchronized强大。
- b: 在线程执行完以后,synchronized会自动释放线程所持有的监视器锁;而Lock则需要手动去释放锁,因此在使用Lock时线程块常放在try-finally块中,并在finally中调用unlock方法释放锁。
- c: synchronized强制锁的获取与释放都必须在同一个块结构中;而Lock则允许在不同的结构中获取和释放锁,也可以以任何顺序获取和释放多个锁。关于此条的理解阅读后面相关例子。
4.2 Lock接口常用方法
void lock() 获取锁
Lock lock = ....; lock.lock();//获取锁
void unlock() 释放锁 ,在finally块中调用,以确保锁会被释放。
Lock lock = ...; lock.lock(); try{ //处理任务 } finally { lock.unlock(); }
boolean tryLock() 如果当前锁为空闲状态,则获取锁并返回true,否则返回false。
Lock lock = ...; if(lock.tryLock()){ try{ //处理任务 } finally { lock.unlock(); } } else { //如果拿不到锁,则另行处理其他业务 }
- boolean tryLock(long time, TimeUnit unit)如果在指定时间time内没有获取获取,则返回false,TimeUnit是一个枚举,用来表示时间粒度。如时,分,秒,毫秒…。
Lock lock = ...;
if(lock.tryLock(100L,TimeUnit.MILLISECONDS)){//表示在100毫秒后超时。
try{
//处理任务
} finally {
lock.unlock();
}
} else {
//如果拿不到锁,则另行处理其他业务
}
- void lockInterruptibly()
- Condition newCondition()