一篇文章带你Java多线程入门
多线程的四种创建方式
1.继承Thread类
/* * 多线程的创建,方式一:继承Thread类 * 1.创建一个继承于Thread类的子类 * 2,重写Thread类的run() 将线程操作写在run方法中 * 3.创建Thread类的子类的对象 * 4.通过对象调用start() */ //创建一个继承于Thread类的子类 public class MThread 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 Threadtest { public static void main(String[] args) { //3.创建Thread类的子类的对象 MThread t1=new MThread(); //4.通过此对象调用start() t1.start(); //start方法来实现启动线程,并调用run方法,从而真正实现了多线程。同时run方法它只是一个普通的函数方法,不需要线程调用start方法也可以调用它. } }
2.实现Runnable接口
/* 1.创建一个实现了Runnable接口 2.实现类去实现Runable接口的类 3.创建实现类的对象 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 5.通过Thread类的对象调用start() */ class window implements Runnable{ private int ticket =100; public void run() { while(true) { if(ticket >0) { System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket); ticket--; }else break; } } } public class RunnableTest { public static void main(String[] args) { window w=new window(); //三个线程用的同一个window,都执行同一个window的run,因此在设置票的数量时不需要设置为static Thread t1=new Thread(w); Thread t2=new Thread(w); Thread t3=new Thread(w); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
3.实现Callable接口
/* * 创建线程方式三 实现Callable接口 * 实现Callable接口的方式强于实现Runnable接口的方式 *1.call()可以有返回值。 *2.call()可以抛出异常。 *3.callable支持泛型。 */ //1.创建一个实现Callable的实现类 class NumThread implements Callable{ //2.实现call方法,将此线程需要执行的操作声明在call()方法中 public Object call() throws Exception{ int sum=0; for(int i=0;i<=100;i++) { if(i%2==0) { System.out.println(i); sum+=i; } } return sum; } } public class CallableWay { 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) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
4.使用线程池
/* * 创建线程的方式四:使用线程池 * 好处: * 1.提高响应速度(提高了创建新线程的时间) * 2.降低资源的消耗(重复利用线程池中线程,不需要每次都创建) * 3.便于线程管理 */ class NumberThread implements Runnable{ public void run() { for(int i=0;i<=100;i++) { if(i%2==0) { System.out.println(Thread.currentThread().getName()+":"+i); } } } } public class Thread4 { public static void main(String[] args) { //1.提供指定线程数量的线程池 ExecutorService service =Executors.newScheduledThreadPool(10); //2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象 service.execute(new NumberThread()); //适用于Runnable //service.submit(); //适用于Callable //3.关闭线程池 service.shutdown(); } }
线程的优先级
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 —>默认优先级
如何获取和设置当前线程优先级:
getpriority() //获取线程的优先级 setpriority(int p) //设置线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上来讲,高优先级的线程高概率的情况下被执行,并不意味着只有高优先级的线程执行完后,低优先级的线程才执行。
测试Thread中常用的方法
1.star():启动当前线程;调用当前线程的run()
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():释放当前线程cpu的执行权,各个线程重新“竞争”7.join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完之后,线程a才结束阻塞状态
8.stop():已过时。当执行此方法时,强制结束当前线程
9.sleep(long millitime):让当前线程“睡眠”指定millitime毫秒。在指定的时间内,当前线程是处于阻塞状态
10.isAlive():判断当前线程是否存活
线程的生命周期
多线程的同步控制
1.同步代码块
synchronized(同步监视器){ // 需要被同步的代码 }
下面展示实现Runnable接口的情况。
class TicketWindow implements Runnable { private int ticket = 100; private Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票"); ticket--; } else { break; } } } } } public class ThreadSync { public static void main(String[] args) { TicketWindow ticketWindow = new TicketWindow(); Thread thread1 = new Thread(ticketWindow); Thread thread2 = new Thread(ticketWindow); Thread thread3 = new Thread(ticketWindow); thread1.setName("售票窗口1"); thread2.setName("售票窗口2"); thread3.setName("售票窗口3"); thread1.start(); thread2.start(); thread3.start(); } }
下面展示继承Thread类的情况。
class TicketWindow1 extends Thread { private static int ticket = 100; private static Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票"); ticket--; } else { break; } } } }}public class ThreadSync1 { public static void main(String[] args) { Thread thread1 = new TicketWindow1(); Thread thread2 = new TicketWindow1(); Thread thread3 = new TicketWindow1(); thread1.setName("售票窗口1"); thread2.setName("售票窗口2"); thread3.setName("售票窗口3"); thread1.start(); thread2.start(); thread3.start(); }}class TicketWindow1 extends Thread { private static int ticket = 100; private static Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票"); ticket--; } else { break; } } } } } public class ThreadSync1 { public static void main(String[] args) { Thread thread1 = new TicketWindow1(); Thread thread2 = new TicketWindow1(); Thread thread3 = new TicketWindow1(); thread1.setName("售票窗口1"); thread2.setName("售票窗口2"); thread3.setName("售票窗口3"); thread1.start(); thread2.start(); thread3.start(); } }
对比二者不同二者不同在于synchronized(同步监视器)中的同步监视器,
实现Runnable接口的情况下,同步监视器不需要用static,因为Runnable接口的实现类只被创建一次,三个线程的同步监视器是同一个。而继承Thread类的情况下,同步监视器如不声明为static则被声明了三次,三个线程的同步监视器不是同一。
2.同步方法
访问修饰符 synchronized 返回值 方法名(参数列表) { // 同步代码块 }
下面展示实现Runnable接口的情况。
class TicketWindow implements Runnable { private int ticket = 100; @Override public void run() { while (ticket > 0) { sellTicket(); } } public synchronized void sellTicket() { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票"); ticket--; } } }
下面展示继承Thread类的情况。
class TicketWindow1 extends Thread { private static int ticket = 100; @Override public void run() { while (ticket > 0) { sellTicket(); } } /** * 通过继承Thread类实现的多线程,同步方法必须为静态方法,因为非静态的同步方法,同步监视器为this, * Thread thread1 = new TicketWindow1(); * Thread thread2 = new TicketWindow1(); * Thread thread3 = new TicketWindow1(); * 而上述的this不唯一,因此无法实现对共享资源的互斥访问。 */ public static synchronized void sellTicket() { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票"); ticket--; } } }
3.同步锁
// 1 获得一个锁 Lock lock = new ReentrantLock(); // 2 加锁 lock.lock(); // 同步代码块 // 3 解锁 lock.unlock();
下面展示代码实现。
class TicketWindowLock implements Runnable { private int ticket = 100; // 1 获得一个锁 private Lock lock = new ReentrantLock(); /** * private ReentrantLock lock = new ReentrantLock(true); * 带参数的构造方法:ReentrantLock(boolean fair); ==> 公平锁 * 所谓公平锁:假设现在三个卖票线程按 1、2、3 顺序先后到达并争取锁,但是窗口1获得了锁并卖票, * 窗口2、3等待,等窗口1释放锁后,窗口2、3再按顺序获得锁并卖票,保证按照先到先得的顺序获得锁, * 以此保证公平性。 * * 若是使用无参构造方法获得锁,则不保证公平性。同样的,三个卖票线程按 1、2、3 顺序先后到达并 * 争取锁,但是窗口1获得了锁并卖票,窗口2、3等待,窗口1释放锁后,有可能再次获得锁并卖票,窗 * 口2、3仍然等待,不保证公平性。 */ @Override public void run() { while (true) { try { // 2 上锁 lock.lock(); if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票"); ticket--; } else { break; } } catch (Exception e) { e.printStackTrace(); } finally { // 3 解锁。使用 try catch finally,将解锁操作放在finally语句块中,保证锁一定会被释放 lock.unlock(); } } } }
synchronized与lock锁有何异同
- 相同:二者都是用来解决线程安全问题
- 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器,lock需要手动的打开释放。
线程通信
wait/notify模式
涉及到的三个方法
1.wait()
:一旦执行此方法,当前线程就会进入阻塞状态,并且释放同步监视器。
2.notify()
:一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就优先唤醒高优先级的线程
3.notifyAll()
:一旦执行此方法,所有的线程都会被唤醒
说明上述三个方法都必须使用在同步代码块或同步方法中的同步监视器中,否则会出现异常
上述三个方法都是定义在Object类中的。
sleep和wait的异同
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
1)俩个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2)调用的要求不同:sleep可以在任何场景下调用,而wait只能在同步代码块或者同步方法的同步监视器中
3)关于是否释放同步监视器:如果俩个方法都使用在同步代码块或同步方法中,sleep()不释放,而wait()释放。