多线程概述

数据安全问题的原因:多线程环境下有多条语句操作共享数据
解决方法:让程序没有安全问题的环境
实现方法:把多条语句操作共享数据的代码锁起来,让任意时刻只有一个线程在执行即可

1. 线程同步

1.1 同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现

- 格式
synchronized(任意对象){
      多条语句操作共享数据代码块
}

同步的好处与弊端:
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很浪费资源的,无意中会降低程序得执行效率

1.2 同步方法

同步方法:就是把synchronized关键字加到方法上

  • 格式:修饰符 synchronized 返回值类型 方法名(方法参数){}
    同步对象的锁对象使用this关键字

当方法为静态方法时,
同步静态方法,就是把synchronized关键字加到静态方法上

  • 格式: 修饰符 static synchronized 返回值类型 方法名(方法参数){}
    同步静态方法的锁对象:类名.class

示例代码:

package thread_0.thread_06;

public class t06test implements Runnable {
    private int ticket = 100;
    private Object obj = new Object();
    private int x = 0;


    @Override
    public void run() {
        if (x % 2 == 0) {
            while (true) {
                synchronized (obj) {//synchronized (this)||synchronized (sellTicket.class)//静态方法时
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                        ticket--;

                    }
                }
            }
        } else {
             /*   synchronized (obj) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                    ticket--;
                }*/
            sellTicket();
        }


    }



    private synchronized void sellTicket() {//private static synchronized void sellTicket()改写为静态方法
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
        ticket--;
    }
}

测试代码示例

package thread_0.thread_06;

public class t06 {
    public static void main(String[] args) {
        t06test test = new t06test();

        Thread t1 = new Thread(test,"窗口1");
        Thread t2 = new Thread(test,"窗口2");
        Thread t3 = new Thread(test,"窗口3");

        t1.start();
        t2.start();
        t3.start();


    }
}

结果展示:

窗口1正在出售第100张票
窗口1正在出售第99张票
窗口3正在出售第98张票
窗口3正在出售第97张票
窗口3正在出售第96张票
窗口2正在出售第95张票
窗口3正在出售第94张票
窗口3正在出售第93张票
窗口3正在出售第92张票
窗口3正在出售第91张票
窗口3正在出售第90张票
窗口3正在出售第89张票
窗口3正在出售第88张票
窗口3正在出售第87张票
窗口3正在出售第86张票
窗口3正在出售第85张票
窗口3正在出售第84张票
窗口1正在出售第83张票
窗口1正在出售第82张票
窗口1正在出售第81张票
窗口1正在出售第80张票
窗口1正在出售第79张票
窗口1正在出售第78张票
窗口1正在出售第77张票
窗口1正在出售第76张票
窗口1正在出售第75张票
窗口1正在出售第74张票
窗口1正在出售第73张票
窗口1正在出售第72张票
窗口1正在出售第71张票
窗口1正在出售第70张票
窗口1正在出售第69张票
窗口1正在出售第68张票
窗口1正在出售第67张票
窗口1正在出售第66张票
窗口1正在出售第65张票
窗口1正在出售第64张票
窗口1正在出售第63张票
窗口1正在出售第62张票
窗口1正在出售第61张票
窗口3正在出售第60张票
窗口2正在出售第59张票
窗口2正在出售第58张票
窗口3正在出售第57张票
窗口3正在出售第56张票
窗口1正在出售第55张票
窗口1正在出售第54张票
窗口1正在出售第53张票
窗口1正在出售第52张票
窗口1正在出售第51张票
窗口1正在出售第50张票
窗口3正在出售第49张票
窗口2正在出售第48张票
窗口3正在出售第47张票
窗口3正在出售第46张票
窗口3正在出售第45张票
窗口3正在出售第44张票
窗口3正在出售第43张票
窗口3正在出售第42张票
窗口3正在出售第41张票
窗口3正在出售第40张票
窗口3正在出售第39张票
窗口3正在出售第38张票
窗口3正在出售第37张票
窗口3正在出售第36张票
窗口3正在出售第35张票
窗口3正在出售第34张票
窗口3正在出售第33张票
窗口3正在出售第32张票
窗口3正在出售第31张票
窗口3正在出售第30张票
窗口3正在出售第29张票
窗口3正在出售第28张票
窗口3正在出售第27张票
窗口3正在出售第26张票
窗口3正在出售第25张票
窗口3正在出售第24张票
窗口3正在出售第23张票
窗口3正在出售第22张票
窗口3正在出售第21张票
窗口3正在出售第20张票
窗口3正在出售第19张票
窗口3正在出售第18张票
窗口3正在出售第17张票
窗口3正在出售第16张票
窗口3正在出售第15张票
窗口3正在出售第14张票
窗口3正在出售第13张票
窗口3正在出售第12张票
窗口3正在出售第11张票
窗口3正在出售第10张票
窗口3正在出售第9张票
窗口3正在出售第8张票
窗口1正在出售第7张票
窗口1正在出售第6张票
窗口1正在出售第5张票
窗口3正在出售第4张票
窗口3正在出售第3张票
窗口2正在出售第2张票
窗口2正在出售第1张票

1.3 线程安全的类

线程安全的类中源码都包含synchronized方法

包括StringBuffer,Vector,Hashtable
分别对应的不安全的类为StringBuilder,ArrayList,HashMap

// static <T> list<T> synchronizedList(list<T> list)//返回由指定列表支持的同步(线程安全)列表
List<String> list = Collections.synchronizedList(new ArrayList<String>());

在这里插入图片描述

