1. 线程的并发(Concurrent)
synchronized (同步关键字)
语法
synchronized(对象) {
要作为原子操作代码}
用synchronized 解决并发问题:
static int i = 0;
static Object obj = new Object(); // 房间,能容纳一个人
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> { // 甲
for (int j = 0; j < 5000; j++) {
synchronized (obj) { // 甲会锁住这个房间
i++;
} // 甲从房间出来解开了锁
}
});
Thread t2 = new Thread(() -> { // 乙
for (int j = 0; j < 5000; j++) {
synchronized (obj) { // 乙 在门外等待
i--;
} //
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器。要检查有没有owner,如果没有,此线程成为owner; 但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。
成为owner可以理解为获得了对象的锁
在竞争的时候,是非公平的
synchronized必须是进入同一个对象的monitor 才有上述的效果
2. volatile 易变的
可以用来修饰成员变量和静态成员变量,他可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值。
它保证的是变量在多个线程之间的可见性, 不能保证原子性
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
Thread.sleep(1000);
run = false;
}
一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止
synchronized 语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能会受到影响。
//要解决上述线程无法停止的问题只需给static boolean run =true;前面加入volatile修饰符,或使用Synchronized.
volatile static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
Thread.sleep(1000);
run = false;
}
volatile static boolean run = true;
static Object obj=new Object();
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
synchronized (obj){
while(run){
// ....
}
}
});
t.start();
Thread.sleep(1000);
run = false;
}
3. synchronized的另外两种写法
public synchronized void test() {
}
等价于
public void test() {
synchronized(this) {
}
}
方法参数前加synchronized,相当于修饰的是这个方法,谁调用的这个方法则锁住的就是调用这个方法的对象.
class Test{
public synchronized static void test() {
}
}
等价于
public static void test() {
synchronized(Test.class) {
}
}
方法参数前加synchronized static,相当于修饰的是这个类,谁调用的这个方法则给他提供一个这个类的监视器,就是使用这个类的锁
例题:情况5:
class Number{
public synchronized static void a() {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(1);
}
public synchronized void b() {
System.out.println(2);
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}//2
//1
情况6:
class Number{
public synchronized static void a() {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(1);
}
public synchronized static void b() {
System.out.println(2);
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}//1
//2
情况7:
class Number{
public synchronized static void a() {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(1);
}
public synchronized void b() {
System.out.println(2);
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}//2
//1
情况8:
class Number{
public synchronized static void a() {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(1);
}
public synchronized static void b() {
System.out.println(2);
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}//1
//2
4. 线程死锁
a 线程 获得 A 对象 锁
接下来获取B对象的锁
b 线程获得 B对象 锁
接下来获取A对象的锁
例:
Object A = new Object();
Object B = new Object();
Thread a = new Thread(()->{
synchronized (A) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("操作...");
}
}
});
Thread b = new Thread(()->{
synchronized (B) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
System.out.println("操作...");
}
}
});
a.start();
b.start();
检测死锁可以使用 jconsole工具
5. 线程的状态
NEW(新建) 线程刚被创建,但是还没有调用 start方法
RUNNABLE(可运行) 当调用了start() 方法之后
BLOCKED(阻塞) 当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态
WAITING(等待) 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态
TIMED_WAITING 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会
TERMINATED (终止)当线程代码运行结束
五种状态:
NEW(新建), RUNNABLE(可运行) , RUNNING(正在运行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(终止)
6. 如何让两个线程以固定的顺序运行
static Object obj = new Object();
static boolean t2runed = false;// t2是否执行过
// 打印 2, 1
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (obj) {
while(!t2runed) { // 如果t2没有执行过
try {
obj.wait(); // 线程t1 先等一会
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(1);
});
Thread t2 = new Thread(()->{
System.out.println(2);
synchronized (obj) {
t2runed = true;
obj.notify();
}
});
t1.start();
t2.start();
}