synchronized关键字:执行方法时,锁定当前对象,保证整个操作过程不能被打断
public class TestSync implements Runnable {
Timer timer = new Timer();
public static void main(String[] args) {
TestSync test = new TestSync();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("T1");
t2.setName("T2");
t1.start();
t2.start();
}
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer {
public static int num = 0;
public /*synchronized*/ void add(String name) {
//使用方法1
synchronized (this) {
//使用关键字,执行方法过程中锁定当前对象(互斥锁)
//使用方法2
num++;
try {
Thread.sleep(1);
//第一个线程睡眠时,第二个线程已经将num更改为2
//导致结果都为"你是第2个使用Timer的线程"
//原因:第一个线程执行过程中被第二个线程打断(sleep是为了放大效果)
} catch (InterruptedException ex) {
}
System.out.println(name + ": 你是第" + num + "个使用Timer的线程");
}
}
}
死锁:
原理:某个任务在等待另一个任务,而后者又在等待第一个任务释放锁,导致一个任务之间互相等待的连续循环
举例:A需要先访问x,再访问y;B需要先访问y,再访问x。当两个线程执行时,A对x加了锁,B对y加了锁,然后A想继续执行则需要访问y(即需要等待B线程执行完毕),而B想要继续执行则需要访问x(即需要等待A线程执行完毕)。导致A、B两个线程进入互相等待的状态,即阻塞状态,都无法继续执行。
解决方法之一:加粗锁的粒度(对大对象加锁,而不是对对象里的小对象加锁)
除非实现系统级的程序(例如数据库产品),一般不会遇到死锁
面试例题:
非加锁方法仍然可以被访问
public class TT implements Runnable{
int b = 100;
public synchronized void m1() throws Exception {
//m1方法被锁定,但m2并未被锁定,所以可以访问
b = 1000;
Thread.sleep(5000);
System.out.println("b = " + b);
}
public void m2() {
System.out.println(b);
}
public void run() {
try {
m1();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
TT tt = new TT();
Thread t = new Thread(tt);
t.start();
Thread.sleep(1000);
//m1被锁定,m2仍然可以访问
tt.m2();
/*输出结果为:
1000
b = 1000
*/
}
}
生产者消费者问题:基础版本
主要涉及到wait和notify方法的使用
public class ProducerConsumer {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(c).start();
}
}
class Product {
int id;
Product(int id) {
this.id = id;
}
public String toString() {
return "Product: " + id;
}
}
class SyncStack {
int index = 0;
Product[] arrProduct = new Product[6];
public synchronized void push(Product p) {
while(index == arrProduct.length) {
//使用while,而不是if,确保出现exception后,仍然会检查index
try {
this.wait();
//访问当前对象的线程等待
//wait过程 中,锁不在归当前线程所有
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
this.notifyAll();
//叫醒在该对象上等待的线程
arrProduct[index] = p;
index++;
}
public synchronized Product pop() {
while(index == 0) {
try {
this.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
this.notifyAll();
index--;
return arrProduct[index];
}
}
class Producer implements Runnable {
SyncStack ss = null;
Producer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i = 0; i < 20; i++) {
Product p = new Product(i);
ss.push(p);
System.out.println("生产: " + p + " 剩余: " + ss.index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
SyncStack ss = null;
Consumer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i = 0; i < 20; i++) {
Product p = ss.pop();
System.out.println("消费: " + p + " 剩余: " + ss.index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
wait和sleep区别:
wait:调用时必须锁定该对象,别的线程可以访问锁定对象
sleep:别的线程不可以访问锁定对象