同步代码块(同步锁)
写法
synchronized(锁){
加锁的代码
}
运行原理:当线程进入同步锁, 会把锁拿走, 执行代码块中的代码, 代码执行完毕后, 会把锁还回去; 如果线程遇到同步代码块, 发现没有锁, 将进入等待(有锁才能进去)
注意:
保证所有线程使用的都是同一把锁
锁可以使用任意一个对象(同一个对象就行)
下面是运用同步锁编写的一个卖门票例子
public class Demo01 {
public static void main(String[] args) {
// 利用接口的实现类 创建三个线程出来
Tickets tickets = new Tickets();
// 创建三个线程
Thread t1 = new Thread(tickets,"窗口1");
Thread t2 = new Thread(tickets,"窗口2");
Thread t3 = new Thread(tickets,"窗口3");
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
// 利用接口方法 保证访问的共享资源
class Tickets implements Runnable {
// 票总数
private int tickets = 50;
// 声明锁对象(保证锁也是线程共享的)
private final Object obj = new Object();
// 卖票
@Override
public void run() {
// 利用循环 保证票都能卖出去
while (true) {
synchronized (obj) {
// 休眠(可以放大问题)
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 判断票
if (tickets > 0) {
// 可以卖
System.out.println(Thread.currentThread().getName() + "--" + tickets);
// 卖票
tickets--;
} else {
// 卖完了
System.out.println("卖完了");
break;
}
}
// 让线程让出CPU的执行资源(可能让出增加几率)
Thread.yield();
}
}
}
运用同步方法来实现,使用synchronized关键词修饰方法
public class Demo02 {
public static void main(String[] args) {
// 利用接口的实现类 创建三个线程出来
Tickets1 tickets = new Tickets1();
// 创建三个线程
Thread t1 = new Thread(tickets,"窗口1");
Thread t2 = new Thread(tickets,"窗口2");
Thread t3 = new Thread(tickets,"窗口3");
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
// 利用接口方法 保证访问的共享资源
class Tickets1 implements Runnable {
// 票总数
private static int tickets = 50;
// 声明锁对象(保证锁也是线程共享的)
private final Object obj = new Object();
// 卖票
@Override
// 封装方法
public void run() {
// 利用循环 保证票都能卖出去
while (true) {
if (sellTickets()) {
break;
}
// 让线程让出CPU的执行资源(可能让出 增加几率)
Thread.yield();
}
}
// 静态方法也可以使用锁,类锁,成员方法使用的对象锁是this
public static synchronized boolean sellTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "--" + tickets);
tickets--;
return false;
} else {
// 卖完了
System.out.println("卖完了");
return true;
}
}
}
运用Lock接口实现
写法
lock();
try{
加锁的代码
} finaly{
unlock();
}
public class Demo03 {
public static void main(String[] args) {
// 利用接口的实现类 创建三个线程出来
Tickets2 tickets = new Tickets2();
// 创建三个线程
Thread t1 = new Thread(tickets,"窗口1");
Thread t2 = new Thread(tickets"窗口2");
Thread t3 = new Thread(tickets"窗口3");
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
// 利用接口方法 来保证 访问的共享资源
class Tickets2 implements Runnable {
// 票总数
private int tickets = 50;
// 声明锁对象(保证锁也是线程共享的)
private final Object obj = new Object();
// 声明lock锁
// 参数:true 可以尽量让线程公平进入锁(不绝对)
private final ReentrantLock lock = new ReentrantLock(true);
// 卖票
@Override
public void run() {
// 利用循环 保证票都能卖出去
while (true) {
// 使用lock锁
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "--" + tickets);
tickets--;
} else {
break;
}
} finally {
// 释放锁
lock.unlock();
}
// 让线程让出CPU的执行资源(可能让出 增加几率)
Thread.yield();
}
}
}
死锁(尽量避免)
出现前提:
1.至少两个线程
2.锁的嵌套(同步代码块的嵌套)
3.两把锁
public class Demo05 {
public static void main(String[] args) {
DieLockRunnable runnable = new DieLockRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
}
}
// 声明锁
class LockA{
// 私有化构造方法
private LockA() {
// TODO Auto-generated constructor stub
}
// 创建锁对象(声明个常量)
public static final LockA A = new LockA();
}
class LockB{
// 私有化构造方法
private LockB() {
// TODO Auto-generated constructor stub
}
// 创建锁对象(声明个常量)
public static final LockB B = new LockB();
}
// 线程
class DieLockRunnable implements Runnable{
// 利用标记来控制 先A->B 或 先B->A
boolean isTrue = false;
@Override
public void run() {
// 利用死循环 增加死锁几率
while (true) {
// 不断让两个线程
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;
}
}
}
如何停止线程?
-
stop()方法, 已经过时, 不推荐用
-
interrupt()方法可以改变中断状态, 初值false --> true, 当线程中有sleep, wait, join方法时,会抛出异常InterruptException, 中断状态将被清除, 这时interrupt()的值还是false, 所以我不喜欢用
-
推荐方式:直接使用标记停止线程,以下为使用例子
public class Demo06 {
public static void main(String[] args) {
InterruptRunnable runnable = new InterruptRunnable();
Thread t = new Thread(runnable);
t.start();
// 休眠几秒 给子线程运行的时间
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 利用标记停止线程
runnable.isTrue = true;
System.out.println("调用线程中断方法---");
// 让主线程运行一会
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
class InterruptRunnable 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(Thread.currentThread().getName() + "run");
}
}
}
从子线程中修改状态, 主线程中是否能够立即接收到?
答:当你从子线程中修改状态时, 主线程不能立即接收到这个状态的改变, 使用关键词 volatile 来标识你改变的状态的变量 ,可以让主线程立即接收到改变的值
以下为实验案例
public class Demo08 {
public static void main(String[] args) {
ChangeRunnable runnable = new ChangeRunnable();
Thread t = new Thread(runnable);
t.start();
// 利用线程中标记 卡住主线程
while (!runnable.isTrue) {
}
System.out.println("主线程结束");
}
}
class ChangeRunnable implements Runnable{
// 使用关键词 volatile 来标识你改变的状态的变量
// 效果:可以让主线程立即接收到 改变的值
public volatile boolean isTrue = false;
// 记录循环次数
private int num = 0;
@Override
public void run() {
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);
}
}
}