主要内容
- 线程安全
- 线程同步
- 死锁
- Lock锁
- 等待唤醒机制
一、线程安全
1.概念
如果多个线程同时运行,这些线程可能会同时运行某一段代码,这个时候如果全局变量和静态变量只有读操作,没有写操作,那么线程是安全的。可是一旦有写操作(更改变量值),那么此线程就是不安全的。
2.演示代码
public class TsShowTicket implements Runnable{
/*
* 模拟多线程售票
*/
private int ticketCount = 100; //目前剩余的所有电影票
@Override
public void run() {
while(ticketCount > 0)
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--);
}
}
}
public static void main(String[] args) {
TsShowTicket task = new TsShowTicket();
Thread t1 = new Thread(task, "窗口1");
Thread t2 = new Thread(task, "窗口2");
Thread t3 = new Thread(task, "窗口3");
t1.start();
t2.start();
t3.start();
}
3.线程同步:解决线程安全隐患的问题
- 线程同步有两种变现形式:同步代码块,同步方法
- 同步代码块:将代码块用大括号括起来,加上synchronized关键字;
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
- 锁对象可以是任意的对象obj,但是多线程中,要保证这些线程中,obj是同一个锁才能保证线程安全。大家共用同一个锁,其中A线程拿走了锁,其余的线程没有锁,就只能等着A线程执行完毕,归还锁,然后再去拿着锁去执行线程。可以理解有锁的线程才能去执行。
@Override
public void run() {
while(true)
{
synchronized (lock) {
if(ticketCount > 0)
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--);
}
}
}
}
- 同步方法
- 同步方法需要在方法的声明上加上synchronized关键字
public synchronized void mothod()
{
可能会产生线程安全问题的代码或者方法;
}
其中,同步方法不需要自己额外的定义锁,因为同步方法的安全锁对象是:this,就是子类实例对象本身。
-
- 同步静态方法
public static synchronized void func()
{
可能会产生线程安全问题的代码或者方法;
}
其中,静态同步方法不需要自己额外的定义锁,因为同步方法的安全锁对象是:这个类,就是类本身。
- 代码展示
@Override
public void run() {
while(true)
{
func();
}
}
private synchronized void func()
{
if(ticketCount > 0)
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--);
}
}
4.死锁
- 同步锁使用的弊端:当线程任务中出现了多个同步锁的时候,如果同步中嵌入了其他同步,那么这个时候很容易出现死锁:无限等待。
- 比如A锁(外层锁)中嵌套B锁(内层锁),如果B锁不能归还,那么A锁就永远不能结束,就无限等待。
- synchronzied(A锁){
synchronized(B锁){
//如果B锁
}
}
public class TsDathLock implements Runnable{ private Object lockA = new Object(); private Object lockB = new Object(); private int x = 0;//new Random().nextInt(1); //产生随机数0-1 @Override public void run() { //一旦出现线程1使用A锁未归还,线程2使用A锁未归还,那么就死了 while(true) { if(x % 2 == 0) { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "偶数lockA"); synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "偶数lockB"); } } } else { synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "偶数lockA"); synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "偶数lockB"); } } } x++; } } }
5.Lock接口
- 概念
Lock接口比
synchronized 关键字提供了更多的方法和操作。
- 常用方法
- Lock() : 获取锁
- unlock(); 归还锁,释放锁
- 使用方法:在需要加锁的代码前添加 lock对象.lock();在代码块结束后添加lock对象.unlock();
@Override public void run() { while(true) { //获取锁 lock.lock(); if(ticketCount > 0 ) { try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--); } //归还锁 lock.unlock(); } }
6.等待唤醒机制
- 线程之间的通信:多个线程处理同一个资源的。
- 多个线程处理同一个资源,但是往往不同线程之间的处理方式不尽相同,那么通过等待唤醒机制,就可以很好的正确的利用和处理资源。
- 常用方法
- wait(); //等待,让正在执行的线程进入线程池中,进行等待操作
- notify(); //唤醒,唤醒线程池中被wait()的线程。一次只能唤醒一个,而且是任意的
- notifyAll(); //唤醒线程池中所有wait()等待的线程
- 其实唤醒的意思就是让wait的线程重新具有执行的资格,实质上还得CPU去分配执行。而且等待和唤醒都是基于锁去执行的。这些方法属于object对象的方法,任何对象都可以调用,但是只有在线程中才会起作用。
- 代码展示
----------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
public class Resource {
private boolean flag = false;
private String name;
private String sex;
public synchronized void out() {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 输出
System.out.println("姓名: " + name + ",性别: " + sex);
// 改变标记
flag = false;
// 开启等待的输入
this.notify();
}
}
public synchronized void set(String name, String sex) {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 设置成员变量
this.name = name;
this.sex = sex;
// 设置之后,Resource中有值,将标记该为 true ,
flag = true;
// 唤醒output
this.notify();
}
}
}
---------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
public class Input implements Runnable{
private Resource r;
public Input(Resource r) {
this.r = r;
}
@Override
public void run() {
int count = 0;
while (true) {
if (count == 0) {
r.set("小明", "男生");
} else {
r.set("小花", "女生");
}
// 在两个数据之间进行切换
count = (count + 1) % 2;
}
}
}
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
public class Output implements Runnable{
private Resource r;
public Output(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.out();
}
}
}
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
public class TsMain {
/**
* @param args
*/
public static void main(String[] args) {
// 资源对象
Resource r = new Resource();
// 任务对象
Input in = new Input(r);
Output out = new Output(r);
// 线程对象
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
// 开启线程
t1.start();
t2.start();
}
}
7.Sleep()和wait()的区别
sleep和wait都会释放CPU的使用权(不再使用CPU),但是sleep不会释放锁,不能唤醒,时间到了自动激活。wait会释放锁,需要通过notify进行唤醒
全文完!