线程安全+Lock锁+死锁+等待唤醒机制+内存可见性+线程池+定时器

一.线程安全

1.线程安全的引入

真实售票案例中,会出现一定的网络延迟.

此时会出现两个问题
 1.出现0张票或负数票:原因,是由于线程的随机性所导致的。
 当只剩下一张票时,此时a线程抢到了CPU执行权,判断piao>=1后进行休眠状态,piao仍然为1,此时b线程抢到了CPU执行权,if判断为true,进行休眠状态,此时c线程抢到了CPU执行权,然后进行休眠状态,然后等a,b,c线程休眠完毕后,卖出的票为第1张,第0张,第-1张票。

        2.出现相同的票:原因,就是由于线程的原子性所导致的  原子性(不可分割性)
        线程对 (piao-- 不是一个原子性操作他要对paio这个变量要进行 读 改 写 三个操作 )

java内存模型:
Java内存分为主存和工作内存,piao变量存放在主存中,需要时读取到每个线程各自的工作内存。
        当a线程读取到100,改为99,没有写回到主存时,b线程就从主存中读取了100,所以就出现了相同票的情况,即线程的原子性导致的.
package org.westos.demo;

public class CellRunnable implements Runnable{
    //三个线程来卖100张票,三个线程共享,是共享数据
    static int piao=100;
    //创建一个共享的锁对象
    static Object object=new Object();

    public CellRunnable() {
    }

    @Override
    public void run() {
        //模拟真实售票案例,存在一定的网络延迟
        /*
        * 发现一个问题,2窗口和3窗口都在卖第97张票
        * */
        while (true) {
         //synchronized (new Object()) {
                synchronized (object){
            if (piao >= 1) {
                //1.注意:多个线程要共享一把锁,才能锁住,解决了出现相同的票的问题
               
                    //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
                    //2.出现0或-1票,线程的随机性,假设最后一张票piao=1,a线程进来
                    休眠,b线程进来休眠,此时c线程抢到了CPU执行权,然后进行休眠状态,然后等a,b,c线程休眠完毕后,卖出的票为第1张,第0张,第-1张票。
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");
                    
                }
                //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
            }
        }
    }
}
------------------
package org.westos.demo;

public class MyTest {
    public static void main(String[] args) {
        //模拟真实售票案例:存在一定的网络延迟
        //创建一个任务
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "1窗口");
        Thread th2 = new Thread(cellRunnable, "2窗口");
        Thread th3 = new Thread(cellRunnable, "3窗口");
        th1.start();
        th2.start();
        th3.start();
        /*我们写的这段售票代码,出现了线程安全问题。多线程的环境下,在对共享数据进行操作时,有可能会出现线程安全问题。


        这个时候出现了一些不合理的数据(数据安全问题)
        1.出现0张票或负数票:原因,是由于线程的随机性所导致的。

        2.出现相同的票:原因,就是由于线程的原子性所导致的  原子性(不可分割性)
        线程对 (piao-- 不是一个原子性操作他要对paio这个变量要进行 读 改 写 三个操作 )


        出现线程安全的问题,得符合三个条件
        1.是不是多线程环境
        2.多个线程有没有共享数据
        3.有没有多条语句在操作这个共享变量 piao--

        那出现了 数据安全问题,我们怎么来解决呢?

        我们把有可能出现数据安全问题的代码,使用同步代码块进行包裹

        同步代码块:
        synchronized (锁){放你认为有肯能出现问题的代码}
        锁:你可以使用Java里面的任意一个对象,来充当锁,注意,多个线程要共享一把锁,才能锁住*/
    }
}

2.线程安全问题的原因

