对于多线程的数据安全问题,除了对其公有部分加锁之外,还可以调用wait方法使其更加高效。所以就有了等待唤醒机制。通俗点来说就是在多个线程中让他们能够更加分工明确,双向分配任务,也就是一些负责去搬运任务,一些负责去完成任务,当它们在没有各自任务分配时使其等待,从而实现线程有序安全。
上面也仅仅是我自己的一点理解,对于线程也仅是看了一些很浅的东西。
下面是示例代码:
public class GetThread extends Thread {
Student student;
public GetThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (!student.flag) {
//没有资源作为消费者,等着
try {
student.wait(); //一旦等待,就会释放锁,等待后,被唤醒,就从这里醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有了资源,你消费
System.out.println(student.name + "===" + student.age);
//修改标记
student.flag = false;
student.notify();
}
}
}
public class SetThread extends Thread {
Student student;
int i = 0;
public SetThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (student.flag) {
//作为生产者,如果有资源了
try {
student.wait(); //等着
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//在生产资源
if (i % 2 == 0) {
student.name = "张三";
student.age = 23;
} else {
student.name = "李四";
student.age = 24;
}
//修改标记
student.flag = true;
student.notify();//通知消费者去消费,唤醒之后,两个线程还得抢时间片
i++;
}
这里要说一下的就是sleep方法和wait方法的区别,wait在等待期间可以释放锁,而sleep在等待期间无法释放,所以前者要比后者更加效率一点。
线程的几种状态
新建状态:线程被创建出啦
就绪状态:有执行资格,没有执行权
运行状态:有执行资格,也有执行权
阻塞状态:不具有CPU的执行资格,,没有执行权
死亡状态:没有执行资格,没有执行权
新建, 就绪, 运行, 阻塞, 死亡
新建:
线程被创建出来
就绪:
具有CPU的执行资格, 但是不具有CPU的执行权
运行:
具有CPU的执行资格, 也具有CPU的执行权
阻塞:
不具有CPU的执行资格, 也不具有CPU的执行权
死亡:
不具有CPU的执行资格, 也不具有CPU的执行权
Java线程安全的解决方法
首先介绍一种CAS算法:
在了解这个算法之前,首先了解Java中的内存模型:
Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,
线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。
线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。
不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
也就是说无论线程如何操作,它始终是在操作从主内存中拿出来的复制,最终再将此复制返回到主内存完成变量改变。
其次还有另一种因素:Java中的可见性
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,
当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,
并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。而volatile 能够保证内存可见性,他不具备互斥性。但是他不保证原子性。
而CAS算法就是在这俩个特性的基础上来实现的。
CAS(Compare - And - Swap) 是一种硬件对并发的支持,针对多处理器
操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并
发访问。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的 值,否则不会执行任何操作。
代码如下:
AtomicInteger(int initialValue) Java给我们提供的原子变量,实现CAS算法
创建具有给定初始值的新 AtomicInteger。
AtomicInteger num = new AtomicInteger(1);
public int getNum() {
return num.getAndIncrement();
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getNum());
}
}
也就是说,在线程写数据时,它首先会先跟主线程中的值进行对比,如果值改变了,那么此操作结束。
线程池
就像我上面提到的,Java中线程池就是让有限的线程去不断的接取并完成任务即可,因为无论你开的线程有多么多,CPU的处理速度是一样的,我们只要保证CPU在不断的工作即可,没必要给它那么大的负担。
在JDK1.5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool ():根据任务的数量来创建线程对应的线程个数
public static ExecutorService newFixedThreadPool ( int nThreads):固定初始化几个线程
public static ExecutorService newSingleThreadExecutor ():初始化一个线程的线程池
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者
ExecutorService executorService = Executors.newCachedThreadPool();
//给线程池提交任务即可
executorService.submit(new MyRunable());
executorService.submit(new MyRunable());
executorService.submit(new MyRunable());
//关闭线程池
executorService.shutdown();
匿名内部类来开启线程
方式1
new Thread() {
@Override
public void run() {
System.out.println("线程执行了");
}
}.start();
//方式2
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行了");
}
}).start();
}
定时器:Timer
一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
Timer timer = new Timer();
可以到时间执行任务
void schedule (TimerTask task, Date time)
安排在指定的时间执行指定的任务。
void schedule (TimerTask task, Date firstTime,long period)
安排指定的任务在指定的时间开始进行重复的固定延迟执行。
void schedule (TimerTask task,long delay)
安排在指定延迟后执行指定的任务。
void schedule (TimerTask task,long delay, long period)
安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
TimerTask由 Timer 安排为一次执行或重复执行的任务。
boolean cancel ()
timer.schedule(myTimerTask, 3000); //2秒之后执行定时任务
timer.schedule(myTimerTask,2000,1000);//第一次等2秒执行,后面每隔一秒重复执行。
**myTimerTask.cancel();**取消定时任务
//在指定的日期来执行任务
String str = “2019-05-17 15:45:00”;
Date date = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”).parse(str);
timer.schedule(myTimerTask, date);
// timer.cancel();取消定时器
线程程序如下:
public class MyTimerTask extends TimerTask {
Timer timer;
public MyTimerTask(Timer timer) {
this.timer=timer;
}
@Override
public void run() {
System.out.println("砰~~~ 爆炸了");
//timer.cancel();
}
例题:在指定时间删除非空文件夹
public class MyTest {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
MyTimerTask myTimerTask = new MyTimerTask(timer, "E:\\myclass");
//在指定日期删除文件夹
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2019-05-17 16:17:00");
timer.schedule(myTimerTask, date);
}
}
class MyTimerTask extends TimerTask {
Timer timer = null;
String s = null;
public MyTimerTask(Timer timer, String s) {
this.timer = timer;
this.s = s;
}
@Override
public void run() {
//删除一个文件夹
File file = new File(s);
if (file.exists()) {
deleteFolder(file);
}
timer.cancel();
}
private void deleteFolder(File file) {
File[] files = file.listFiles();
for (File f : files) {
if (f.isFile()) {
f.delete();
} else {
deleteFolder(f);
}
}
file.delete();
}
}