多个线程访问共享资源
解决步骤(以售票为例)
- 1.尝试写代码
- 2.发现问题
-
票会被重复出售 有票被跳过了
- 3.分析问题 如何出现的
-
三个线程同时执行那个run方法 CPU执行资源随机分配
-
线程在执行方法过程中 随时可以进入受阻塞状态
-
所以可以使用 假设线程停止的位置 来分析问题(找极限位置)
- 4.思考解决方法
-
一个线程执行完买票操作 另一个线程才能买票
-
这样来保证 共享数据的安全
- 5.尝试解决
- 同步代码块(同步锁)
- 写法:
- synchronized(锁){
-
上锁的代码
- }
- 当代码进入同步代码块 会把锁拿走 执行代码块中的代码
- 当代码执行完毕后 会把锁还回去
- 如果线程遇到同步代码块 发现没有锁 将进入等待(有锁才能进去)
- 锁:保证所有线程使用的都是同一把锁
- 锁可以是任意一个对象
例(同步代码块):
//利用接口方法 来保证 访问的共享资源
class Tickets implements Runnable{
//票总数
private int tickets = 100;
//声明锁对象(保证唯一)
private final Object obj = new Object();
//买票
@Override
public void run() {
// TODO Auto-generated method stub
//保证票都能卖出
while (true) {
synchronized (obj) {
//休眠放大问题
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//判断票
if (tickets > 0) {
//可以卖
System.out.println(Thread.currentThread().getName() + "----" + tickets);
//卖票
tickets--;
}else {
//卖完了
break;
}
}
//让线程让出CPU的执行资源(可能让出 增加几率)
Thread.yield();
}
}}
public static void main(String[] args) {
//利用接口的实现类 创建三个线程出来
Tickets tickets = new Tickets();
//创建三个线程
Thread t1 = new Thread(tickets);
Thread t2 = new Thread(tickets);
Thread t3 = new Thread(tickets);
//开启线程
t1.start();
t2.start();
t3.start();
}
例(synchronized):
//利用接口方法 来保证 访问的共享资源
class Tickets1 implements Runnable{
//票总数
private int tickets = 100;
//声明锁对象(保证唯一)
private final Object obj = new Object();
//买票
@Override
public void run() {
// TODO Auto-generated method stub
//保证票都能卖出
while (true) {
if (sellTickets()) {
break;
}
//让线程让出CPU的执行资源(可能让出 增加几率)
Thread.yield();
}
}
//封装一个方法
//使用同步方法
//写法:使用synchronized关键词修饰方法
//原理跟同步代码块一样 使用的对象锁是 this
//同步方法 可以是静态方法 但同时要求成员变量也是静态的
//但是静态的同步方法不能用this 使用类锁 类.class
public synchronized boolean sellTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "----" + tickets);
tickets--;
return false;
}else {
//卖完了
return true;
}
}
}
jdk1.5 Lock 接口
-
lock();加锁方法
-
unlock();释放锁方法
-
保证出现异常时 也能把锁关闭
-
写法:
-
lock();
-
try{
-
加锁的代码
-
}finally{
-
释放锁
-
unlock();
-
}
例:
//利用接口方法 来保证 访问的共享资源
class Tickets3 implements Runnable{
//票总数
private int tickets = 100;//声明锁对象(保证唯一)
private final Object obj = new Object();//声明lock锁 //参数:true 可以尽量让线程公平进入锁(不一定) private final ReentrantLock lock = new ReentrantLock(true);
//买票
@Override
public void run() {
// TODO Auto-generated method stub//保证票都能卖出 while (true) { //使用lock锁 lock.lock(); try { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "----" + tickets); tickets--; }else { break; } } finally { // TODO: handle finally clause //释放锁 lock.unlock(); } //让线程让出CPU的执行资源(可能让出 增加几率) Thread.yield(); }
}
}
死锁
前提:
-
1.至少两个线程
-
2.锁的嵌套(同步代码块的的嵌套)
-
3.两把锁
线程1和线程2同时访问有嵌套的同步代码块程序 并且有两个锁 A和B 线程1拿到了A 向进入下一个代码块需要B锁 线程2拿到了B 向进入下一个代码块需要A锁 这时谁也进不去 线程进入相互等待的状态 导致程序卡住
例:
//声明锁
class LockA{
//私有化构造方法
private LockA() {
}
//创建锁对象(声明常量)
public static final LockA A = new LockA();
}
class LockB{
//私有化构造方法
private LockB() {
}
//创建锁对象(声明常量)
public static final LockB B = new LockB();
}
//线程
class DeadLockRunnable implements Runnable{
//利用标记来控制 先A->B 或 先B->A
boolean isTrue = true;
@Override
public void run() {
// TODO Auto-generated method stub
//利用死循环 增加死锁几率
while (true) {
//不断地让两个线程先进A锁再进B锁下次从B锁进A锁
if (isTrue) {
//先进A锁再进B锁
synchronized (LockA.A) {
System.out.println(“if A锁”);
synchronized (LockB.B) {
System.out.println(“if B锁”);
}
}
}else {
//下一次从B锁进A锁
synchronized (LockB.B) {
System.out.println(“else B锁”);
synchronized (LockA.A) {
System.out.println(“else A锁”);
}
}
}
//改变标记
isTrue = !isTrue;
}
}
}
public static void main(String[] args) {
DeadLockRunnable runnable = new DeadLockRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
}
线程停止
调用stop()方法 但已经过时了 不推荐使用
interrupt() 中断线程? 不能中断线程
- 中断状态将被设置
- 1.可以改变中断状态 就是一个布尔值 初值false --> true
- 2.当你这个线程中 使用sleep wait join方法时
-
会抛出个异常InterruptException
-
中断状态将被清除 这是interrupt()的值还是false
正确方式:使用标记停止线程
例:
class Interrupt implements Runnable{
//声明标记 控制线程的停止
public boolean isTrue = false;
@Override
public void run() {
while (!isTrue) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(11);
//InterruptedException 中断异常
// Thread.interrupted();
System.out.println(22);
}
}
}
public static void main(String[] args) throws InterruptedException {
Interrupt interrupt = new Interrupt();
Thread t1 = new Thread(interrupt);
t1.start();
//休眠几秒 给子线程运行的时间
Thread.sleep(3000);
//中断线程
// t1.interrupt();
interrupt.isTrue = true;
System.out.println(“线程中断”);
//让主线程运行一秒
//中断状态被清除指的是 从休眠状态—>运行状态(或者受阻塞)
Thread.sleep(1000);
System.out.println(“主线程结束”);
}
wait方法
class WaitRunnable implements Runnable{
//声明标记 停止线程
public boolean isTrue = false;
//声明锁
private final Object obj = new Object();
@Override
public synchronized void run() {
// TODO Auto-generated method stub
while (!isTrue) {
System.out.println(Thread.currentThread().getName());
try {
//使用线程等待
//wait方法需要使用锁对象来调用
//将线程从等待状态—>运行状态(受阻塞状态)
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}}
public static void main(String[] args) throws InterruptedException {
WaitRunnable runnable = new WaitRunnable();
Thread t1 = new Thread(runnable);
t1.start();
Thread.sleep(3000);
for (int i = 0; i < 10; i++) {
if (i == 5) {
//interrupt方法
//t1.interrupt();
//让线程停止
//runnable.isTrue = true;
}
}
Thread.sleep(1000);
System.out.println("主线程停止");
}
如何立即接收线程状态
当你从子线程中 修改状态时 主线程不能立即接收到这个状态的改变
使用关键词 volatile 来标识你改变的状态的变量
效果是:可以让主线程立即接收到改变的值
class ChangeRunnable implements Runnable{
//当你从子线程中 修改状态时 主线程不能立即接收到这个状态的改变
//使用关键词 volatile 来标识你改变的状态的变量
//效果是:可以让主线程立即接收到改变的值
public volatile boolean isTrue = false;
//记录循环次数
private int num = 0;
@Override
public void run() {
// TODO Auto-generated method stub
while (!isTrue) {
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (num == 5) {
//修改状态
isTrue = true;
}
System.out.println(Thread.currentThread().getName() + “–” + num);
}
}
}
public static void main(String[] args) {
ChangeRunnable runnable = new ChangeRunnable();
Thread thread = new Thread(runnable);
thread.start();
//利用线程中标记 卡住主线程
while (!runnable.isTrue) {
}
System.out.println("主线程结束");
}