A:首先想为什么出现问题?(也是我们判断是否有问题的标准)
		是否是多线程环境
		是否有共享数据
		是否有多条语句操作共享数据
	B:如何解决多线程安全问题呢?
		基本思想:让程序没有安全问题的环境。
		怎么实现呢?
		把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。


	- 判断一个多线程应用程序是否有问题的标准:
	- a: 是否是多线程环境
	- b: 是否存在共享数据
	- c: 是否存在多条语句同时操作共享数据
	- 
	- 我们现在这个程序是存在问题的,因为它满足上面的标准,那么我们只要将这个标准打乱,那么我们就可以解决这个问题.
	- 而上面的标准中a , b是不能打乱的,因此我们只能对c做处理,关键是怎么处理? 如果我们把操作共享数据的多条语句看做
	- 成一个整体,当一个线程执行这个整体的时候,其他的线程处于等待状态,也就说当一个线程执行这个整体的时候,其他线程
	- 不能进行执行,那么怎么做到这个一点呢?
	- 
	- 需要使用同步代码块:
	- 
	- 格式: 	
	- 
	- synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果
	- 要被同步的代码 ;
	- }
	
	   *
	- 这个同步代码块保证数据的安全性的一个主要因素就是这个对象
	  	注意这个对象 要定义为静态成员变量 才能被所有线程共享
	- 需要这个对象被所有的线程对象所共享
	- 这个对象其实就是一把锁.
	- 这个对象习惯叫做监视器
	
	同步方法:
	同步方法,将synchronized关键字写在方法上
    同步方法:默认用的锁对象是this,谁调用方法,谁就代表this
package org.westos.demo3;

public class CellRunnable implements Runnable {
    //三个线程来卖100张票,三个线程共享,是共享数据
    static int piao = 100;
    //创建一个共享的锁对象
    static Object object = new Object();
    int i = 0;

    public CellRunnable() {
    }

    @Override
    public void run() {

        //模拟真实售票案例,存在一定的网络延迟
        /*
         * 发现一个问题,2窗口和3窗口都在卖第97张票
         * */
        while (true) {
            if (i % 2 == 0) {
                synchronized (object) {
                    if (piao >= 1) {
                        //1.注意:多个线程要共享一把锁,才能锁住,解决了出现相同的票的问题
                        //synchronized (new Object()) {

                        //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面

                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");

                    }
                    //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
                }
            } else {
                //将卖票抽取为一个方法
                soldTecket();
            }

            i++;
        }

    }

    //同步方法,将synchronized关键字写在方法上
    //同步方法:默认用的锁对象是this,谁调用方法,谁就代表this
    //此时会出现线程安全问题,原因是if里面指定锁对象是object,锁对象不一致
    public synchronized void soldTecket() {
        System.out.println("卖票");
        while (true) {
            //synchronized (object) {
            if (piao >= 1) {
                //1.注意:多个线程要共享一把锁,才能锁住,解决了出现相同的票的问题
                //synchronized (new Object()) {

                //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面

                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");

            }
            //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
        }
        //}
    }
}
-----------------------
package org.westos.demo3;

public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable,"1");
        Thread th2 = new Thread(cellRunnable,"2");
        Thread th3 = new Thread(cellRunnable,"3");
        th1.start();
        th2.start();
        th3.start();
    }
}

3.同步代码块与同步方法

package org.westos.demo3;

public class CellRunnable implements Runnable {
    //三个线程来卖100张票,三个线程共享,是共享数据
    static int piao = 100;
    //创建一个共享的锁对象
    static Object object = new Object();
    int i = 0;

    public CellRunnable() {
    }

    @Override
    public void run() {

        //模拟真实售票案例,存在一定的网络延迟
        /*
         * 发现一个问题,2窗口和3窗口都在卖第97张票
         * */
        while (true) {
            if (i % 2 == 0) {
                //synchronized (object) {
                synchronized (this){
                    if (piao >= 1) {
                        //1.注意:多个线程要共享一把锁,才能锁住,解决了出现相同的票的问题
                        //synchronized (new Object()) {

                        //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面

                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");

                    }
                    //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
                }
            } else {
                //将卖票抽取为一个方法
                soldTecket();
            }

            i++;
        }

    }

