举个例子说一下多线程的安全问题
public class ThreadDemo implements Runnable {
private int num = 0;
@Override
public void run() {
num = num+1;
//-->thread-0 -->thread-1
System.out.println(num);
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
两个线程一起启动,我们想要的结果是thread-0进入run方法,打印输出1,紧接着thread-1进入run方法,打印输出2,但是可能发生的情况是:thread-0进入run方法,对num加1后,num变成1,此时线程thread-0停在此处,执行权让给thread-1,再对num运算,变成2,然后两个线程打印出来的都是2了。
解决方法:采用同步(synchronized)
格式为
synchronized (对象) {
//要同步的代码
}
public class ThreadDemo implements Runnable {
private int num = 0;
Object o = new Object();
@Override
public void run() {
synchronized (o) {
num = num+1;
//-->thread-0 -->thread-1
System.out.println(num);
}
}
}
参数里面的对象可以是任意一个对象,随便什么对象都行,但是如果要实现同步,这个对象必须是唯一的,这里的对象我们称为锁,当thread-0进入run方法后,会持有锁o,此时如果thread-1进来run方法,会被挡在被synchronized包裹的代码块外,不允许进入,当thread-0执行完代码块里的代码后,会释放锁,此时thread-1就可以拿到锁,执行代码块,两个线程相敬如宾,恩恩爱爱,和睦相处,生活中的例子就是“火车上的厕所”,火车上有一个厕所,小明先进去,把门锁上,在里面边玩手机边蹲坑,小刚捂着肚子过来了,发现里面有人,只好在外面等着,等小明弄完,把门打开,小刚就可以进去了。
这时我们把上面的代码改一下
public class ThreadDemo implements Runnable {
private int num = 0;
@Override
public void run() {
Object o = new Object();
synchronized (o) {
num = num+1;
//-->thread-0 -->thread-1
System.out.println(num);
}
}
}
object挪到了run方法里面,由成员变量变成了局部变量,那么两个线程运行run方法的时候,就会new两次对象,相当于两把锁,此时就不能解决安全问题了,我拿着我的锁进代码块,你拿着你的锁进代码块,我们互不限制,就好像火车上有两个厕所,小明上了一个,小刚跑去上另外一个。所以实现同步安全的条件是:多个线程,一把锁。
同步代码块还有一种写法是
public class ThreadDemo implements Runnable {
private int num = 0;
@Override
public void run() {
synchronized(this){
num = num+1;
//-->thread-0 -->thread-1
System.out.println(num);
}
}
}
就是在synchronized的参数里面加this,那么哪个ThreadDemo对象调用了这个方法,这个this就是指哪个对象,只要保证这个对象是唯一的,就可以实现同步安全。
上面讲的是同步代码块,接下来是同步方法,同步方法其实就是在方法修饰符里加一个synchronized关键字,这里说一下静态同步方法
public class ThreadDemo implements Runnable {
private static int num = 0;
@Override
public void run() {
add();
}
public static synchronized void add(){
synchronized(ThreadDemo.class){
num = num+1;
//-->thread-0 -->thread-1
System.out.println(num);
}
}
}
如果这个方法是静态方法,那就不能用synchronize(this)了,因为静态方法在类初始化的时候就会加载,但是synchronize(this)在对象调用的时候才加载,所以参数不能用this,我们一般用当前类的字节码文件对象:xxx.class,每一个类的字节码文件对象都是唯一的,所以用它当锁是可以实现同步的。
接下来说说死锁
首先创建两个对象,也就是两把锁
class MyLock
{
static Object locka=new Object();
static Object lockb=new Object();
}
然后进行同步的嵌套
class ThreadDemo implements Runnable
{
private boolean flag;
ThreadDemo(boolean flag){
flag = flag;
} public void run() { if(flag) { synchronized(MyLock.locka) { //-->thread-0 synchronized(MyLock.lockb) { } } } else { synchronized(MyLock.lockb) { //-->thread-1 synchronized(MyLock.locka) { } } } } }
接下来是测试类
public class DeadLockTest
{
public static void main(String[] args)
{
Thread t1=new Thread(new Test(true));
Thread t2=new Thread(new Test(false));
t1.start();
t2.start();
}
}
thread-0拿到a锁,进入代码块,thread-1拿到b锁,进入代码块,此时thread-0不释放a锁,等着对方释放b锁,对方不释放b锁,等着thread-0释放a锁,相持不下,形成死锁。