以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类
来体现。
Thread类
的特性
每个线程都是通过某个特定Thread对象
的run()
方法来完成操作的,经常把run()
方法的主体称为线程体
通过该Thread对象
的start()
方法来启动这个线程,而非直接调用run()
Thread()
:创建新的Thread对象Thread(String threadname)
:创建线程并指定线程实例名Thread(Runnabletarget)
:指定创建线程的目标对象,它实现了Runnable接口中的run方法Thread(Runnable target, String name)
:创建新的Thread对象Thread
类的方式Runnable
接口的方式/** * 例子:遍历100以内的所有的偶数 */ //1.创建一个继承于Thread类的子类 class MyThread extends Thread{ //重写Thread类的run() @Override public void run() { for(int i = 1;i < 100;i++){ if(i % 2 == 0){ System.out.println(i); } } } } public class ThreadTest { public static void main(String[] args) { //3.创建Thread类的子对象 MyThread t1 = new MyThread(); //4.通过此对象调用start():①启动当前线程 ②调用当前线程的run() t1.start(); //如下操作仍在main线程中执行的 for(int i = 1;i < 100;i++){ if(i % 2 == 0){ System.out.println(i + "***main()***"); } } } }
//1.创建一个继承于Thread类的子类 class MyThread extends Thread{ //重写Thread类的run() @Override public void run() { for(int i = 1;i < 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3.创建Thread类的子对象 MyThread t1 = new MyThread(); //4.通过此对象调用start():①启动当前线程 ②调用当前线程的run() t1.start(); //问题1:我们不能通过直接调用run()的方式启动线程。 // t1.run(); //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException // t1.start(); //我们需要重现创建一个线程的对象,去start(). MyThread t2 = new MyThread(); t2.start(); //如下操作仍在main线程中执行的 for(int i = 1;i < 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i + "***main()***"); } } } }
start()
:启动当前线程,执行当前线程的run()
run()
:通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中currentThread()
: 静态方法,返回当前代码执行的线程getName()
:获取当前线程的名字setName()
:设置当前线程的名字yield()
:释放当前CPU的执行权join()
:在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。stop()
:已过时。当执行此方法时,强制结束当前线程。sleep(long millitime)
:让当前线程“睡眠”指定时间的millitime毫秒)。在指定的millitime毫秒时间内,当前线程是阻塞状态的。isAlive()
:返回boolean,判断线程是否还活着class HelloThread extends Thread{ @Override public void run() { for(int i = 0;i < 100; i++){ try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } // if(i % 20 == 0){ // yield(); // } } } public HelloThread(String name){ super(name); } } public class ThreadModeTest { public static void main(String[] args) { HelloThread h1 = new HelloThread("Thread : 1"); // 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()); } }
调度策略
时间片
抢占式:高优先级的线程抢占CPU
Java的调度方法
class HelloThread extends Thread { @Override public void run() { for (int j = 0; j < 100; j++) { // try { // sleep(10); // } catch (InterruptedException e) { // e.printStackTrace(); // } if (j % 2 == 0) { System.out.println(getName() + ":" + getPriority() + ":" + j); } } } public HelloThread(String name){ super(name); } } public class ThreadModeTest { public static void main(String[] args) { HelloThread h2 = new HelloThread("Thread : 1"); h2.start(); //设置分线程的优先级 h2.setPriority(Thread.MAX_PRIORITY); //给主线程命名 Thread.currentThread().setName("主线程"); Thread.currentThread().setPriority((Thread.MIN_PRIORITY)); for(int j = 0;j < 100; j++){ if(j % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + j); } // if(j == 20){ // try { // h2.join(); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } } System.out.println(h2.isAlive()); } }
Runnable接口的类
run()
Thread类的构造器
中,创建Thread类的对象
Thread类的对象
调用start()
//1.创建一个实现了Runnable接口得类 class MThread implements Runnable{ //2.实现类去实现Runnable中的抽象方法:run() @Override public void run() { for(int i = 0;i < 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest1 { public static void main(String[] args) { //3.创建实现类的对象 MThread m1 = new MThread(); //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 Thread t1 = new Thread(m1); //5.通过Thread类的对象调用start():①启动线程 ②调用当前线程的run() --> 调用了Runnable类型的target的run() t1.start(); //再启动一个线程,遍历100以内的偶数 Thread t2 = new Thread(m1); t2.setName("线程2"); t2.start(); } }
Java中的线程分为两类:一种是守护线程,一种是用户线程。
start()
方法前调用thread.setDaemon(true)
start()
后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源run()
方法定义了线程的操作和功能例子
创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
卖票过程中出现重票、错票 —》出现了线程的安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
在java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){ //需要被同步的代码 }
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
关于同步方法的总结:
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
同步的好处与局限性
class Windows1 implements Runnable{ private int ticket = 100; // Object obj = new Object(); // Dog dog = new Dog(); @Override public void run() { while(true){ synchronized (this) {//此时的this:唯一的windows1的对象 //方式二:synchronized (dog) { if (ticket > 0) { try{ Thread.sleep(100); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket); ticket--; } else { break; } } } } } public class WindowsTest1 { public static void main(String[] args) { Windows1 w = new Windows1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
/** * 使用同步代码块解决继承Thread类的方式的线程安全问题 * * 例子:创建三个c窗口卖票,总票数为100张 */ class Windows extends Thread{ private static int ticket = 100; private static Object obj = new Object(); @Override public void run() { while(true){ //正确的 // synchronized (obj) { synchronized (Windows.class){ //Class clazz = Windows.class //错误的,因为此时this表示的是t1,t2,t3三个对象 // synchronized (this) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + ":卖票,票号为: " + ticket); ticket--; } else { break; } } } } } public class WindowsTest2 { public static void main(String[] args) { Windows t1 = new Windows(); Windows t2 = new Windows(); Windows t3 = new Windows(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
/** * 使用同步方法解决实现Runnable接口的线程安全问题 * * */ class Windows3 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { show(); } } public synchronized void show() { //同步监视器:this // synchronized (this){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket); ticket--; } // } } } public class WindowsTest3 { public static void main(String[] args) { Windows3 w3 = new Windows3(); Thread t1 = new Thread(w3); Thread t2 = new Thread(w3); Thread t3 = new Thread(w3); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
/** * 使用同步方法处理继承Thread类的方式中的线程安全问题 */ class Windows4 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { show(); } } private static synchronized void show(){//同步监视器:Window4.class //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; } } } public class WindowsTest4 { public static void main(String[] args) { Windows4 t1 = new Windows4(); Windows4 t2 = new Windows4(); Windows4 t3 = new Windows4(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
/** * 使用同步机制将单例模式中的懒汉式改写为线程安全的 */ public class BankTest { } class Bank{ private Bank(){} private static Bank instance = null; public static Bank getInstance(){ //方式一:效率稍差 //快捷键:Alt+Shift+Z // synchronized (Bank.class) { // if(instance == null){ // instance = new Bank(); // } // return instance; // } //方式二:效率较高 if(instance == null) { synchronized (Bank.class) { if (instance == null) { instance = new Bank(); } } } return instance; } }
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(); } }
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock
对象加锁,线程开始访问共享资源之前应先获得Lock
对象。
ReentrantLock
类实现了Lock
,它拥有与synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock
,可以显式加锁、释放锁。
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock
对象充当。
优先使用顺序
注意:如果同步代码有异常,要将unlock()写入finally语句块
面试题
class Windows implements Runnable{ private int ticket = 100; //1.实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try{ //调用锁定方法: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(); } } } } public class LockTest { public static void main(String[] args) { Windows w = new Windows(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
wait()
一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。notify()
:一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。notifyAll()
:一旦执行此方法,就会唤醒所有被wait的线程。wait()
,notify()
,notifyAll()
三个方法必须使用在同步代码块或同步方法中。wait()
,notify()
,notifyAll()
三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException
异常wait()
,notify()
,notifyAll()
三个方法是定义在java.lang.Object
类中。class Number implements Runnable{ private int number = 1; public Object obj = new Object(); @Override public void run() { while (true){ synchronized (obj) { obj.notify(); if(number <= 100){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { //使得调用如下wait()方法的线程进入阻塞状态 obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(); Thread t1 = new Thread(number); Thread t2 = new Thread(number); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
/** * 线程通信的应用:经典例题:生产者/消费者问题 * * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品, * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品, * 店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产; * 如果店中没有产品了,店员会告诉消费者等一下, * 如果店中有产品了再通知消费者来取走产品。 * * 分析: * 1.是否是多线程的问题?是,生产者的线程,消费者的线程 * 2.是否有共享数据的问题?是,店员、产品、产品数 * 3.如何解决线程的安全问题?同步机制,有三种方法 * 4.是否涉及线程的通信?是 */ class Clerk{ private int productCount = 0; //生产产品 public synchronized void produceProduct() { if(productCount < 20){ productCount++; System.out.println(Thread.currentThread().getName() + ": 开始生产第" + productCount + "个产品"); notify(); }else{ //等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } //消费产品 public synchronized void consumeProduct() { if(productCount > 0){ System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品"); productCount--; notify(); }else{ //等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer extends Thread{//生产者 private Clerk clerk; public Producer(Clerk clerk){ this.clerk = clerk; } @Override public void run() { System.out.println(getName() + ": 开始生产产品......"); while(true){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } clerk.produceProduct(); } } } class Consumer extends Thread{ //消费者 private Clerk clerk; public Consumer(Clerk clerk){ this.clerk = clerk; } @Override public void run() { System.out.println(getName() + ": 开始消费产品......"); while(true){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } clerk.consumeProduct(); } } } public class ProductTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); p1.setName("生产者1"); Consumer c1 = new Consumer(clerk); c1.setName("消费者1"); Consumer c2 = new Consumer(clerk); c2.setName("消费者2"); p1.start(); c1.start(); c2.start(); } }
Callable
接口的方式创建多线程比实现Runnable
接口创建多线程方式强大?
call()
可以有返回值的。call()
可以抛出异常,被外面的操作捕获,获取异常的信息Callable
是支持泛型的FutureTask
类,比如获取返回结果import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; //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 numThread = new NumThread(); //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象 FutureTask futureTask = new FutureTask(numThread); //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start() new Thread(futureTask).start(); try { //6.获取Callable中call方法的返回值 //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。 Object sum = futureTask.get(); System.out.println("总和为:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
Future接口
Runnable
、Callable
任务的执行结果进行取消、查询是否完成、获取结果等。FutrueTask
是Futrue
接口的唯一的实现类FutureTask
同时实现了Runnable
, Future
接口。它既可以作为Runnable
被线程执行,又可以作为Future
得到Callable
的返回值。corePoolSize
:核心池的大小maximumPoolSize
:最大线程数keepAliveTime
:线程没有任务时最多保持多长时间后会终止import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * 面试题:创建多线程有几种方式?四种! */ class NumberThread implements Runnable{ @Override public void run() { for(int i = 0;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 = 0;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); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //设置线程池的属性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime(); //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 service.execute(new NumberThread()); //适合适用于Runable service.execute(new NumberThread1()); //适合适用于Runable // service.submit(Callable callable); //适合适用于Callable //3.关闭连接池 service.shutdown(); } }
ExecutorService
和Executors
ExecutorService
:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task)
:执行任务,有返回值,一般又来执行Callable
void shutdown()
:关闭连接池Executors
:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool()
:创建一个可根据需要创建新线程的线程池Executors.newFixedThreadPool(n)
; 创建一个可重用固定线程数的线程池Executors.newSingleThreadExecutor()
:创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n)
:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。