    //同步方法,将synchronized关键字写在方法上
    //同步方法:默认用的锁对象是this,谁调用方法,谁就代表this
    //此时会出现线程安全问题,原因是if里面指定锁对象是object,锁对象不一致
    public synchronized void soldTecket() {
        System.out.println("卖票");
       
            //synchronized (object) {
            if (piao >= 1) {
                //1.注意:多个线程要共享一把锁,才能锁住,解决了出现相同的票的问题
                //synchronized (new Object()) {

                //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面

                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");

            }
            //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
        
        //}
    }
}
---------------------------------
package org.westos.demo3;

public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable,"1");
        Thread th2 = new Thread(cellRunnable,"2");
        Thread th3 = new Thread(cellRunnable,"3");
        th1.start();
        th2.start();
        th3.start();
    }
}

4.静态同步方法

  同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
   静态同步方法:默认用的锁对对象,用的是当前类的字节码对象
package org.westos.demo4;

import org.westos.demo4.CellRunnable;

public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable,"1");
        Thread th2 = new Thread(cellRunnable,"2");
        Thread th3 = new Thread(cellRunnable,"3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class CellRunnable implements Runnable{
    //静态同步方法
    static int piao=100;
    static int i=0;
    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {
                synchronized (CellRunnable.class) { //
                //synchronized (this){
                    //synchronized (CellRunnable.class) { //
                    if (piao >= 1) {
                        //1.注意:多个线程要共享一把锁,才能锁住,解决了出现相同的票的问题
                        //synchronized (new Object()) {

                        //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面

                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");

                    }
                    //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
                }
            } else {
                //将卖票抽取为一个方法
                soldTecket();
            }

            i++;
        }
    }

    //同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
    //静态同步方法:默认用的锁对对象,用的是当前类的字节码对象 synchronized (CellRunnable.class) { //
    public static synchronized void soldTecket() {
        // System.out.println("卖票");


            if (piao >= 1) {
                //1.注意:多个线程要共享一把锁,才能锁住,解决了出现相同的票的问题
                //synchronized (new Object()) {

                //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面

                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");

            }
            //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片

        //}
    }
}

5.同步的好处与弊端

A:同步代码块的格式
		格式:
		
		synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
		
			死循环
			需要同步的代码;
		}
		
		同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能

	B:同步的好处:	同步的出现解决了多线程的安全问题。
	C:同步的弊端:	当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
	
	在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
	  因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
	  java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
	  线程进入同步代码块或同步方法的时候会自动获得该锁,在退出同步代码块或同步方法时会释放该锁。
	  获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
	  java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
	  当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
	  直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
	  java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
	  但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
	  类锁是用于类的静态方法或者一个类的class对象上的。
	  我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
	  所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
	  但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
	  它只是用来帮助我们理解锁定实例方法和静态方法的区别的.

二.Lock锁

1.概述

A:Lock锁的概述
		虽然我们可以理解同步代码块和同步方法的锁对象问题,
		但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
		为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

2.使用

Lock和ReentrantLock
		void lock() 加锁 
		void unlock() 释放锁 
		
        //Collections中的synchronizedList()方法可以把ArrayList转换成一个线程安全的集合
        List list = Collections.synchronizedList(new ArrayList<String>());

		Lock锁的使用
	
	建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下: 
	
	 class X {
	   private final ReentrantLock lock = new ReentrantLock();
	   // ...
	
	   public void m() { 
	     lock.lock();  // block until condition holds
	     try {
	       // ... method body
	     } finally {
	      //释放锁 不管有没有遇到异常,锁必须释放
	       lock.unlock() 
	     }
	   }
	 }
package org.westos.demo5;

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