1.4 锁

lock是一个接口,不可以进行实例化,需要使用其实现类来创建实例对象:比如ReentrantLock
lock有两种方法:void lock() /void unlock()

一般地,加锁使用格式为

 try {
     lock.lock();
     ...
    } catch (...) {
    ...
    } finally {
    ...
    lock.unlock();
}

代码示例:

package thread_0.thread_07;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class t07 implements Runnable {
    private int ticket = 50;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                                       
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                    ticket--;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
package thread_0.thread_07;

public class t07test {
    public static void main(String[] args) {
        t07 t = new t07();

        Thread t1 = new Thread(t,"窗口1");
        Thread t2 = new Thread(t,"窗口2");
        Thread t3 = new Thread(t,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

窗口2正在出售第50张票
窗口2正在出售第49张票
窗口2正在出售第48张票
窗口3正在出售第47张票
窗口3正在出售第46张票
窗口3正在出售第45张票
窗口3正在出售第44张票
窗口3正在出售第43张票
窗口3正在出售第42张票
窗口3正在出售第41张票
窗口3正在出售第40张票
窗口3正在出售第39张票
窗口3正在出售第38张票
窗口3正在出售第37张票
窗口3正在出售第36张票
窗口3正在出售第35张票
窗口3正在出售第34张票
窗口1正在出售第33张票
窗口1正在出售第32张票
窗口1正在出售第31张票
窗口1正在出售第30张票
窗口1正在出售第29张票
窗口1正在出售第28张票
窗口1正在出售第27张票
窗口1正在出售第26张票
窗口1正在出售第25张票
窗口2正在出售第24张票
窗口3正在出售第23张票
窗口3正在出售第22张票
窗口1正在出售第21张票
窗口2正在出售第20张票
窗口2正在出售第19张票
窗口3正在出售第18张票
窗口3正在出售第17张票
窗口3正在出售第16张票
窗口3正在出售第15张票
窗口3正在出售第14张票
窗口3正在出售第13张票
窗口3正在出售第12张票
窗口3正在出售第11张票
窗口3正在出售第10张票
窗口3正在出售第9张票
窗口3正在出售第8张票
窗口3正在出售第7张票
窗口3正在出售第6张票
窗口3正在出售第5张票
窗口3正在出售第4张票
窗口3正在出售第3张票
窗口3正在出售第2张票
窗口3正在出售第1张票

1.5 生产者与消费者

最常用的多线程模式就是生产者与消费者模式
模式:生产者—>共享代码块<—消费者

案例:使用生产者与消费者模式设计生产者放入奶箱a瓶奶,消费者拿到a瓶奶;需要对有无奶进行判断。
在这里插入图片描述

分析:

/**
 * 编写思路
 * 奶箱类(Box):定义一个成员变量,表示第x箱奶,提供存储牛奶和获取牛奶的操作
 * 生产者类(Producer):实现Runnable接口,重写run方法,调用存储牛奶的操作
 * 消费者类(Customer):实现Runnable接口,重写run方法,调用获取牛奶的操作
 * 测试类:含有main方法:操作步骤为:
 *     创建奶箱对象,这是共享数据区域;
 *     创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
 *     穿件消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
 *     创建两个线程对象,分别把生产者和消费者作为构造方法参数传递
 *     启动线程
 *     */

示例代码:

package thread_0.thread_08;

public class Box {
    private int milk;
    private boolean state = false;


    public synchronized void put(int milk) {//方法不使用synchronized修饰的话会出现报错IllegalMonitorStateException
        this.milk = milk;
        if (state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("送奶工将" + this.milk + "瓶奶放入奶箱中");

        state = true;
        //唤醒其他等待的线程
        notifyAll();
    }

    public synchronized void get() {//方法不使用synchronized修饰的话会出现报错IllegalMonitorStateException
        //如果没有牛奶就等待牛奶
        if (!state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果有牛奶就消费
        System.out.println("用户拿到" + this.milk + "瓶奶");

        //消费完成之后,修改奶箱状态
        state = false;
        //唤醒其他等待的线程
        notifyAll();
    }
}

package thread_0.thread_08;

public class Customer implements Runnable {
    private Box b;


    public Customer(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        while (true) b.get();
    }
}

package thread_0.thread_08;

public class Producer implements Runnable {

    private Box b;


    public Producer(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            b.put(i);
        }
    }
}

package thread_0.thread_08;
/**
 * 编写思路
 * 奶箱类(Box):定义一个成员变量,表示第x箱奶,提供存储牛奶和获取牛奶的操作
 * 生产者类(Producer):实现Runnable接口,重写run方法,调用存储牛奶的操作
 * 消费者类(Customer):实现Runnable接口,重写run方法,调用获取牛奶的操作
 * 测试类:含有main方法:操作步骤为:
 *     创建奶箱对象,这是共享数据区域;
 *     创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
 *     穿件消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
 *     创建两个线程对象,分别把生产者和消费者作为构造方法参数传递
 *     启动线程
 *     */

public class test {
    public static void main(String[] args) {
        Box b = new Box();

        //使用alt+enter键来实现自动构建构造参数方法
        Producer p = new Producer(b);

        Customer c = new Customer(b);

        Thread t1 =new Thread(p);
        Thread t2 =new Thread(c);

        t1.start();
        t2.start();

    }

}

注意:
Box类中方法不使用synchronized修饰的话会出现报错IllegalMonitorStateException,添加synchronized即可解决

在这里插入图片描述
在这里插入图片描述

瓦达西瓦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值