分析: 共享数据:同一个学生对象Student 生产者:SetThread给学生对象的成员变量进行赋值操作 消费者:GetThread获取学生对象的成员变量的值 测试者:StudentDemo创建两个线程,一个设置值线程,一个获取值线程 问题: 按照我们分析的步骤去写代码,运行后发现,每一次的结果都是null---0,这是必然的。 原因:设置值的线程与获取值的线程的操作的不是同一个学生对象 如何解决呢? 目的:让设置值的线程与获取值的线程的操作的是同一个学生对象 解决方式:在外界创建出一个学生对象,通过参数的形式让两个线程操作同一个对象
public class StudentDemo { public static void main(String[] args) { Student s = new Student(); //创建自定义类对象 SetThread setThread = new SetThread(s); GetThread getThread = new GetThread(s); //使用Thread类创建线程对象,将自定义对象作为参数传递 Thread proThread = new Thread(setThread); Thread cusThread = new Thread(getThread); proThread.setPriority(1); cusThread.setPriority(10); //启动线程 proThread.start(); cusThread.start(); } }
public class SetThread implements Runnable{ private Student s; SetThread(Student student){ s = student; } @Override public void run() { // Student s = new Student(); s.name = "王宇"; s.age = 18; } }
public class GetThread implements Runnable{ private Student s; GetThread(Student student){ s = student; } @Override public void run() { // Student s = new Student(); System.out.println(s.name+"---"+s.age); } }
public class Student { String name; int age; }
问题二:我们为了方便观察更好的结果来修改,于是我们加入了循环和判断,不同的判断给同一个学生对象赋不同的值 但是呢,我们在运行的时候出现新的问题 1、同一条数据出现了多次 2、姓名和年龄与代码中不匹配 原因: 1、同一条数据出现了多次 由于CPU小小的时间片就可以执行很多次 2、姓名和年龄与代码中不匹配 这是由于线程的执行具有随机性导致的 产生线程同步安全问题: 1、是否存在多线程环境 是 2、是否存在共享数据 是 3、是否有很多条语句操作着共享数据 是 既然都满足,那就说明线程不安全 解决方案: 1、关键字加锁 2、Lock锁 注意事项: 1、不同种类的线程对象都需要加锁 2、不同种类的线程对象拥有的必须是同一把锁
public class StudentDemo { public static void main(String[] args) { Student s = new Student(); Lock lock = new ReentrantLock(); //创建自定义类对象 SetThread setThread = new SetThread(s, lock); GetThread getThread = new GetThread(s, lock); //使用Thread类创建线程对象,将自定义对象作为参数传递 Thread proThread = new Thread(setThread); Thread cusThread = new Thread(getThread); proThread.setPriority(1); cusThread.setPriority(10); //启动线程 proThread.start(); cusThread.start(); } }
import java.util.concurrent.locks.Lock; public class SetThread implements Runnable{ private Student s; private Lock lock; private int i = 0; public SetThread(Student student){ s = student; } SetThread(Student student, Lock lock){ s = student; this.lock = lock; } @Override public void run() { // Student s = new Student(); // s.name = "王宇"; // s.age = 18; // while(true){ // synchronized(s){ // if (i % 2 == 0){ // s.name = "王宇"; // s.age = 18; // }else{ // s.name = "张保桂"; // s.age = 19; // } // i++; // } // } while (true){ lock.lock(); if(i % 2 == 0){ s.name = "王宇"; s.age = 18; }else{ s.name = "张保桂"; s.age = 19; } i++; lock.unlock(); } } }
import java.util.concurrent.locks.Lock; public class GetThread implements Runnable { private Student s; private Lock lock; public GetThread(Student student) { s = student; } GetThread(Student student, Lock lock) { s = student; this.lock = lock; } @Override public void run() { // Student s = new Student(); // while (true){ // synchronized(s){ // System.out.println(s.name+"---"+s.age); // } // } while (true) { lock.lock(); System.out.println(s.name + "---" + s.age); lock.unlock(); } } }
public class Student { String name; int age; }
问题3:我们虽然解决了线程安全的问题,但是观察结果后发现,同一条数据连续消费了很多次,而这样的问题叫做生产者 消费者的问题,使用Java提供的等待唤醒机制去解决。 如何添加等待唤醒机制呢? 所需要调用的方法在Object类中,Object类中有三个方法需要我们学习 void notify() 唤醒正在等待对象监视器的单个线程 void notifyAll() 唤醒正在等待对象监视器的所有线程 void wait() 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法 为什么这三个方法不定义在Thread类中呢? 这些方法要想调用,必须通过锁对象调用,因为如果连锁对象都不一样了,就没必要等待唤醒了,直接执行逻辑代码 而说到现在同步代码块的锁对象是任意对象,类型无法确定,所以这些方法都定义在Object类中,引Java所有的类都有一个共同父类Object
public class StudentDemo { public static void main(String[] args) { Student s = new Student(); //创建自定义类对象 SetThread setThread = new SetThread(s); GetThread getThread = new GetThread(s); //使用Thread类创建线程对象,将自定义对象作为参数传递 Thread proThread = new Thread(setThread); Thread cusThread = new Thread(getThread); //启动线程 proThread.start(); cusThread.start(); } }
import java.util.concurrent.locks.Lock; public class SetThread implements Runnable{ private Student s; private Lock lock; private int i = 0; SetThread(Student student){ s = student; } SetThread(Student student,Lock lock){ s = student; this.lock = lock; } @Override public void run() { // Student s = new Student(); s.name = "王宇"; s.age = 18; while (true){ synchronized(s){ //判断学生对象有没有值 //flag初始的值是false,表示没有值,如果是true表示生产者已经生产了值 //有值对于生产者来说,等待消费者消费数据 if (s.flag){ try { s.wait();//阻塞,等待消费者消费数据,才能继续往下走 } catch (InterruptedException e) { e.printStackTrace(); } } if (i % 2 == 0){ s.name = "王宇"; s.age = 18; }else{ s.name = "张保桂"; s.age = 19; } i++; //生产者生产完数据后,将flag值改为true //通知消费者消费数据 s.notify(); s.flag = true; } } // while (true){ // lock.lock(); // //先看看数据有没有被消费 // if (i % 2 == 0){ // s.name = "王宇"; // s.age = 18; // }else{ // s.name = "张保桂"; // s.age = 19; // } // //通知消费者来消费数据 // i++; // lock.unlock(); // } } }
import java.util.concurrent.locks.Lock; public class GetThread implements Runnable{ private Student s; private Lock lock; GetThread(Student student){ s = student; } GetThread(Student student,Lock lock){ s = student; this.lock = lock; } @Override public void run() { // Student s = new Student(); while (true){ synchronized(s){ //判断学生对象的成员变量是否有值 //如果flag的值是true,表示可以进行取值,反之,等待生产者生产数据 if (!s.flag){ try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name+"---"+s.age); //消费者消费完数据后,通知生产者生产数据 s.notify(); s.flag = false; } } // while (true){ // lock.lock(); // //先看看数据有没有存在 // System.out.println(s.name+"---"+s.age); // //通知生产者去生产数据 // lock.unlock(); // } } }
public class Student { String name; int age; boolean flag; }
死锁: 指的是两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象
中国人和美国人吃饭的案例 假设中国人吃饭的时候,必须要有两根筷子才能吃饭 美国人吃饭的时候,必须要有一把刀和一把叉才能吃饭 正常情况下: 中国人:两根筷子 美国人:一把刀,一把叉 发生死锁的情况: 中国人:一根筷子,一把刀 美国人:一根筷子,一把叉 死锁的现象更容易出现在同步嵌套情况下,所以今后开发的时候,尽量避免同步嵌套
public class DieLockDemo { public static void main(String[] args) { //创建两个线程对象 DieLock d1 = new DieLock(true); DieLock d2 = new DieLock(false); //启动线程 d1.start(); d2.start(); } }
public class DieLock extends Thread{ private boolean flag; public DieLock(boolean flag){ this.flag = flag; } /** * 现象1: * if lock1 * else lock2 * * 现象2: * else lock2 * if lock1 */ @Override public void run() { if (flag){ synchronized(MyLock.lock1){ System.out.println("if lock1"); synchronized(MyLock.lock2){ System.out.println("if lock2"); } } }else{ synchronized(MyLock.lock2){ System.out.println("else lock2"); synchronized(MyLock.lock1){ System.out.println("else lock1"); } } } } }
1、线程同步的好处
解决了多线程的安全问题
2、线程同步的弊端
加了一个同代码块后,就相当于加了一把锁,每次进入同步代码块的时候都会去判断一下 无形之中,降低了我们执行效率
3、代码
public class SellTicketDemo1 { public static void main(String[] args) { TicketWindow1 ticketWindow1 = new TicketWindow1(); //使用Thread类创建多个线程对象 Thread window1 = new Thread(ticketWindow1); Thread window2 = new Thread(ticketWindow1); Thread window3 = new Thread(ticketWindow1); //给线程起名字 window1.setName("窗口1"); window2.setName("窗口2"); window3.setName("窗口3"); //启动线程 window1.start(); window2.start(); window3.start(); } }
public class TicketWindow1 implements Runnable{ private int tickets = 100; private Object object = new Object(); @Override public void run() { while(true){ synchronized(object){ if (tickets>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票"); } } } } }
1、同步代码块上的锁对象是什么呢? 任意对象,但是需要注意的是,多个线程之间的锁对象要一样 2、同步方法呢? 将synchronized关键字放在方法的定义上 同步方法的锁对象是this 3、静态的同步代码块的锁对象是谁呢? class文件对象,是线程类的字节码文件对象,其他的字节码文件对象不行
public class SellTicketDemo2 { public static void main(String[] args) { TicketWindow2 ticketWindow1 = new TicketWindow2(); //使用Thread类创建多个线程对象 Thread window1 = new Thread(ticketWindow1); Thread window2 = new Thread(ticketWindow1); Thread window3 = new Thread(ticketWindow1); //给线程起名字 window1.setName("窗口1"); window2.setName("窗口1"); window3.setName("窗口1"); //启动线程 window1.start(); window2.start(); window3.start(); // Hashtable<String,String> stringStringHashtable = new Hashtable<>(); } }
public class TicketWindow2 implements Runnable{ // private int tickets = 100; private static int tickets = 100; private Object object = new Object(); private Demo demo = new Demo(); int i = 0; @Override public void run() { // while(true){ // synchronized(demo){ // if(tickets>0){ // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票"); // } // } // } while(true){ if(i % 2 == 0){ synchronized(TicketWindow2.class){ if (tickets > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票"); } } }else{ sellTickets(); } i++; } } //同步方法 // public synchronized void sellTickets(){ // if (tickets>0){ // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票"); // } // } //静态同步方法 public synchronized static void sellTickets(){ if(tickets>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票"); } } } class Demo{ }
在此之前我们解决同步线程安全的时候,都是使用synchronized关键字,通过一顿分析后,将需要被包裹的代码给包起来,但是呢我们并没有 实际看到在哪里上了锁,又或是在哪里释放了锁,让其他线程获取到锁对象,执行线程。
Lock(接口) 具体的子类:Class ReentrantLock lock()加锁 unlock()释放锁
public class SellTicketDemo3 { public static void main(String[] args) { TicketWindow3 ticketWindow1 = new TicketWindow3(); //使用Thread类创建多个线程对象 Thread window1 = new Thread(ticketWindow1); Thread window2 = new Thread(ticketWindow1); Thread window3 = new Thread(ticketWindow1); //给线程起名字 window1.setName("窗口1"); window2.setName("窗口2"); window3.setName("窗口3"); //启动线程 window1.start(); window2.start(); window3.start(); // Hashtable<String,String> stringStringHashtable = new Hashtable<>(); } }
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TicketWindow3 implements Runnable{ private int tickets = 100; private Object object = new Object(); //创建锁对象 private Lock lock = new ReentrantLock(); @Override public void run() { while (true){ lock.lock(); if (tickets>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票"); } lock.unlock(); } } }
import java.util.*; /* 回忆下到目前为止,我们学习哪些线程安全的类 */ public class ThreadDemo { public static void main(String[] args) { //StringBuffer StringBuffer sb = new StringBuffer(); //Vector Vector<String> strings = new Vector<>(); //Hashtable Hashtable<String,String> stringStringHashtable = new Hashtable<>(); //虽然Vector这个类是线程安全的,但是今后我们也不用 //Collections工具类将线程不安全的集合转成线程安全的 ArrayList<String> strings1 = new ArrayList<>(); List<String> strings2 = Collections.synchronizedList(strings1); } }
/* 匿名内部类的形式创建线程对象 */ public class ThreadDemo2 { public static void main(String[] args) { //1、继承Thread类,重写run方法,start()启动线程 new Thread("王宇"){ @Override public void run() { for(int i = 1;i<=200;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }.start(); //2、实现Runnable接口,实现run方法 new Thread(new Runnable(){ @Override public void run() { for (int i =1;i <= 200;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } },"小虎").start(); } }
java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
public class ThreadGroupDemo { public static void main(String[] args) { //创建自定义对象 MyRunnable myRunnable = new MyRunnable(); //通过Thread类创建的多个线程对象 Thread t1 = new Thread(myRunnable, "王宇"); Thread t2 = new Thread(myRunnable, "张保桂"); //默认情况下,所有的线程都属于主线程组 //public final ThreadGroup geThreadGroup(); ThreadGroup tg1 =t1.getThreadGroup(); System.out.println(tg1); ThreadGroup tg2 = t2.getThreadGroup(); System.out.println(tg2); //如何获取线程组的名字 //public final String getName()返回此线程组的名称 String s1 = tg1.getName(); String s2 = tg2.getName(); System.out.println(s1); System.out.println(s2); System.out.println(s1.equals(s2)); //需求:给线程分组 //Thread(ThreadGroup group,Runnable target,String name) //分配一个新的Thread对象,使其具有target作为其运行对象,具有指定的name作为其名称,属于group引用的线程组 //创建一个新的线程组 //ThreadGroup(String name) //构造一个新的线程组 ThreadGroup tg3 = new ThreadGroup("帅哥组"); //创建线程对象并分组 Thread t3 = new Thread(tg3, myRunnable, "李雨阳"); Thread t4 = new Thread(tg3, myRunnable, "小虎"); Thread t5 = new Thread(tg3, myRunnable, "刘志成"); //获取到目前位置所有线程的名字以及所属线程组的名字 System.out.println(t1.getName()+"属于线程组:"+t1.getThreadGroup().getName()); System.out.println(t2.getName()+"属于线程组:"+t2.getThreadGroup().getName()); System.out.println(t3.getName()+"属于线程组:"+t3.getThreadGroup().getName()); System.out.println(t4.getName()+"属于线程组:"+t4.getThreadGroup().getName()); System.out.println(t5.getName()+"属于线程组:"+t5.getThreadGroup().getName()); //java允许程序直接对线程组进行控制 //需求:将一个组内的所有线程设置为守护线程 //直接通过组名进行设置,组里面的线程都是属于守护线程 tg3.setDaemon(true); } }
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0;i<=200;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
程序启动一个新的线程成本是比较高的,因为它涉及到要与操作系统进行交互 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑线程池 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用 在JDk5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
1、创建线程池对象,Executors工厂类下有很多获取线程池的静态方法 newFixedThreadPool是其中的一种线程池 public static ExecutorService newFixedThreadPool(int nThreads) 2、如何在线程池中放线程呢?(可以存放哪些线程呢?) 3、在线程池中的线程如何去运行呢? 4、我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
public class ThreadPoolDemo { public static void main(String[] args) { //创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); //Future<?> submit(Runnable task) //提交一个可运行的任务执行,并返回一个表示该任务的未来 //提交即运行 MyRunnable myRunnable = new MyRunnable(); pool.submit(myRunnable);//底层封装成了一个线程对象并启动执行 pool.submit(myRunnable); //提交的线程数超过线程池的数量也会执行,只不过是当有空闲线程位置的时候再去执行 pool.submit(myRunnable); //我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束? //void shutdown() //启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务 pool.shutdown(); //RejectedExecutionException //线程池已经被关闭了,不能再继续提交任务 pool.submit(myRunnable); } }
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0;i<=200;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
自定义类实现Callable接口,实现call方法,该线程的启动必须与线程池结合,单独无法创建线程对象启动 <T> Future<T> submit(Callable<T> task) 提交值返回任务可以执行,并返回代表任务待处理结果的Future
public class ThreadPoolDemo2 { public static void main(String[] args) { //创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); //创建自定义Callable对象 MyCallable myCallable = new MyCallable(); //提交到线程池中执行 pool.submit(myCallable); //手动停止线程池 pool.shutdown(); } }
import java.util.concurrent.Callable; public class MyCallable implements Callable { @Override public Object call() throws Exception { Object o = new Object(); for(int i = 1;i <= 200;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } return o; } }
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行 在Java中,可以通过Timer和TimerTask类来实现定义调度的功能 如何实现创建定时器呢? Java提供了一个类给我们使用实现定时器:Timer
import java.util.Timer; import java.util.TimerTask; public class TimerDemo { public static void main(String[] args) { //创建定时器对象 //Timer //创建一个新的计时器 Timer timer = new Timer(); //调度任务执行 //void schedule(TimerTask task,long delay) //在指定的延迟之后安排指定的任务执行 timer.schedule(new MyTask(timer),3000); // timer.cancel(); // timer.schedule(new MyTask(timer),3000);//IllegalStateException } } class MyTask extends TimerTask { private Timer timer; public MyTask(Timer timer){ this.timer = timer; } @Override public void run() { System.out.println("beng!!!!!爆炸了"); timer.cancel(); } }
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; public class TimerDemo2 { public static void main(String[] args) { //创建定时器对象 Timer timer = new Timer(); //void schedule(TimerTask task,long delay,long period) //在指定的延迟之后开始,重新执行,固定延迟执行的指定任务 //3秒后执行任务,并且之后每隔两秒执行一次 timer.schedule(new MyTask2(),3000,2000); } } class MyTask2 extends TimerTask { @Override public void run() { try { FileReader fr = new FileReader("ss.txt"); BufferedReader br = new BufferedReader(fr); String s = br.readLine(); System.out.println(s); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }