1 线程同步
- 由于同一进程的多个线程共享同一片存储空间,会引起访问冲突问题。
/**
* 使用锁模拟12306
*
* @author Administrator
*
*/
public class ThreadSynchronizedTest {
public static void main(String[] args) {
// test12306_SyMethod test = new test12306_SyMethod();
// test12306_Norm test = new test12306_Norm();
test12306_SyBlock test = new test12306_SyBlock();
Thread t1 = new Thread(test, "t1");
Thread t2 = new Thread(test, "t2");
Thread t3 = new Thread(test, "t3");
t1.start();
t2.start();
t3.start();
}
}
/*
* 普通抢购模拟,会出现一张票被多个线程抢到,或抢到-1号票问题
*/
class test12306_Norm implements Runnable {
private int ticketNum = 20;
private boolean flag = true;
@Override
public void run() {
while (this.flag) {
buyTicket();
}
}
public void buyTicket() {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
if (ticketNum <= 0) {
this.flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNum + "号票");
ticketNum--;
}
}
/*
* 同步方法
*/
class test12306_SyMethod implements Runnable {
private int ticketNum = 20;
private boolean flag = true;
@Override
public void run() {
while (this.flag) {
buyTicket();
}
}
public synchronized void buyTicket() {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
if (ticketNum <= 0) {
this.flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNum + "号票");
ticketNum--;
}
}
/*
* 同步块
*/
class test12306_SyBlock implements Runnable {
private int ticketNum = 20;
private boolean flag = true;
@Override
public void run() {
while (this.flag) {
buyTicket();
}
}
public void buyTicket() {
synchronized (ThreadSynchronizedTest.class) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
if (ticketNum <= 0) {
this.flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNum + "号票");
ticketNum--;
}
}
}
2 同步块
public void test() {
synchronized (){
// 线程体
}
}
-
同步块可以传入以下几种参数(传参不能为空)
- 引用类型
this
- 类名.class
-
参数是this对象和非this对象的区别
参数 | 相同点 | 不同点 |
---|---|---|
this对象 | 都可以实现同步 | 实现同步的同时,会阻塞其他线程获取这个对象的锁,影响执行效率 |
非this对象 | 都可以实现同步 | 代码块中程序与同步方法是异步的,不与其他锁this同步方法争夺this锁,可以提高执行效率 |
3 单例设计模式
- 作用:确保一个类只有一个对象
- 一般步骤
- 构造器私有化,避免外部直接创建对象
- 声明一个私有的静态变量
- 创建一个对外的公共静态方法访问该变量,如果变量没有对象,创建该对象
- 懒汉式:Double Checking,私有的静态变量初始化为
null
class SingletonPatternSafe {
// 声明一个私有的静态变量
private static SingletonPatternSafe instance = null;
// 构造器私有化,避免外部直接创建对象
private SingletonPatternSafe() {
}
// 创建一个对外的公共静态方法,访问该变量,如果变量没有实例化,创建该对象
// 最基本的变成同步块
public static synchronized SingletonPatternSafe getInstance() {
if (instance == null) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
instance = new SingletonPatternSafe();
}
return instance;
}
/*
* 如果有ABC三个线程进行访问,则A线程访问时,BC线程需要等待
* 如果A线程已经创建了SingletonPatternSafe对象,则BC线程也依旧需要排队等待 效率低下
*/
public static SingletonPatternSafe getInstance01() {
synchronized (SingletonPatternSafe.class) {
if (instance == null) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
instance = new SingletonPatternSafe();
}
return instance;
}
}
/*
* 如果一个线程创建对象成功后,instance不再为空,会直接return 提升效率(Double Checking)
*/
public static SingletonPatternSafe getInstance02() {
// 如果一个线程创建对象成功后,instance不再为空,会直接return
if (instance == null) {
synchronized (SingletonPatternSafe.class) {
if (instance == null) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
instance = new SingletonPatternSafe();
}
}
}
return instance;
}
}
- 饿汉式:私有的静态变量初始化为对应的对象
/*
* 基础版本:当调用getInstance方法时,instance变量才应该被实例化
* 但是基础版本的写法,在调用到SingletonPatternHungry这个类的时候,instance就会被实例化
*/
class SingletonPatternHungryBasic {
// 声明私有的静态属性,同时创建该对象(懒汉式中不会创建)
private static SingletonPatternHungryBasic instance = new SingletonPatternHungryBasic();
// 构造器私有化,防止外部类对其进行实例化
private SingletonPatternHungryBasic() {
}
// 获取对象的实例
public static SingletonPatternHungryBasic getInstance() {
return instance;
}
}
/*
* 进阶版本:将instance封装在一个内部类当中,只有调用getInstance方法时,instance才会被实例化
* 单纯使用SingletonPatternHungry中的其他方法时,instance不会被实例化,延缓了加载时机,提高性能
*/
class SingletonPatternHungry {
// 声明私有的静态属性,同时创建该对象(懒汉式中不会创建)
private static class SingletonPatternHungryHolder {
private static SingletonPatternHungry instance = new SingletonPatternHungry();
}
// 构造器私有化,防止外部类对其进行实例化
private SingletonPatternHungry() {
}
// 获取对象的实例
public static SingletonPatternHungry getInstance() {
return SingletonPatternHungryHolder.instance;
}
}
4 死锁——生产者消费者模式
4.1 模拟死锁现象
- 过多的同步方法可能造成死锁
- 多个线程访问同一份资源的时候会出现死锁现象
/**
* 模拟死锁
*
* @author Administrator
*
*/
public class ThreadDeadlockTest {
public static void main(String[] args) {
Object goods = new Object();
Object prize = new Object();
// 两个线程公用一份资源
Thread01 thread01 = new Thread01(prize, goods);
Thread02 thread02 = new Thread02(prize, goods);
thread01.start();
thread02.start();
}
}
class Thread01 extends Thread {
private Object prize;
private Object goods;
public Thread01(Object prize, Object goods) {
this.goods = goods;
this.prize = prize;
}
@Override
public void run() {
while (true) {
test();
}
}
public void test() {
// Thread01锁住了goods对象,Thread02锁住了prize对象,两者均不释放资源时产生死锁
synchronized (goods) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (prize) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("一手交货");
}
}
class Thread02 extends Thread {
private Object prize;
private Object goods;
public Thread02(Object prize, Object goods) {
this.goods = goods;
this.prize = prize;
}
@Override
public void run() {
while (true) {
test();
}
}
public void test() {
synchronized (prize) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("一手交钱");
}
}
4.2 生产者消费者模式(信号灯法)
- 生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例
- 生产者:生成一定量的数据放到缓冲区中,重复此过程
- 消费者:在缓冲区中消耗这些数据
- 关键问题:保证生产者不会再缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据
- 解决方案:保证生产者在缓冲区满时休眠,等到消费者消耗掉缓冲区中的数据时再次唤醒生产者进行生产。同样的,缓冲区空时,消费者进入休眠状态,等待生产者向缓冲区中添加完数据后,唤醒消费者。
- 通常的采用的方法有:信号灯法和管程法
/**
* 模拟生产者
*
* @author Administrator
*
*/
public class Player extends Thread {
private Movie m;
public Player(Movie m) {
this.m = m;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (0 == i % 2) {
m.play("a" + i);
} else {
m.play("b" + i);
}
}
}
}
/**
* 模拟消费者
*
* @author Administrator
*
*/
public class Watcher extends Thread {
private Movie m;
public Watcher(Movie m) {
this.m = m;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
m.watch();
}
}
}
/**
* 共同数据部分
* 生产者的产物和消费者的消费物
*
* @author Administrator
*
*/
public class Movie {
private String name;
// flag --> True 生产者开始生产,消费者停止消费
// flag --> False 消费者开始消费,生产者停止生产
private boolean flag = true;
public synchronized void play(String name) {
// flag是false的时候,不再生产,生产挂起
if (!flag) {
try {
this.wait(); // wait方法会释放锁,而sleep方法是持锁
} catch (Exception e) {
e.printStackTrace();
}
}
// 模拟生产过程
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
// 生产结束后,将flag置成true,准备唤醒等待的消费者线程
this.flag = false;
// 唤醒消费者线程
this.notify();
}
public synchronized void watch() {
// flag是true的时候,不再消费,消费挂起
if (flag) {
try {
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name);
this.flag = true;
this.notify();
}
}
/**
* 最终测试
*
* @author Administrator
*
*/
public class App {
public static void main(String[] args) {
Movie m = new Movie();
// 两个线程同时持有共同的m对象
Player player = new Player(m);
Watcher watcher = new Watcher(m);
player.start();
watcher.start();
}
}
5 任务调度(Timer类)
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class ThreadScheduleTest {
public static void main(String[] args) {
Timer timer = new Timer();
// schedule(线程体,开始时间,多长时间运行一次线程体)
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("aaa");
}
}, new Date(System.currentTimeMillis() + 5000), 1000);
}
}