同步代码块(同步锁)
处理多线程访问共同数据的问题
保证数据只有一份,同一时间只有一个线程可以操作共享数据
使用同步代码块(同步锁)
(当一个线程操作数据时 其他安县城不能操作 只能等着操作的线程结束后 才能进入操作数据)
锁: 任意对象(唯一的 只有一把锁)
synchronized(锁) {
操作共享数据的代码
}
锁可以用this(但静态方法不能使用)
静态方法中用类锁 --- 类名.class
可以创建一个常量对象(使锁 唯一) 作为对象锁 --- 类名.对象名
同步代码块可以做到 一个线程执行代码时 其他线程在外面等待
的规则:
当线程进入同步代码块的时候
先看一下 有没有锁
如果有锁 就进入同步代码块中执行代码
进入的同时 会获取这把锁
当执行完毕 出同步代码块时 将锁释放 还回去
如果没有锁 则 线程在同步代码块外面等待(等里面的线程执行完释放锁)
有锁才能进
Thread.yield(); 线程每执行一次,让出资源
(随机让出---下一个可以还是该线程占有资源)
Ensample:
三个平台卖演唱会门票
保证多个线程访问共享数据(数据只有一份)
使用接口实现类的方式创建线程 来实现共享数据 (Thread子类创建线程不能实现共享资源)
// 使用接口实现类方式创建线程来实现共享数据
class Ticket implements Runnable {
// 共享数据 50张票
private int ticket = 50;
// 声明锁对象 保证只有一把锁
private Object obj = new Object();
int num = 0; // 做标记(让最后提示只打印一次)
// 卖票 票--
@Override
public void run() {
while (true) {
// 同步代码块
synchronized (obj) {
// 看是否还有票
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()
+ "卖票" + ticket);
} else {
num++;
if (num>2) {
System.out.println("票卖光了");
}
break;
}
// 卖一张少一张
ticket--;
}
// 随机让出资源
Thread.yield();
}
}
}
打印测试:
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
同步方法(作用和同步代码块相同)
也是使用synchroized关键字
改关键字声明在方法上 --- 同一时间 只能有一个线程进入到同步方法中执行代码
同步方法中 写操作共享数据的代码
// 接口实现线程的方式
class TicketRunnable1 implements Runnable {
// 声明票(共享数据)
private static int ticket = 50;
// 卖票 票--
@Override
public void run() {
while (true) {
// 调用方法
if (sellTicket()) {
break;
}
// 让出CPU的执行资源
Thread.yield();
}
}
// 声明同步方法
public synchronized static 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 {
return true;
}
}
}
注意:
同步方法的处理方式和同步代码块一样
同步的对象(成员)方法 锁是this(本类对象)
静态方法的锁不能使用this 需要使用类锁 类名.class
Lock接口
JDK1.5出现 Lock接口
使用其 实现类ReentrantLock
lock(); 获取锁
unlock(); 释放锁
使用前提: 要保证 使用的是同一把锁
使用格式:
try{
操作共享数据的代码
} finally {
释放锁
}
// 接口实现线程
class TicketReentranLock implements Runnable {
// 声明票
private int ticket = 50;
// 生成锁 保证同一把(唯一锁)
private ReentrantLock createLock = new ReentrantLock();
@Override
public void run() {
// 卖票
while (true) {
// 获取锁
createLock.lock();
try {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()
+ "卖票" + ticket);
ticket--;
} else {
// 卖完
break;
}
} finally {
// 释放锁
createLock.unlock();
}
// 让出CPU的执行资源
Thread.yield();
}
}
}
线程死锁
前提:
1.必须要有同步锁嵌套
2.锁对象 唯一(使用同一把锁)
两把锁都要保证唯一
synchronized(){
synchronized() {
}
}
Ensample:
测试死锁
创建锁
// 创建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 Test implements Runnable {
// 声明一个标记
private boolean sign = true;
// 第一次 先进A锁再进B锁
// 下一次 先进B锁再进A锁
@Override
public void run() {
// 死循环 增加死锁的几率
while (true) {
if (sign) {
// 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锁");
}
}
}
// 修改标记
sign = !sign;
}
}
}
测试
public static void main(String[] args) {
// 测试
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
中断线程
线程如何停止
stop()方法 已过时 不推荐使用
interrupt()方法并不能中断线程
interrupt()方法的作用:
1.调用interruput方法时 线程中有 wait() / sleep()等方法时
这时会抛出 InterruptException异常 并且 清除wait、sleep中断状态
2.调用interruput方法时 线程中没有上述方法
这时 会设置(改变) 中断状态的值(true--false)
中断线程 --- 使用标记法
class InterruptRun implements Runnable {
// 声明标记
public boolean isFlag = false;
@Override
public void run() {
while (!isFlag) {
// 休眠1秒(使用循环达到)
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 1000) {
// 使用空循环 卡住进程1秒
}
System.out.println("进程正常运行..run");
}
}
}
interruput()方法中断无效 使用标记法
public static void main(String[] args) {
// 开启一个进程
InterruptRun run = new InterruptRun();
Thread t = new Thread(run);
t.start();
// 给子线程几秒的执行时间 然后中断
try {
// 3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 中断线程
//t.interrupt(); // 中断无效
// 标记线程中断
run.isFlag = true;
System.out.println("中断线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程结束");
}
从子线程中更改中断的标记
主线程可以接收到标记的更改
注意:
当子线程中 修改的状态不能及时同步到主线程中
这时 可以使用volatile关键词
让线程中的这个状态信息 可以及时同步过去
------------------------------------------------------
今天很残酷 明天更残酷 后天很美好