public class MyTest {
    public static void main(String[] args) {
        MyRunnable cellRunnable = new MyRunnable();
        Thread th1 = new Thread(cellRunnable,"1");
        Thread th2 = new Thread(cellRunnable,"2");
        Thread th3 = new Thread(cellRunnable,"3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class MyRunnable implements Runnable{
    static int piao=100;
    static ReentrantLock lock=new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //加锁
                lock.lock();

                if (piao >= 1) {
                    try {
                        Thread.sleep(20);
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + (piao--) + "票");
                    //System.out.println(1/0);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();

            }

        }
    }
}

三.死锁

1.概述

	A:死锁问题概述
		如果出现了同步嵌套,就容易产生死锁问题
		是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
		同步代码块的嵌套案例
		 死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
		 举例:	中国人和美国人一起吃饭
	 		中国人使用的筷子
	 		美国人使用的刀和叉
			中国人获取到了美国人的刀
		   美国人获取到了中国人的一根筷子
package org.westos.demo6;

import static org.westos.demo6.Utils.A;

public class MyTest {
    public static void main(String[] args) {
          /* 死锁问题概述
        如果出现了同步嵌套,就容易产生死锁问题
        是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
        举例:	中国人和美国人一起吃饭
 		        中国人使用的筷子
 		        美国人使用的刀和叉
		         中国人获取到了美国人的刀
	             美国人获取到了中国人的一根筷子

        */
        EatThread th1 = new EatThread(false);
        EatThread th2 = new EatThread(true);
        th1.start();
        //Thread.sleep(20);
        th2.start();
    }
}
class EatThread extends Thread{
    boolean flag=false;

    public EatThread(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (Utils.A){
                System.out.println("true线程拥有A锁");
                synchronized (Utils.B){
                    System.out.println("true线程拥有B锁");
                }
            }
        } else {
            synchronized (Utils.B){
                System.out.println("false线程拥有B锁");
                synchronized (Utils.A){
                    System.out.println("false线程拥有A锁");
                }
            }
        }
    }
}
interface Utils{
    //创建两个锁对象
    public static final Object A=new Object();
    public static final Object B=new Object();

}

打印结果:
false线程拥有B锁
true线程拥有A锁

可以在1线程开启后休眠一段时间线程,这样1线程就会立马持有两把锁,立即执行完.第二个线程延迟开启,false线程后开

2.线程间的等待唤醒机制

Object 类中
  void wait ()  在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
       
 void wait (long timeout) 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。
 
   void notify () 唤醒在此对象监视器上等待的单个线程。
        
    void notifyAll ()  唤醒在此对象监视器上等待的所有线程。
    
    生产者与消费者:
前面所学的线程都是同一种类的线程,现在学习两种不同种类的线程,一个是生产者线程,另一个是消费者线程。

这就会涉及不同种类的线程之间通信问题

在这里插入图片描述

package org.westos.demo;

//资源类
public class Student {
    public String name;
    public int age;
    //标记,用来表示是否存在资源
    public boolean flag=false;

    public Student() {
    }
}
----------------------
package org.westos.demo;

//生产者线程
public class SetThread extends Thread{
    Student student;
    int i=1;
    public SetThread(Student student) {
        this.student=student;
    }

