一、线程的同步问题
当多个线程访问同一个资源对象时会引发同步问题。
示例:
public class MyThread implements Runnable {
private int n=10;//n为共享资源
@Override
public void run() {
for (int i=0;i<100;i++){
if (n>0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票" + n--);
}
}
}
}
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread=new MyThread();
Thread a=new Thread(myThread,"A");
Thread b=new Thread(myThread,"B");
Thread c=new Thread(myThread,"C");
a.start();
b.start();
c.start();
}
}
输出:
为什么会出现负数的情况呢?原因如下:A线程进人if判断语句,当前票数=1>0,进入休眠的时候,B线程也进入if判断语句,当前票数也=1,B线程也进入休眠,同样,C线程也如此。然后,A线程休眠完了,打印票数=1并将票数减1变为0。此时,B线程也休眠完,打印票数=0减1变为-1,那么当C打印票数的时候就是-1。
你可能会疑惑,这样说来,应该是先打印B卖票0,再打印C卖票-1啊。这个又要跟print()括号里有关了,其实是先执行n--,再执行打印的。n--和打印字符串并不是两步并不是原子性操作。所以当B线程执行n--后,可能还没打印n,C线程也执行了n--,并且先打印了n。
所以据此我猜想,print那行代码,是分三步执行的,先将括号里的合成一个字符串str,再n--,最后打印str,只有最后的打印是原子性操作。可参考 留意i--与System.out.println()的异常
二、线程的同步处理——synchronized
1.一个线程访问一个对象中的synchronized(this)时,其他的线程就不能访问该对象的所有synchronized(this)代码。
示例:
输出:
无论test.fun1()还是test.fun2(),输出结果都一样,都是一个线程执行完再执行另一线程。因为A线程和main线程都试图访问同一个对象test的synchronized(this)代码块,所以其中必有一个线程需要等待另一个线程访问完才能访问。
如果我们把代码改一下:
输出:
我们仅仅把test.fun1()改成test1.fun1(),结果就不一样。因为test和test1是两个不同的对象,而synchronized(this)锁住的是当前对象,有且仅有一个对象,所以A线程和main线程可以并发执行。
2.当一个线程访问一个对象的synchronized(this)代码块时,其他线程仍能访问该对象的非synchronized(this)代码块。
仍然是上面的例子,我们稍稍给动一下代码,将fun2()改成非同步代码块:
结果:
可以看出,虽然两个线程访问的都是同一个对象test,但是非synchronized()代码块是不受阻挠的,两个线程仍能并发执行。
3.指定要给某对象加锁。这里只是把synchhronized(this)括号中的this换成一个指定对象,同步原理相同。当几个线程同时访问synchronized(x对象)代码块时(此处不仅仅是指访问同一处的,而是指所有的,只要加了synchronized(x对象),其实与this是相同的),若这几个线程所拥有的x对象为同一个,那么只能有一个线程执行,其它线程受到阻塞。
示例:
import java.util.concurrent.ExecutionException;
class Test implements Runnable {
private String str;
private Account account;
public Test(String str, Account account) {
this.str = str;
this.account = account;
}
@Override
public String toString() {
return this.str;
}
@Override
public void run() {
fun1();
}
public void fun1(){
synchronized (account){
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" fun1 "+this+" "+i);
}
}
}
public void fun2(){
synchronized (account){
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" fun2 "+this+" "+i);
}
}
}
}
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Account account1=new Account();
Account account2=new Account();
Thread.currentThread().setName("Thread-M");
Test test1=new Test("account1",account1);
Test test2=new Test("account2",account2);
new Thread(test1,"Thread-A").start();
new Thread(test2,"Thread-B").start();
test1.fun2();
test2.fun2();
}
}
结果:
Thread-M fun2 account1 0
Thread-B fun1 account2 0
Thread-M fun2 account1 1
Thread-B fun1 account2 1
Thread-M fun2 account1 2
Thread-B fun1 account2 2
Thread-M fun2 account1 3
Thread-B fun1 account2 3
Thread-M fun2 account1 4
Thread-B fun1 account2 4
Thread-M fun2 account1 5
Thread-B fun1 account2 5
Thread-M fun2 account1 6
Thread-B fun1 account2 6
Thread-M fun2 account1 7
Thread-B fun1 account2 7
Thread-M fun2 account1 8
Thread-B fun1 account2 8
Thread-B fun1 account2 9
Thread-M fun2 account1 9
Thread-A fun1 account1 0
Thread-M fun2 account2 0
Thread-M fun2 account2 1
Thread-A fun1 account1 1
Thread-M fun2 account2 2
Thread-A fun1 account1 2
Thread-A fun1 account1 3
Thread-M fun2 account2 3
Thread-M fun2 account2 4
Thread-A fun1 account1 4
Thread-A fun1 account1 5
Thread-M fun2 account2 5
Thread-M fun2 account2 6
Thread-A fun1 account1 6
Thread-A fun1 account1 7
Thread-M fun2 account2 7
Thread-M fun2 account2 8
Thread-A fun1 account1 8
Thread-A fun1 account1 9
Thread-M fun2 account2 9
可以看出,首先是Thread-M的test1.fun2()和Thread-B的test2.fun1()并发执行。等到Thread-M执行完test1.fun2()才执行Thread-A的test1.fun1();等到Thread-B执行完test2.fun1()才执行Thread-M的test2.fun2()。如果我们把代码改成test和test1拥有同一个account对象,那么所有的线程都不能并发执行:
Test test1=new Test("account1",account1);
Test test2=new Test("account2",account1);
结果:
Thread-M fun2 account1 0
Thread-M fun2 account1 1
Thread-M fun2 account1 2
Thread-M fun2 account1 3
Thread-M fun2 account1 4
Thread-M fun2 account1 5
Thread-M fun2 account1 6
Thread-M fun2 account1 7
Thread-M fun2 account1 8
Thread-M fun2 account1 9
Thread-M fun2 account2 0
Thread-M fun2 account2 1
Thread-M fun2 account2 2
Thread-M fun2 account2 3
Thread-M fun2 account2 4
Thread-M fun2 account2 5
Thread-M fun2 account2 6
Thread-M fun2 account2 7
Thread-M fun2 account2 8
Thread-M fun2 account2 9
Thread-B fun1 account2 0
Thread-B fun1 account2 1
Thread-B fun1 account2 2
Thread-B fun1 account2 3
Thread-B fun1 account2 4
Thread-B fun1 account2 5
Thread-B fun1 account2 6
Thread-B fun1 account2 7
Thread-B fun1 account2 8
Thread-B fun1 account2 9
Thread-A fun1 account1 0
Thread-A fun1 account1 1
Thread-A fun1 account1 2
Thread-A fun1 account1 3
Thread-A fun1 account1 4
Thread-A fun1 account1 5
Thread-A fun1 account1 6
Thread-A fun1 account1 7
Thread-A fun1 account1 8
Thread-A fun1 account1 9
综上得知,synchronized()锁住的其实是括号里的对象,不同线程执行时,我们要看括号里的对象是否为同一个,如果是,则要等待解锁。
4.修饰一个普通方法,与2效果相同。
5.修饰一个静态方法=修饰一个类。锁住的是该类的所有对象。
参考http://www.importnew.com/21866.html
三、死锁
当A线程在B个线程执行完,B线程又在等A线程执行完的时候,就会出现死锁。
示例:
结果:
当main线程执行到b.say(a),打印完第一行后,进入休眠2秒的状态,此时,子线程Thread-0执行a.say(b),也打印完第二行。主线程b.say(a)中的a.get()需要等子线程a.say(b)方法执行完,而a.say(b)中的b.get()又要等主线程的b.say(a)执行完,所以就形成死锁。
假如把休眠2秒去掉:
结果:
此时,main线程执行a.get()时,子线程还未执行run()方法,那么就不会死锁。