线程同步(线程安全处理Synchronized)
线程同步的两种方式:
1、同步代码块
2、同步方法
同步代码块
同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
package com.oracle.demo01;
/*格式:
* synchronized(任意对象){
* 线程要操作的共享数据
* }
* 任意对象:同步对象、同步锁、对象监视器
* 同步怎么能保证安全性?
* 没有锁的线程不能执行,只能等待
*
* 线程获得CPU资源以后想要执行同步代码块的内容,它先去看一下同步锁有没有
* 如果有,那么你的线程获得锁,然后进入同步代码块执行代码,虽然在同步代码块中,线程休眠了
* 在此线程休眠时,其他线程获得资源想要执行同步代码块,它先去看一看有没有锁,一看,没有锁
* 它就被阻挡在代码块之后,只有等
* 休眠的线程睡完了,起来继续执行代码块中的内容,全部执行完代码块中的内容,就释放锁
* 然后其他线程获得锁,就可以继续执行了
*
* 线程安全了,那么执行速度就会变慢了
* */
public class Ticket implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + (ticket--) + "张票");
}
}
}
}
}
package com.oracle.demo01;
public class Test {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
同步方法
同步方法:在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的锁对象是 this
静态同步方法: 在方法声明上加上static synchronized
public static synchronized void method(){
可能会产生线程安全问题的代码
}
package com.oracle.demo02;
/*
*同步方法中有锁么?
*有锁,同步方法中的对象锁,就是本类对象引用 this
*StringBuffer:之所以安全,就是因为里面有同步方法,只要有同步,就安全,就慢
*StringBuilder:不安全,就是因为没有同步,就快
*
*如果你的同步方法时静态的,还有锁么?还是this么?
*有锁,不是this,是本类自己,也就是ticket.class 本类类名.class
* */
public class Ticket implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
//synchronized (this)
synchronized (obj) {
method();
}
}
}
//同步代码块,代码更简洁
public synchronized void method() {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + (ticket--) + "张票");
}
}
}
package com.oracle.demo01;
public class Test {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
常用方法:
package com.oracle.demo03;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* JDK5以后出现Lock接口的可以代替同步关键字
* 功能是一样的,但是更灵活
* */
public class Ticket implements Runnable {
private int ticket = 100;
//接口不能实例化对象,就用子类的引用
private Lock l=new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
l.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + (ticket--) + "张票");
}else{
return;
}
l.unlock();
}
}
}
package com.oracle.demo03;
public class Test {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
死锁
死锁的前提就是:一个线程必须同时拥有两个对象的资源才能执行程序。
1.线程1 首先占有对象1,接着试图占有对象2
2. 线程2 首先占有对象2,接着试图占有对象1
3. 线程1 等待线程2释放对象2
4. 与此同时,线程2等待线程1释放对象1
就会。。。一直等待下去,直到天荒地老,海枯石烂,山无棱 ,天地合。。。
package com.oracle.demo04;
public class LockA {
private LockA() {
}
//保证A锁的唯一性
public final static LockA la = new LockA();
}
package com.oracle.demo04;
public class LockB {
private LockB() {
}
//保证了B锁的唯一性
public final static LockB lb = new LockB();
}
package com.oracle.demo04;
public class DeadLock implements Runnable {
private int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (LockA.la) {
System.out.println("if...LockA");
synchronized (LockB.lb) {
System.out.println("if...LockB");
}
}
} else {
synchronized (LockB.lb) {
System.out.println("else...LockB");
synchronized (LockA.la) {
System.out.println("else...LockA");
}
}
}
i++;
}
}
}
package com.oracle.demo04;
public class Test {
public static void main(String[] args) {
DeadLock dl = new DeadLock();
Thread t0 = new Thread(dl);
Thread t1 = new Thread(dl);
t0.start();
t1.start();
}
}
等待唤醒机制
线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
等待唤醒机制:通过一定的手段使各个线程能够有效的利用资源,该手段就是等待唤醒机制
等待唤醒机制所涉及到的方法:
wait():等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中
notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的
notifyAll():唤醒全部,可以将线程池中的所有wait()线程都唤醒
所谓唤醒:就是让线程池中的线程具备执行资格。必须注意:这些方法都是在同步中才有效。同时这些方法在使用时必须表明所属锁,这样才可以明确这些方法操作的是哪个锁上的线程。
这些方法都被定义在Object类中了,因为这些方法在使用时,必须表明所属锁,而锁又可以是任意的,能被任意对象调用的方法一定定义在Object类中。
例如:
输入线程向Resource中输入name ,sex ,输出线程从资源中输出,先要完成的任务是:
l 1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();
l 2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。
package com.oracle.demo05;
public class Resouse {
public String name;
public String sex;
public boolean flag=false;
}
//flag标记:为true的时候,赋值完成
// 为false的时候,获取值完成
//输入类:true,等待,false,赋值,赋值完成后,把flag变为true notify输出,自己wait()
//输出类:false,等待,true,取值,取值完成后,把flag变为false notify输入,自己wait()
package com.oracle.demo05;
public class Input implements Runnable {
private Resouse r;
public Input(Resouse r) {
this.r = r;
}
@Override
public void run() {
// 往资源对象中输入数据
int i = 0;
while (true) {
//r表明锁的唯一性
synchronized (r) {
if (r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (i % 2 == 0) {
r.name = "张三";
r.sex = "女";
} else {
r.name = "lisi";
r.sex = "nan";
}
r.flag = true;
r.notify();
}
i++;
}
}
}
package com.oracle.demo05;
public class Output implements Runnable {
private Resouse r;
public Output(Resouse r) {
this.r = r;
}
@Override
public void run() {
// 从资源对象中输出数据
while (true) {
synchronized (r) {
if (!r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(r.name + "..." + r.sex);
r.flag = false;
r.notify();
}
}
}
}
package com.oracle.demo05;
public class Test {
public static void main(String[] args) {
Resouse r = new Resouse();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}