    @Override
    public void run() {
        //不断生产
        while (true) {
            //th1进来之后,其他线程必须等待
            synchronized (student) {
                if (student.flag){
                    //flag为true进来,表示存在资源,生产者线程等待
                    try {
                        student.wait();//一旦等待,就会释放锁,在哪里等待,被唤醒后,就从这里执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没有资源就生产资源
                if (i % 2 == 0) {
                    //生产资源:生产一个学生资源
                    //2.Student student = new Student();
                    //资源 学生对象 要让线程来共享数据
                    student.name = "李白";
                    student.age = 22;
                    //
                } else {
                    student.name = "杜甫";
                    student.age = 33;
                }
                //修改标记,存在资源
                student.flag=true;
                //通知消费线程去消费
                student.notify();//唤醒之后,线程还得再次争抢时间片 即便唤醒后仍然是th1抢到,不影响结果,因为flag=true


                //4.出现李白=33,杜甫=22,数据错乱,出现线程安全
            /*原因:
            假设生产者线程先抢到时间片,先生产李白=22,然后生产到杜甫,线程被消费者线程抢去,所以是杜甫=22
            抢到时间片够循环很多次
            * */
            }
            i++;
        }
    }
}
---------------------
package org.westos.demo;

//消费者线程:没有资源等待,通知生产者生产资源
public class GetThread extends Thread{
    Student student;
    public GetThread(Student student) {
        this.student=student;
    }

    @Override
    public void run() {
        //不断消费
        while (true) {
            //两个线程共用一把锁,只new了一份student
            synchronized (student) {
                //没有资源等待,flag为false进去
                if (!student.flag){
                    try {
                        student.wait();//一旦等待,就会释放锁,在哪里等待,被唤醒后,就从这里执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //存在资源就消费
                //消费学生资源:

                //2.Student student = new Student();
                //资源 学生对象 要让线程来共享数据
                System.out.println(student.name + "===" + student.age);

                //消费完通知生产者线程去生产
                student.flag=false;
                student.notify();//唤醒之后,线程还得再次争抢时间片

            }
        }
    }
}
-----------------
package org.westos.demo;

public class MyTest {
    public static void main(String[] args) {
        //生产者-消费者问题

        /*不同种类线程间的通信问题,或者说线程的等待唤醒机制。
        生产者线程
        消费者线程
        2.资源 学生对象 要让线程来共享数据*/
        Student student = new Student();
        //两个线程共用一个学生对象
        SetThread th1 = new SetThread(student);
        GetThread th2 = new GetThread(student);
        th1.start();
        th2.start();
        //1.打印null===0,原因是生产线程里new了一个资源,消费线程也消费了一个new资源,资源未共享
        //3.可能会出现李白=23,null=0,原因是线程抢占时间片
    }
}

3.wait()方法和sleep()方法的区别

wait() 方法和 sleep() 方法的区别
      共同点:都可以使线程处于阻塞状态
      sleep() 必须设置时间量
      wait() 方法可以设置时间量,也可以不设置时间量
       wait() 一旦等待,就会释放锁。
      sleep() 一旦休眠,不释放锁。

四.内存可见性问题

1.volatile 关键字

volatile 解决内存可见性问题
		 一、Java内存模型
		 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。
		 Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,
		 线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。
		 线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。
		 不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
	
		 3.Java中的可见性
		
		 对于可见性,Java提供了volatile关键字来保证可见性。
		 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
		 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,
		 当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
		 另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,
		 并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
		 
	volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。
	 	           相较于 synchronized 是一种较为轻量级的同步策略。
	               
	volatile 变量,用来确保将变量的更新操作通知到其他线程。
	可以将 volatile 看做一个轻量级的锁,但是又与
	锁有些不同:
	 对于多线程,不是一种互斥关系
	 不能保证变量状态的“原子性操作”   

在这里插入图片描述

package org.westos.demo2;

public class MyTest {
    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread th1 = new Thread(myRunnable,"th1");
        th1.start();
        while (true){
            if (myRunnable.getFlag()){
                System.out.println("flag为true,进来执行了");
                break;
            }

        }
    }
}
//任务类
class MyRunnable implements Runnable{
    /* 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,
    当有其他线程需要读取时,它会去内存中读取新值。
		 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,*/
     volatile boolean  flag=false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public boolean getFlag(){
        return flag;
    }

    public MyRunnable() {
    }

    public MyRunnable(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag=true;
        System.out.println(Thread.currentThread().getName() + "线程执行了,此时flag为" + flag);
    }
}

2.CAS算法

CAS 算法:看有没有人先一步将内存值进行更新,如果有放弃此次操作,没有就更新
保证更新时只能有一个线程对内存值进行操作

	 CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器
	操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并
	发访问。
	 CAS 是一种无锁的非阻塞算法的实现。
	 CAS 包含了 3 个操作数:
	 需要读写的内存值 V
	 进行比较的值 A
	 拟写入的新值 B
	 当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的
	   值,否则不会执行任何操作。
	  jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
	  
	 java.util.concurrent.atomic 包下提供了一些原子操作的常用类:
	 AtomicBoolean 、 AtomicInteger 、 AtomicLong 、 AtomicReference
	 AtomicIntegerArray 、 AtomicLongArray
	 AtomicMarkableReference
	 AtomicReferenceArray 

在这里插入图片描述

注:t1,t2线程是同时更新同一变量56的值
因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。
假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。
(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。
t1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值变为了57,
与预期值56不一致,就操作失败了(想改的值不再是原来的值)。
(上图通俗的解释是:CPU去更新一个值,但如果想改的值不再是原来的值,
操作就失败,因为很明显,有其它操作先改变了这个值。)
就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,
然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所。
容易看出 CAS 操作是基于共享数据不会被修改的假设,
采用了类似于数据库的commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。
package org.westos.demo3;

import java.util.concurrent.atomic.AtomicInteger;

public class MyTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread th1 = new Thread(myRunnable, "th1");
        for (int i = 0; i < 10; i++) {
            new Thread(myRunnable).start();
        }
        //i++  i-- 不是一个 原子性的操作 读 改 写

    }
}
class MyRunnable implements Runnable{
    //出现相同的元素
    //int piao=1;
    //原子变量
    AtomicInteger piao= new AtomicInteger(1);
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //System.out.println(piao++);
            int andIncrement = piao.getAndIncrement();
            System.out.println(andIncrement);
        }
    }
}

