线程同步
死锁
java">
/**
* 死锁:
* 多个线程各自占有一些共享资源﹐并且互相等待其他线程
* 占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源﹐都停止执行的情形.
* 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.
*
* 产生死锁的四个必要条件:
* 1.互斥条件:一个资源每次只能被一个进程使用。
* 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
* 3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
* 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
*/
买票案例
不安全的买票
java">/**
* 线程同步:
* 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题﹐为了保证数据在方法中被访问时的正确性﹐在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源﹐其他线程必须等待,使用后释放锁即可.存在以下问题:
* 1. 一个线程持有锁会导致其他所有需要此锁的线程挂起;
* 2. 在多线程竞争下﹐加锁﹐释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
* 3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置﹐引起性能问题.
*/
public class UnTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"张三").start();
new Thread(station,"李四").start();
new Thread(station,"王五").start();
}
}
class BuyTicket implements Runnable{
int ticket = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void buy() throws InterruptedException {
if (ticket<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"买到了第"+ticket--+"张票");
}
}
加锁之后买票
java">/**
* 同步块: synchronized (Obj ){ } Obj称之为同步监视器
* Obj可以是任何对象﹐但是推荐使用共享资源作为同步监视器,也就是进行增删改的对象
* 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身﹐或者是class
*同步监视器的执行过程
* 1.第一个线程访问,锁定同步监视器﹐执行其中代码.
* 2.第二个线程访问﹐发现同步监视器被锁定﹐无法访问.
* 3. 第一个线程访问完毕﹐解锁同步监视器.
* 4.第二个线程访问,发现同步监视器没有锁﹐然后锁定并访问
*/
public class SafeTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"张三").start();
new Thread(station,"李四").start();
new Thread(station,"王五").start();
}
}
class BuyTicket1 implements Runnable{
int ticket = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void buy() throws InterruptedException {
if (ticket<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"买到了第"+ticket--+"张票");
}
}
Lock的使用
java">/**
* 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
* java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
* 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开
* 始访问共享资源之前应先获得Lock对象
* ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,
* 在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释
* 放锁。
*/
public class TestLock {
public static void main(String[] args) {
TestLock1 station = new TestLock1();
new Thread(station, "张三").start();
new Thread(station, "李四").start();
new Thread(station, "王五").start();
}
}
class TestLock1 implements Runnable {
int ticket = 10;
boolean flag = true;
// 显式的定义锁
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (flag) {
buy();
}
}
public void buy() {
try {
reentrantLock.lock();//加锁
if (ticket <= 0) {
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "买到了第" + ticket-- + "张票");
} catch (Exception e) {
} finally {
reentrantLock.unlock();//释放锁
}
}
}
/**
* synchronized 与Lock 的对比
* Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
* Lock只有代码块锁,synchronized有代码块锁和方法锁
* 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
* 优先使用顺序:
* Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
*/
生产者和消费者问题
管程法解决
java">/**
* 生产者和消费者问题--管程法解决
* 四个角色:生产者、消费者、产品、缓冲区
* 生产者生产出产品后,通知消费者来取,消费者取出产品后,通知消费者来生产
* 容器里面没有产品时,通知生产者生产,消费者等待
* 容器中产品满时,通知消费者来取,生产者等待
*/
public class TestPc {
public static void main(String[] args) {
SynContain contain = new SynContain();
new Product(contain).start();
new Customer(contain).start();
}
}
/**
* 定义生产者
*/
class Product extends Thread {
private SynContain contain ;
public Product(SynContain contain) {
this.contain = contain;
}
@Override
public void run() {
for (int i = 0; i < 13; i++) {
System.out.println("生产了"+i+"个产品");
contain.push(new Dog(i));
}
}
}
/**
* 定义消费者
*/
class Customer extends Thread {
private SynContain contain ;
public Customer(SynContain contain) {
this.contain = contain;
}
@Override
public void run() {
for (int i = 0; i < 13; i++) {
System.out.println("取出了第"+contain.pop().i+"个产品");
}
}
}
/**
* 定义产品
*/
class Dog {
int i;//产品编号,标识唯一产品
public Dog(int i) {
this.i = i;
}
}
/**
* 定义缓冲区
*/
class SynContain {
Dog[] dogs = new Dog[10];//定义容器大小
int count = 0;//计数器
// 生产者放入产品
public synchronized void push(Dog dog) {
if (count == dogs.length) {
// 如果容器满了,通知消费者来取,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,生产者丢入产品,同时也同这消费者消费
dogs[count] = dog;
count++;
this.notifyAll();
}
// 消费者消费产品
public synchronized Dog pop() {
if (count == 0) {
// 如果容器中,没有产品,通知生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费者取出产品,痛着生产者生产
count--;
Dog dog = dogs[count];
this.notifyAll();
return dog;
}
}
信号灯法解决
java">/**
* 生产者和消费者问题--信号灯法解决
* 四个角色:生产者、消费者、产品、缓冲区
* 生产者生产出产品后,通知消费者来取,消费者取出产品后,通知消费者来生产
* 容器里面没有产品时,通知生产者生产,消费者等待
* 容器中产品满时,通知消费者来取,生产者等待
*/
public class TestPc2 {
public static void main(String[] args) {
TV tv = new TV();
new Actor(tv).start();
new Watcher(tv).start();
}
}
/**
* 定义生产者-->演员
*/
class Actor extends Thread {
TV tv;
public Actor(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
tv.show("唱歌");
}else {
tv.show("游戏主播");
}
}
}
}
/**
* 定义消费者--->观众
*/
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
/**
* 定义产品--->节目
*/
class TV {
//演员表演,观众等待T
//观众观看,演员等待F
boolean flag = true;
String voice;//表演的节目
//节目表演
public synchronized void show(String voice) {
// 没有节目,观众等待
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 有节目,通知观众观看
this.notifyAll();
System.out.println("表演了:"+voice);
this.voice = voice;
this.flag = !this.flag;
}
//观看表演
public synchronized void watch() {
// 观众观看,演员等待
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有节目,通知演员表演
this.notifyAll();
System.out.println("观看了:"+this.voice );
this.flag = !this.flag;
}
}
线程池
java">
/**
* 线程池:
* 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
* 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
* 可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
* 好处:
* 提高响应速度(减少了创建新线程的时间)
* 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 便于线程管理
*/
public class TestPool {
public static void main(String[] args) {
// 创建服务,创建线程池
// newFixedThreadPool参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
// 执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 关闭
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}