Day17
1.多线程数据安全隐患
怎么产生的?线程的随机性+访问延迟
2.以后如何判断程序有没有线程安全问题?
在多线程程序中 + 有共享数据 + 多条语句操作共享数据
1)单线程程序不会出现多线程抢占资源的情况
2)如果没有共享数据,互不干涉,也不会出现数据的安全问题
3)多条语句操作了共享数据,而多条语句的执行是需要时间的,存在延迟
所以这个时间差导致了数据的安全问题
================================
2.多线程安全问题解决方案
1.加锁的位置:
容易出现线程安全问题的资源
2.加锁的范围
太大:整个程序都是同步的,都得排队,效率太低,失去了多线程的效果
太小:要保护的资源锁不住
*同步与异步:
同步:类似于排队的效果,同一时刻只能有一个线程占有资源,其他线程排队
**好处:是线程安全的
**坏处:排队的效率低
异步:不排队,多线程的效果,各个线程之间互相不等待,抢占资源
**好处:抢占资源效率高
**坏处:可能会出现数据安全隐患
死锁:一定要注意避免,手动加锁后一定要记得释放锁
================================
售票案例–4个窗口共计售票100张
/双重校验一:有票的时候再卖票增加一个判断
* synchronized(锁对象){容易发生数据安全问题的代码}
* 在同步代码块中,同一时刻,同一资源只能被一个线程独享,排队
* 注意:锁对象唯一,如果不唯一,还会发生安全问题
* 如果是继承的方式的话,锁对象一般使用本类的字节码对象/
* 同步方法:用synchronized修饰方法就变成了同步方法–不常用
继承Thread
//synchronized (TicketThread2.class) {
package cn.tedu.tickets;
//解决继承下的多线程数据安全
public class TestExtends2 {
public static void main(String[] args) {
TicketThread2 t1 = new TicketThread2("窗口1");
TicketThread2 t2 = new TicketThread2("创口2");
TicketThread2 t3 = new TicketThread2("窗口3");
TicketThread2 t4 = new TicketThread2("窗口4");
//6.以多线程的方式启动--将线程加入到就绪队列
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//自定义多线程类
class TicketThread2 extends Thread{
//定义变量设置票数,设置为静态资源属于类资源,被所有对象共享,只有一份
public TicketThread2(){
super();
}
public TicketThread2(String name){
super(name);
}
static int tickets = 100;
@Override
public void run() {
//super.run();
while (true){
/*双重校验一:有票的时候再卖票增加一个判断
* synchronized(锁对象){容易发生数据安全问题的代码}
* 在同步代码块中,同一时刻,同一资源只能被一个线程独享,排队
* 注意:锁对象唯一,如果不唯一,还会发生安全问题
* 如果是继承的方式的话,锁对象一般使用本类的字节码对象*/
synchronized (TicketThread2.class) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
//4.1输出当前正在卖票的线程名+票数
System.out.println(getName() + "=" + tickets--);
}
if (tickets <= 0) break;
}
}
}
}
实现Runnable接口
//实现接口的方式,不限制锁对象的类型【类型、也可以使用字节码对象】,只需要保持锁对象的唯一
//也就是提取到成员位置创建对象即可,因为target对象只创建一次
//synchronized (new Object) {
//注意锁对象必须唯一,此种方式不正确
//原因:每个线程执行run()时,都会new一个锁对象
package cn.tedu.tickets;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//解决实现接口下的多线程数据安全
public class TestRunnable2 {
public static void main(String[] args) {
TicketsRunnabble2 target = new TicketsRunnabble2();
Thread t1 = new Thread(target, "窗口1");
Thread t2 = new Thread(target, "窗口2");
Thread t3 = new Thread(target, "窗口3");
Thread t4 = new Thread(target, "窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//自定义多线程售票业务类
class TicketsRunnabble2 implements Runnable{
int tickets = 100;
//实现接口的方式,不限制锁对象的类型,只需要保持锁对象的唯一
//也就是提取到成员位置创建对象即可,因为target对象只创建一次
Object o = new Object();
@Override
/*被synchronized修饰的方法称为同步方法*/
//synchronized public void run() {
public void run() {
while(true){
//synchronized (new Object) {
//注意锁对象必须唯一,此种方式不正确
//原因:每个线程执行run()时,都会new一个锁对象
synchronized (o) {//同步代码块
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "=" + tickets--);
} else {
break;
}
}
}
}
}
线程池
存放线程的池子
创建线程池的工具类:Executors.newFixedThreadPool(int自定义的线程数)
启动线程池中的线程:pool.execute(target目标业务对象)
TIPS:线程池会自动管理线程,线程池目前单机测试不关闭,需要手动关闭
//解决实现接口下的多线程数据安全
public class TestRunnable2 {
public static void main(String[] args) {
TicketsRunnabble2 target = new TicketsRunnabble2();
// Thread t1 = new Thread(target,"窗口1");
// Thread t2 = new Thread(target,"窗口2");
// Thread t3 = new Thread(target,"窗口3");
// Thread t4 = new Thread(target,"窗口4");
//
//
// t1.start();
// t2.start();
// t3.start();
// t4.start();
/*9.线程池ExectorService:用于存储线程池子,把新建/启动/关闭线程都交给池来做
* 10.Executors 用于创建线程池的工具类,
* newFixedThreadPool(线程数)--创建一个指定线程数的线程池*/
//创建线程池对象,线程池容纳的线程数指定为5
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i =1;i<6;i++){
//execute(target);让线程池中的线程来执行任务,每次调用都会启动一个线程
//方法的参数是要执行的自定义业务,也就是目标对象taeget
pool.execute(target);
}
}
}
===============================