3.线程的状态转换图

在这里插入图片描述

4.匿名内部类来开启线程

package org.westos.demo3;

public class MyTest2 {
    public static void main(String[] args) {
        //使用匿名内部类来开启线程
        Thread th1 = new Thread() {
            @Override
            public void run() {
                System.out.println("线程执行1");
            }
        };
        th1.start();


        new Thread(){
            @Override
            public void run() {
                System.out.println("线程执行2");
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行3");
            }
        }).start();

    }
}

五.线程池

1.概述

A:线程池概述
		程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
		而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
		线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
		在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
	B:内置线程池的使用概述
		JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
			public static ExecutorService newCachedThreadPool():			根据任务的数量来创建线程对应的线程个数	
			public static ExecutorService newFixedThreadPool(int nThreads):	固定初始化几个线程
			public static ExecutorService newSingleThreadExecutor():			初始化一个线程的线程池
		这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
			Future<?> submit(Runnable task)
			<T> Future<T> submit(Callable<T> task)
		使用步骤:
			创建线程池对象
			创建Runnable实例
			提交Runnable实例
			关闭线程池

线程池:容器,存有一定数量线程对象的容器,线程池可以复用管理线程对象。
        程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
        	而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
        	线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
        	在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

        JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
         public static ExecutorService newCachedThreadPool ():根据任务的数量来创建线程对应的线程个数

当线程池的线程刚创建时,让他们进入阻塞状态:等待某个任务的到来。
 如果任务来了,那就好办,唤醒其中一个线程,让它拿到任务去执行即可

2.方法:

public static ExecutorService newCachedThreadPool ():创建线程池
         根据任务的数量来创建线程对应的线程个数
public static ExecutorService newFixedThreadPool ( int nThreads):
		固定初始化几个线程
		public static ExecutorService newSingleThreadExecutor ():
		初始化一个线程的线程池
package org.westos.demo4;

import javax.sql.rowset.CachedRowSet;
import java.util.concurrent.*;

