一.同步代码块和锁
CPU是随机执行线程的 也就是说线程可以在方法的任何位置被停止执行(用极限位置去假设)
在多个线程访问共享数据时:
要保证同一时间 只有一个线程操作共享数据
(当一个线程操作数据时 其他线程不能操作 只能等着操作的线程操作完成之后才能继续进入操作数据)
Thread子类不能实现资源共享
用同步代码块(同步锁)来解决:
锁:任意对象(只有一把锁 多个线程使用的是通一把锁)
写法:
synchronized(锁){
操作共享数据的代码
}
卖票的例子:
多个平台同时卖票 为了防止多个平台同时卖一张票
class TicketRunnable implements Runnable {
// 声明票
private int ticket = 50;
// 声明锁对象 保证只有一把锁
private Object obj = new Object();
// 卖票 票--
@Override
public void run() {
// 循环卖
while (true) {
// 同步锁
synchronized (obj) {
// 判断卖完没
if (ticket > 0) {
// 为了让测试结果更明显 加个休眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 卖票
System.out.println(Thread.currentThread().getName()
+ "****" + ticket);
// 卖一张 少一张
ticket--;
} else {
// 卖光了 结束循环
break;
}
}
// 让出CPU的执行资源(随机让出 随机性)
Thread.yield();
}
}
}
在主线程中创建三个子线程 相当于三个平台卖票:
public static void main(String[] args) {
// 创建三个线程
TicketRunnable runnable = new TicketRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
// 开启线程
t1.start();
t2.start();
t3.start();
}
那么同步代码块是怎么做到一个线程执行代码时 其他线程在外面等待的?
同步代码块的规则:
当线程进入同步代码块的时候 先看一下有没有锁
如果有锁 就近入同步代码块中执行代码 进去的同时 会获取这把锁
当执行完毕 出同步代码块时 会将这把锁释放还回去
如果没锁 线程在同步代码块前等待(等着有锁才能进)
二.同步方法
作用和同步代码块一样
也是用synchronized关键字 只是该关键字声明在方法上
同一时间只能有一个线程进入到同步方法中 执行代码
同步方法中 也是写操作共享数据的代码
把上面的例子用同步方法来写:
用接口实现线程的方式
在接口的实现类中再写一个同步方法 如下:
class TicketRunnable1 implements Runnable {
// 声明票
private static int ticket = 50;
// 卖票 票--
@Override
public void run() {
// 循环卖
while (true) {
// 判断卖完没
if (sellTicket()) {
break;
}
}
// 让出CPU的执行资源(随机让出 随机性)
Thread.yield();
}
//声明同步方法
public synchronized boolean sellTicket() {
if (ticket >= 0) {
// 为了让测试结果更明显 加个休眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 卖票
System.out.println(Thread.currentThread().getName()
+ "****" + ticket);
// 卖一张 少一张
ticket--;
return false;
} else {
// 卖光了 结束循环
System.out.println("票已售空");
return true;
}
}
}
几个小问题:
1.同步方法怎么处理的 有没有锁 锁是什么?
有锁 处理方式和同步代码块是一样的 锁是同步的对象(成员)方法 this()本类的对象
2.静态方法能不能加锁?
能
3.静态方法的锁是this吗?
不是 (静态方法不能使用this) 静态方法使用的锁是类锁 类名.class表示这个类
三.锁类 ReentranLock
JDK1.5的Lock接口 使用实现类ReentrantLock
方法:
lock();获取锁
unlock();释放锁 保证锁一定会被释放
使用前提和同步代码块一样 要保证用的是同一把锁
使用格式:
try{
操作共享数据的代码
}finally{
释放锁
}
还是上面的例子 用锁类的方法来写:
class TicketRunnable2 implements Runnable {
// 声明票
private int ticket = 50;
//声明锁 保证同一把
private ReentrantLock lock = new ReentrantLock();
// 卖票 票--
@Override
public void run() {
// 循环卖
while (true) {
lock.lock();
try {
// 判断是否卖完
if (ticket >= 0) {
// 为了让测试结果更明显 加个休眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 卖票
System.out.println(Thread.currentThread().getName()
+ "****" + ticket);
// 卖一张 少一张
ticket--;
} else {
// 卖光了 结束循环
break;
}
} catch (Exception e) {
// TODO: handle exception
}finally {
lock.unlock();
}
// 让出CPU的执行资源(随机让出 随机性)
Thread.yield();
}
}
}
Thread.yield();方法可以让出CPU的执行资源(随机让出 随机性)
四.死锁
前提:
1. 必须要有同步锁的嵌套
synchronized(){
synchronized(){
}
}
2. 锁对象要唯一(使用的是同一把锁)
两把锁都要保证唯一
测试:
创建两个锁类用来创建锁对象
//创建A锁
class LockA{
//为了唯一 不让外人创建 自己创建
private LockA(){
}
//定义一个常量
//能在外界获取到 但是不能修改
public static final LockA LOCK_A = new LockA();
}
//创建B锁
class LockB{
//为了唯一 不让外人创建 自己创建
private LockB(){
}
//定义一个常量
//能在外界获取到 但是不能修改
public static final LockB LOCK_B = new LockB();
}
创建一个接口实现类来创建线程
class DieLockRunnable implements Runnable{
//声明一个标记
private boolean isFlag = true;
//第一次 先进A锁再进B锁
//下一次 先进B锁再进A锁
@Override
public void run() {
//死循环 为了增加死锁的几率
while (true) {
if (isFlag) {
//A -> B
synchronized (LockA.LOCK_A) {
System.out.println("我是if的LOCK_A锁");
synchronized (LockB.LOCK_B) {
System.out.println("我是if的LOCK_B锁");
}
}
}else {
//B -> A
synchronized (LockB.LOCK_B) {
System.out.println("我是else的LOCK_B锁");
synchronized (LockA.LOCK_A) {
System.out.println("我是else的LOCK_A锁");
}
}
}
//修改标记
isFlag = !isFlag;
}
}
}
五.线程的停止问题
stop()方法停止线程已经过时 不推荐使用
interrupt()方法 中断线程
interrupt方法的作用:
1.调用interrupt方法时 线程中有wait()/sleep()等方法 这时会抛出一个异常
InterruptException异常 并且清除中断状态
2.调用interrupt方法时 线程中没有上述方法 这时会设置(改变)中断状态的值(true --> false)
用标记法中断线程:
用接口类的方法创建线程
class InterruptRunnable implements Runnable{
//声明一个标识 用来关闭或者停止线程
public boolean isFlag = false;
@Override
public void run() {
//直接使用中断状态来中断线程
//Thread.currentThread().isInterrupted()
while (!isFlag) {
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
InterruptRunnable runnable = new InterruptRunnable();
Thread t1 = new Thread(runnable);
t1.start();
//给子线程几秒的执行时间 然后再中断线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//标记中断线程
runnable.isFlag = true;
System.out.println("中断线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程结束");
}
wait()方法是Object类中的方法 是让线程等待 并且如果没有被唤醒 就会一直等待下去
直接使用wait()方法会出现异常 IllegalMonitorStateException
注意:wait()方法 必须使用锁对象去调用
当子线程中修改了状态 不能及时同步到主线程中
这时可以使用volatile关键词 让线程中的这个状态信息可以及时得到修改
class VolRunnable implements Runnable{
//当子线程中修改了状态 不能及时同步到主线程中
//这时可以使用volatile关键词 让线程中的这个状态信息 可以及时得到修改
//标记
public volatile boolean isFlag = false;
//声明一个变量 记录 什么时候更改状态
private int num = 0;
@Override
public void run() {
while (!isFlag) {
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (num > 5) {
//更改中断状态
isFlag = true;
}
System.out.println(Thread.currentThread().getName() + "..run");
}
}
}