12.线程
package com.atguigu.java; /* 多线程的创建: 方式一:继承于Thread类 ① 创建一个继承thread类的子类 ② 重写Thread类的run()-->此线程执行的操作声明在run鸿 ③创建Thread类的子类对象 ④ 通过此对象调用start() start()的作用:①启动当前线程 ②调用当前线性run() 注意:①:不能通过直接调用run()的方式启动线程 ② 当再启动一个线程,不可以还让已经start()的线程去执行,会报illegalThreadStateException 需要新建一个线程对象 例子:遍历100以内的多有偶数 */ public class ThreadTest { public static void main(String[] args) { MyThread mythread = new MyThread(); mythread.start(); System.out.println("hello"); } } //① 创建一个继承thread类的子类 class MyThread extends Thread{ // ② 重写Thread类的run() @Override public void run() { for( int i = 0; i<=100; i++){ if (i%2 == 0){ System.out.println(i); } } } }
线程中常用方法
package com.atguigu.java; /* 测试Thread中的常用方法 1.start():启动当前线程;调用当前线程run()方法 2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 3.currentThread(): 静态方法,返回执行当前代码的线程 4.getName(): 获取当前线程的名字 5.setName(): 设置当前线程的名字 6.yield():释放当前cpu的执行权 7.join():在线程a中调用线程b的join()方法,此时线程a就进入阻塞状态,当线性b执行完后,a开始执行 8.sleep(long millitime):让当前线程睡眠指定millitime毫秒,在时间段内,当前线程是阻塞状态 9.isAlive():判断当前线程是否存活 线程的优先级 1. MAX_PRIORITY:10 NORM_PRIORITY: 5 --normal 默认优先级 MIN_PRIORITY: 1 2.获取和设置当前线程的优先级 getPriority():获取线程优先级 setPriority(int p):设置线程优先级 说明:高优先级的线程要抢占低优先级线程cpu的执行权,但并不意味着,只有当高优先级线程执行完之后,低优先级线程在执行 */ public class ThreadMethodsTest { public static void main(String[] args) { Hthread h1 = new Hthread("thread:1-"); //分线程起名:使用setName方法;或使用 带参构造器 //设置分线程的优先级 h1.setPriority(Thread.MAX_PRIORITY); //h1.setName("线程一"); h1.start(); //给主线程起名 Thread.currentThread().setName("主线程"); for(int i = 0; i <= 100;i++){ if(i% 2 == 0){ System.out.println(Thread.currentThread().getName() +i); } if (i == 20){ try { h1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(h1.isAlive());//h1已执行完 false } } class Hthread extends Thread{ public Hthread(String name){ super(name); } @Override public void run() { for(int i = 0; i <= 100;i++){ if(i% 2 == 0){ try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +i); System.out.println(Thread.currentThread().getPriority()); } if(i % 20 == 0){ this.yield(); } } } }
package com.atguigu.java; /* 例子:创建三个窗口,总票数为100张 存在线程安全问题,待解决 */ class Window extends Thread{ private static int ticket = 100; @Override public void run() { while(true){ if (ticket > 0 ){ System.out.println(getName()+": 卖票,票号为:"+ticket); ticket--; }else{ break; } } } } public class WindowTest { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } }
创建线程方式二
package com.atguigu.java; /* 创建多线程的方式二:实现Runnable接口 1.创建实现Runnable接口的类 2.实现类去实现Runnable中的抽象方法:run() 3.创建实现类的对象 4.将此对象作为参数传递到Thread类的构造其中,创建Thread类的对象 5.通过Thread类的对象调用start()
比较创建线程的两种方式:
开发中,优先选择 实现Runnable接口的方式
1.实现的方式没有类的单继承的局限性
2.实现类的方式更适合处理多个线程有共享数据的情况
联系;Thread类实现类Runnable接口
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run中
*/ public class ThreadTest1 { public static void main(String[] args) { Mthread m1 = new Mthread(); Thread t1 = new Thread(m1); Thread t2 = new Thread(m1); t1.start(); t2.start(); } } class Mthread implements Runnable{ @Override public void run() { for (int i = 0; i<= 100 ; i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
package com.atguigu.java; /* 例子:创建三个窗口,总票数为100张 */ public class WindowTest1 { public static void main(String[] args) { Windows w1 = new Windows();//window只具有一个对象,及共享一百张票 Thread t1 = new Thread(w1); Thread t2 = new Thread(w1); Thread t3 = new Thread(w1); t1.setName("窗口1:"); t2.setName("窗口2:"); t3.setName("窗口3:"); t1.start(); t2.start(); t3.start(); } } class Windows implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ if(ticket>0) { System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; }else{ break; } } } }
Java中,通过同步机制,来解决线程安全问题
package com.atgugu.java; /* 例子:创建三个窗口,总票数为100张 1.买票过程中,出现重票和错票 2.问题出现原因,当某个线程操作过程中,且尚未完成,其它线程进行操作 3.当一个线程在操作共享数据的时候,其他线程不可参与操作,指导当前线程操作完,其它线程才可以操作共享线程,即使当前线程阻塞,也不可改变 4.通过同步机制,解决线程安全问题 方式一:同步代码块 synchronized(同步监视器){ //需要被同步的代码 } 说明:1.操作共享数据的代码,即为需要被同步的代码 2.共同数据:多个线程共同操作的变量。 3.同步监视器(锁):任何一个类的对象,都可以充当一个锁, 要求:多个线程必须要共用同一把锁。 4.好处:同步代码块,解决了线程安全问题 局限性:操作同步代码时,只能有一个线程参与,其它线程等待。想相当于单线程的过程 */ public class WindowTest1 { public static void main(String[] args) { Windows w1 = new Windows();//window只具有一个对象,及共享一百张票 Thread t1 = new Thread(w1); Thread t2 = new Thread(w1); Thread t3 = new Thread(w1); t1.setName("窗口1:"); t2.setName("窗口2:"); t3.setName("窗口3:"); t1.start(); t2.start(); t3.start(); } } class Windows implements Runnable{ private int ticket = 100; //Object obj = new Object(); @Override public void run() { while(true){ synchronized(this){//synchronized(obj){ 优化:this当前对象-->Windows的对象--->只有一个对象w if(ticket>0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; }else{ break; } } } } }
package com.atgugu.java; /* 使用同步方法解决 实现Runnable接口的线程安全问题 关于同步方法的总结: 1.同步方法仍然涉及到同步监视器,知识不需要显示的声明 2.非静态的同步方法,同步监视器是:this 静态的同步方法,同步监视器是:当前类本身 */ public class WindowTest2 { public static void main(String[] args) { Windows2 w1 = new Windows2();//window只具有一个对象,及共享一百张票 Thread t1 = new Thread(w1); Thread t2 = new Thread(w1); Thread t3 = new Thread(w1); t1.setName("窗口1:"); t2.setName("窗口2:"); t3.setName("窗口3:"); t1.start(); t2.start(); t3.start(); } } class Windows2 implements Runnable{ private int ticket = 100; //Object obj = new Object(); @Override public void run() { while(true){ show(); } } private synchronized void show(){//同步监视器:this if(ticket>0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; } } }
package com.atgugu.java1; /* 使用同步机制将单例模式中的懒汉式改写为线程安全的 */ public class BlankTest { public static void main(String[] args) { Blank b1 = Blank.getBlank(); Blank b2 = Blank.getBlank(); System.out.println(b1 == b2); } } class Blank{ private Blank(){ } private static Blank blank = null; public synchronized static Blank getBlank(){ if(blank == null){ blank = new Blank(); } return blank; } /*或者使用同步代码块 public static Blank getBlank(){ synchronized(Blank.class){ if(blank == null){ blank = new Blank(); } return blank; } }*/ }
死锁问题
package com.atgugu.java1; /* 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁 2.说明: 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续 2)使用同步时,需要避免出现死锁 */ public class ThreadTest { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread(){ @Override public void run() { synchronized(s1){ s1.append("a"); s2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run() { synchronized(s2){ s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1){ s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
解决线程安全方式三:lock锁
package com.atgugu.java1; import java.util.concurrent.locks.ReentrantLock; /* 解决线程安全问题的方式三: lock锁 ---JDK5.0新增 synchronized 与Lock的异同 1.相同: 解决线程安全问题 2.不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器 Lock需要手动的启动同步Lock(),同时结束同步也需要手动实现 unLock() */ public class LockTest { public static void main(String[] args) { Window w1 = new Window(); Thread t1 = new Thread(w1); Thread t2 = new Thread(w1); Thread t3 = new Thread(w1); t1.start(); t2.start(); t3.start(); } } class Window implements Runnable{ private int ticket = 100; //1.实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try{ //2.调用锁定方法:lock()方法 lock.lock(); if (ticket >0 ){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+ticket); ticket --; }else{ break; } }finally{ //3.调用解锁方法:unlock() lock.unlock(); } } } }
线程通信的三个方法:
package com.atgugu.exec; /* 两个线程交替打印 1-100的数 涉及到的三个方法: wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器。 notify():一旦执行此方法,就会唤醒被wait()的一个线程,要是多个线程,按照优先级唤醒 notifyall();一定那执行此方法,就会唤醒所有被wait的线程 说明: 1.wait、notify、notifyall:必须使用在同步代码块或同步方法中。 2.wait、notify\notifyall: 调用者必须是同步代码块或者同步方法中的同步监视器,否则出现IllegalMonitorStateException 3.wait、notify\notifyall: 定义在Object */ class Number implements Runnable{ private int num = 1; private Object obj = new Object(); @Override public void run() { while(true){ synchronized(obj){ obj.notify();//notify唤醒一个,notifyall 唤醒所有线程 if (num <= 100){ try { Thread.sleep(100);//进入阻塞状态,但不释放锁 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+num); try { obj.wait();//使得调用如下wait方法的线程进入阻塞状态,并释放锁 } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } } public class NumberTest { public static void main(String[] args) { } }
线程的创建方式三:实现Callable接口
package com.atgugu.java2; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /* 创建线程的方式三:实现Callable接口 ---JDK5.0 新增加 如何理解实现Callable接口比是心啊Runnable接口强大: 1.call方法可以有返回值 2.call方法可以抛出异常,被外面操作捕获,获取异常信息 3.Callable可以支持泛型 */ //1.创建一个实现Callable接口的实现类 class NumThread implements Callable{ //2.实现call方法,将此线程需要执行的操作,放在call方法中 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i<=100; i++){ if (i%2==0){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { //3.创建Callable接口实现类的对象 NumThread n1 = new NumThread(); //4.将次Callable接口实现类的对象床底到FutureTask的构造器中,创建FutureTask对象 FutureTask f1 = new FutureTask(n1); //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法 Thread t1 = new Thread(f1); t1.start(); try { //6.(可选)获取Callable中call方法的返回值 //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值 Object sum = f1.get(); System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
线程的创建方式四:使用线程池
package com.atgugu.java2; /* 创建线程的方式4:使用线程池 使用连接池的好处 1.提高响应速度(减少了创建新线程的时间) 2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建) 3.便于线程管理 corePoolSize 核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长时间后会终止 */ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class NumberThread implements Runnable{ @Override public void run() { for (int i = 1; i<=100; i++){ if (i%2==0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 1; i<=100; i++){ if (i%2 != 0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } public class ThreadPool { public static void main(String[] args) { //1.提供指定线程数量的线程池 ExecutorService service = Executors.newFixedThreadPool(10); //2.执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象 service.execute(new NumberThread());//适合使用于Runnable service.execute(new NumberThread1());//适合使用于Runnable //service.submit();适合使用于Callable //3.关闭线程池。 service.shutdown(); } }
虽不能至,心向往之