线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
解决思想:当一个线程进入数据操作的时候,无论是否休眠,其他线程只能等待。
- 通过线程休眠,出现安全问题
- 解决安全问题,java程序,提供了一个技术叫做同步技术
- 公式:
- synchronized(任意对象){线程要操作的共享数据}
- 同步代码块
任意对象的作用
任意对象:同步对象,同步锁,对象监视器obj
同步保证安全性:没有锁的线程不能执行只能等
线程遇到同步代码块后,线程判断同步锁还有没有
同步锁,有!
获取锁,进入同步,去执行,在执行完毕后,出去同步代码块后,线程在将锁对象换回去。
如果线程在同步线程中,进行了休眠,此时另外一个线程执行,遇到同步代码块,判断对象锁还有没有。没有锁的线程,不能进入同步中执行,被阻挡在同步代码块外面。
加了同步后,线程进同步判断锁,获取锁,出同步释放锁,导致程序运行速度的下降。
没有锁的线程不能进入同步。在同步中的线程不出去不会释放锁。
package cn.hiluna.demo;
/**
* 通过线程休眠,出现安全问题
* 解决安全问题,java程序,提供了一个技术叫做同步技术
* 公式:
* synchronized(任意对象){线程要操作的共享数据}
* 同步代码块
* @author zhang
*/
public class Tickets implements Runnable{
//定义可以出售的票源
private int ticket = 100;
private Object object = new Object();
@Override
public void run() {
while (true){
//线程共享数据,保证安全,加入同步代码块
synchronized(object){
//对票数进行判断,大于0,可以出售,变量--操作
if (ticket > 0){
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()+"出售第"+ticket-- +"票");
}
}
}
}
}
package cn.hiluna.demo;
/**
* 多线程并发访问同一个数据资源
* 3个线程同时并发,对一个票资源进行出售
* @author zhang
*/
public class ThreadDemo {
public static void main(String[] args) {
//创建runnable接口实现类对象
Tickets tickets = new Tickets();
//创建3个Thread对象,传递runnable接口实现类
Thread thread0 = new Thread(tickets);
Thread thread1 = new Thread(tickets);
Thread thread2 = new Thread(tickets);
thread0.start();thread1.start();thread2.start();
}
}
上面的方式代码比较繁琐,因此可以使用同步方法。
采用同步方法形式,解决线程的安全问题
好处:代码量简洁 将线程的共享数据,和同步,抽取到一个方法中 在方法的声明上,加入同步关键字
问题:同步方法的锁是?
同步方法中的对象锁是本类对象引用this
如果方法是静态的呢,同步锁有吗?有,绝对不是this,而是本类自己.class。
静态方法同步锁是:本类类名.class。
package cn.hiluna.demo01;
import cn.hiluna.demo.*;
/**
* 采用同步方法形式,解决线程的安全问题
* 好处:代码量简洁 将线程的共享数据,和同步,抽取到一个方法中 在方法的声明上,加入同步关键字
*
* @author zhang
*/
public class Tickets implements Runnable {
//定义可以出售的票源
private int ticket = 100;
@Override
public void run() {
while (true) {
payTicket();
}
}
public synchronized void payTicket() {
//对票数进行判断,大于0,可以出售,变量--操作
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "票");
}
}
}
使用JDK1.5的接口lock,替换同步代码块,实现线程安全 Lock接口的方法: lock()获取锁 unlock()释放锁
实现类ReentrantLock
package cn.hiluna.demo02;
import cn.hiluna.demo.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用JDK1.5的接口lock,替换同步代码块,实现线程安全 Lock接口的方法: lock()获取锁 unlock()释放锁
* 实现类ReentrantLock
*
* @author zhang
*/
public class Tickets implements Runnable {
//定义可以出售的票源
private int ticket = 100;
//在类的成员位置创建Lock接口的实现类对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//调用Lock接口方法lock获取锁
lock.lock();
//对票数进行判断,大于0,可以出售,变量--操作
if (ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "票");
} catch (Exception e) {
} finally {
//释放锁,调用Lock接口方法unlock
lock.unlock();
}
}
}
}
}
线程同步:死锁
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这是容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
package cn.hiluna.demo03;
public class LockA {
private LockA(){}
public final static LockA lockA = new LockA();
}
package cn.hiluna.demo03;
public class LockB {
private LockB(){}
public static final LockB lockB = new LockB();
}
package cn.hiluna.demo03;
public class DeadLock implements Runnable{
private int i = 0 ;
public void run(){
while (true) {
if (i%2 == 0) {
//先进入A同步,在进入B同步
synchronized(LockA.lockA){
System.out.println("if...locka");
synchronized(LockB.lockB){
System.out.println("if...lockb");
}
}
}else{
//先进入B同步,在进入A同步
synchronized(LockB.lockB){
System.out.println("else...lockb");
synchronized(LockA.lockA){
System.out.println("else...locka");
}
}
}
i++;
}
}
}
package cn.hiluna.demo03;
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
Thread t0 = new Thread(deadLock);
Thread t1 = new Thread(deadLock);
t0.start();
t1.start();
}
}
线程等待与唤醒案例的实现线程等待与唤醒案例的实现
资源类:
package cn.hiluna.demo04;
/**
* 定义资源类,2个成员变量
* name,sex
* 同时又2个线程,对资源中的变量操作
* 一个对name,age赋值
* 一个对name,age做变量的输出打印
* @author zhang
*/
public class Resource {
public String name;
public String sex;
public boolean flag = false;
}
Input类:
package cn.hiluna.demo04;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 输入的线程,对资源对象Resource中成员变量赋值 一次张三,男 一次李四,女
*
* @author zhang
*/
public class Input implements Runnable {
private Resource r;
public Input(Resource r) {
this.r = r;
}
@Override
public void run() {
int i = 0;
while (true) {
synchronized (r) {
//标记是true,等待
if (r.flag) {
try {
r.wait();
} catch (InterruptedException ex) {
Logger.getLogger(Input.class.getName()).log(Level.SEVERE, null, ex);
}
}
if (i % 2 == 0) {
r.name = "张三";
r.sex = "男";
} else {
r.name = "lisi";
r.sex = "nv";
}
//将对方线程换线,标记改为true
r.flag = true;
r.notify();
}
i++;
}
}
}
Output类:
package cn.hiluna.demo04;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 输出线程,对资源对象Resource中成员变量,输出变量值
*
* @author zhang
*/
public class Output implements Runnable {
private Resource r;
public Output(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
//判断标记,如果是false,等待
if (!r.flag) {
try {
r.wait();
} catch (InterruptedException ex) {
Logger.getLogger(Output.class.getName()).log(Level.SEVERE, null, ex);
}
}
System.out.println(r.name + "..." + r.sex);
//标记改为false
r.flag = false;
r.notify();
}
}
}
}
主类:
package cn.hiluna.demo04;
/**
*开启输入和输出线程,实现程序的赋值和打印值
* @author zhang
*/
public class ThreadDemo {
public static void main(String[] args) {
Resource r = new Resource();
Input input = new Input(r);
Output output = new Output(r);
Thread tin = new Thread(input);
Thread tou = new Thread(output);
tin.start();
tou.start();
}
}