public class MyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程池
        /*
        线程池:容器,存有一定数量线程对象的容器,线程池可以复用管理线程对象。
        程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
        	而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
        	线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
        	在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

        JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
         public static ExecutorService newCachedThreadPool ():创建线程池
         根据任务的数量来创建线程对应的线程个数
         */

        //ExecutorService 线程池对象
        ExecutorService executorService = Executors.newCachedThreadPool();
        //提交任务,提交几个任务,线程池里就有几个线程
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程执行了这个任务");
            }
        });
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程执行了这个任务");
            }
        });

        Future<Integer> future = executorService.submit(new MyCallable());
        Integer num = future.get();
        System.out.println(num);

        //关闭线程池
        executorService.shutdown();
    }
}
class MyCallable implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call方法执行");
        return 1;
    }
}
--------------------
package org.westos.demo4;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyTest2 {
    public static void main(String[] args) {
        // public static ExecutorService newFixedThreadPool ( int nThreads):固定初始化几个线程
        ExecutorService executorService = Executors.newFixedThreadPool(3);
       // 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
        //提交任务
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行了");
            }
        });
        executorService.shutdown();
    }
}
---------------
package org.westos.demo5;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyTest {
    public static void main(String[] args) {
        // public static ExecutorService newSingleThreadExecutor ():初始化一个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行了任务");
            }
        });
    }
}

六.定时器

1.概述

A:定时器概述
		定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。
		在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。
	B:Timer和TimerTask
		Timer:
			public Timer()
			public void schedule(TimerTask task, long delay):	
			public void schedule(TimerTask task,long delay,long period);
			public void schedule(TimerTask task,  Date time):
			public void schedule(TimerTask task,  Date firstTime, long period):
		TimerTask:定时任务,抽象类
			public abstract void run()
			public boolean cancel()
		开发中
			Quartz是一个完全由java编写的开源调度框架。

2.指定时间的定时器

package org.westos.demo5;

import java.util.Timer;
import java.util.TimerTask;

public class MyTest2 {
    public static void main(String[] args) {
        //定时器 Timer
        Timer timer = new Timer();
        //让定时器,执行定时任务
        MyTimerTask myTimerTask = new MyTimerTask(timer);
        //等3s后执行该定时任务
        //timer.schedule(myTimerTask,3000);

        //取消定时器,在这里取消炸弹不会爆炸
       // timer.cancel();

        //等3秒第一次执行任务,以后间隔1秒重复执行定时任务
        timer.schedule(myTimerTask, 3000,1000);

        //取消定时任务
         myTimerTask.cancel();


    }
}
class MyTimerTask extends TimerTask{
    private Timer timer;

    public MyTimerTask(Timer timer) {
        this.timer = timer;
    }

    @Override
    public void run() {
        System.out.println("炸弹爆炸");
        //timer.cancel();
    }
}

3.指定日期

package org.westos.demo5;


import java.sql.Time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class MyTest3 {
    public static void main(String[] args) throws ParseException {
        //在指定日期执行定时任务
        Timer timer = new Timer();

        String str="2020-06-11 17:17:00";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = simpleDateFormat.parse(str);

        //在指定日期执行定时任务一次
        // timer.schedule(task,date);
        //在指定日期第一次执行任务,以后间隔1s重复执行
        MyTask task = new MyTask();
        timer.schedule(task, date,1000);

    }
}
class MyTask extends TimerTask{
    @Override
    public void run() {
        System.out.println("炸弹爆炸了");
    }
}

4.指定时间删除文件夹

package org.westos.demo5;

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class MyTest4 {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();

        String str="2020-06-11 17:27:00";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = simpleDateFormat.parse(str);

        //执行定时任务
        MyTimer myTimer = new MyTimer();
        timer.schedule(myTimer,date);
    }
}

class MyTimer extends TimerTask{
    File srcFolder= new File("C:\\Users\\user\\Desktop\\demo2");
    @Override
    public void run() {
        deleteFolder(srcFolder);
    }

    public static void deleteFolder(File srcFolder){
        File[] files = srcFolder.listFiles();
        for (File f : files) {
            if (f.isFile()){
                f.delete();
            }else {
                deleteFolder(f);
            }

        }
        srcFolder.delete();
    }
    
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值