多线程
java.lang.Thread类代表线程,所有线程对象都必须是Thread类或其子类的对象
线程创建并启动步骤
方式一:继承Thread类
创建一个继承于Thread类的子类
重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
创建当前Thread的子类的对象
通过对象调用start():1.启动线程 2.调用当前线程的run()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class EvenNumberTest { public static void main(String[] args) { PrintNumber t1 = new PrintNumber(); t1.start();
PrintNumber t2 = new PrintNumber(); t2.start(); for(int i =1; i<=10000;i++){ if(i % 2 ==0){ System.out.println(Thread.currentThread().getName()+":"+i+"****"); } } } }
class PrintNumber extends Thread{ @Override public void run() { for(int i =1;i<=1000;i++){ if(i%2 ==0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } }
|
方法二:实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现接口的run() –>将此线程要执行的操作,声明在此方法体中
- 创建当前实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
- Thread类的实例调用start()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class EvenNumberTest { public static void main(String[] args) { EvenNumberPrint p = new EvenNumberPrint(); Thread t1 = new Thread(p); t1.start();
Thread t2 = new Thread(p); t2.start();
for(int i=1; i<=100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
class EvenNumberPrint implements Runnable{ @Override public void run() { for(int i=1; i<=100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
|
两种方式对比
共同点:
1.创建线程的对象,都是Thread类或其子类的实例
2.启动线程都使用的是Thread类中的start()
不同点:
一个是类的继承,一个是接口的实现
建议使用实现Runnable接口的方式。
Runnable好处:1.避免单继承的局限性 2.更适合处理有贡献数据的问题 3.实现代码与数据的分离
二者联系:public class Thread implements Runnable
线程常用结构
线程的构造器
public Thread(): 分配一个新的线程对象
public Thread(String name):分配一个指定名字的新的线程对象
public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
public Thread(Runnable target, String name):分配一个带有指定目标新的线程对象并指定名字
线程中常用方法
- start():1.启动线程 2.调用线程的run()
- run():将线程要执行的操作,声明在run()中
- currentThread():获取当前执行代码对应的线程
- getName():获取线程名
- setName():设置线程名
- sleep(long millis):静态方法,调用时,可以使得代码所在当前线程睡眠指定的毫秒数
- yield():静态方法,一旦执行此方法,就释放cpu的执行权
- join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
- isAlive():判断当前线程是否存活
线程优先级
(java是抢占式调度)
Thread类的三个优先级常量
MAX_PRIORITY(10):最高优先级
MIN_PRIORITY(1):最低优先级
NORM_PRIORITY(5):普通优先级,默认情况main线程具有普通优先级
getPriority():获取线程的优先级
setPriority():设置线程的优先级,范围[1,10],大数值就有大概率
线程生命周期
JDK1.5之前:5种状态
JDK5.0之后:6种状态
线程安全机制
当多个线程访问同一资源(同一个变量、同一个文件、同一条记录)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程对资源有读和写操作,就容易出现线程安全问题。
方法一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
- 需要被同步的代码,即为操作共享数据的代码
- 共享数据,即多个线程都需要操作的数据
- 需要被同步的代码,在被synchronized包裹以后,就是的一个线程在操作这些代码过程中。其他线程必须等待。
- 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码
- 同步监视器,可以使用任何一个类的对象。但是,多个线程必须公用同一个同步监视器。
注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this
在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用当前类.class
继承Thread类中的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class WindowTest { public static void main(String[] args) {
SaleTicket w1 = new SaleTicket("窗口1"); SaleTicket w2 = new SaleTicket("窗口2"); SaleTicket w3 = new SaleTicket("窗口3"); w1.start(); w2.start(); w3.start(); } }
class SaleTicket extends Thread{ static int ticket = 1000; static Object obj = new Object(); @Override public void run() { while(true){ synchronized (SaleTicket.class) { if (ticket > 0) {
try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket); ticket--;
} else { break; }
} } }
public SaleTicket(String name) { super(name); } }
|
实现Runnable接口的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class WindowTest { public static void main(String[] args) { SaleTicket s = new SaleTicket(); Thread t1 = new Thread(s); Thread t2 = new Thread(s); Thread t3 = new Thread(s);
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
class SaleTicket implements Runnable{ int ticket = 1000; @Override public void run() { while(true){ synchronized (this) { if (ticket > 0) {
try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket); ticket--;
} else { break; }
} } } }
|
方式二:同步方法
说明:
继承Thread类中的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class WindowTest { public static void main(String[] args) {
SaleTicket w1 = new SaleTicket("窗口1"); SaleTicket w2 = new SaleTicket("窗口2"); SaleTicket w3 = new SaleTicket("窗口3"); w1.start(); w2.start(); w3.start(); } }
class SaleTicket extends Thread{ static int ticket = 1000; static boolean isFlag = true; @Override public void run() { while(isFlag){ show();
} } public static synchronized void show(){ if (ticket > 0) {
try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket); ticket--;
} else { isFlag = false; }
} public SaleTicket(String name) { super(name); } }
|
实现Runnable接口的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class WindowTest { public static void main(String[] args) { SaleTicket s = new SaleTicket(); Thread t1 = new Thread(s); Thread t2 = new Thread(s); Thread t3 = new Thread(s);
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
class SaleTicket implements Runnable{ int ticket = 1000; static boolean isFlag = true; @Override public void run() { while(isFlag){ show(); } } public synchronized void show(){ if (ticket > 0) {
try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket); ticket--; }else { isFlag=false; } } }
|
synchronized优缺点
好处:解决了线程问题
弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。
单例模式-懒汉式(线程安全解决版)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public class BankTest { static Bank b1 = null; static Bank b2 = null;
public static volatile void main(String[] args) { Thread t1 = new Thread(){ @Override public void run() { b1 = Bank.getInstance(); } };
Thread t2 = new Thread(){ @Override public void run() { b2 = Bank.getInstance(); } };
t1.start(); t2.start(); try { t1.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } try { t2.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(b1); System.out.println(b2); System.out.println(b1==b2); } }
class Bank { private Bank() { }
private static volatile Bank instance = null;
public static synchronized Bank getInstance() { try { if (instance == null) { Thread.sleep(1000); instance = new Bank(); } } catch (InterruptedException e) { throw new RuntimeException(e); } return instance; } public static Bank getInstance(){ if(instance ==null){ synchronized(Bank.class){ if(instance==null){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } instance = new Bank(); } } } return instance; } }
|
锁
除了使用synchronized同步机制处理线程安全问题之外,还可以使用jdk5.0提供的锁方式
步骤
- 创建Lock的实例,需要确保多个线程公用同一个Lock实例!需要考虑将此对象声明为static final
- 执行lock()方法,锁定对共享资源的调用
- Unlock()的调用,释放对共享资源的锁定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class WindowTest { public static void main(String[] args) {
SaleTicket w1 = new SaleTicket("窗口1"); SaleTicket w2 = new SaleTicket("窗口2"); SaleTicket w3 = new SaleTicket("窗口3"); w1.start(); w2.start(); w3.start(); } }
class SaleTicket extends Thread{ static int ticket = 1000; static boolean isFlag = true; private static final ReentrantLock lock =new ReentrantLock(); @Override public void run() { while(isFlag){ try { lock.lock(); show(); }finally { lock.unlock(); } } } public void show(){ if (ticket > 0) {
try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket); ticket--; } else { isFlag = false; } } public SaleTicket(String name) { super(name); } }
|
Lock与synchronized的区别
synchronized不管是同步代码块还是同步方法,都需要在结束一对{},释放对同步监视器的调用。
Lock是通过两个方法控制需要被同步的代码,更灵活一些。
Lock作为接口,提供了多种实现类,适合更多更复杂,效率更高。
通信
线程间通信的理解
当我们’需要多个线程’来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信可以协调它们的工作,以此实现多线程共同操作一份数据。
涉及到三个方法的使用
wait():线程一旦执行此方法,就进入等待状态,同时会释放对同步监视器的调用。
notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程优先级相同就随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
注意:
- 此三个方法的使用,必须是在同步代码块或同步方法中。
- 此三个方法的调用者,必须是同步监视器。否则会报IllegalMontiorStateException异常
- 此三个方法声明在Object类中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class PrintNumberTest { public static void main(String[] args) { PrintNumber p = new PrintNumber(); Thread t1 = new Thread(p,"线程1"); Thread t2 = new Thread(p,"线程2"); t1.start(); t2.start(); } }
class PrintNumber implements Runnable{ private int number =1;
@Override public void run() { while(true){ synchronized (this) { notify(); if(number<=100){
try { Thread.sleep(10); } catch (Exception e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+":"+number); number++;
try { wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }else { break; } } } } }
|
wait()和sleep()的区别
相同点:一旦执行,当前线程都会进入阻塞状态
不同点:
声明位置不同:wait():声明在Object类中
sleep()声明在Thread类中,静态的
使用场景不同:wait():只能使用在同步代码块或同步方法中
sleep():可以在任何需要使用的场景
使用在同步代码块中:wait()一旦执行会释放同步监视器
sleep()一旦执行,不会释放
结束阻塞的方式:wait()到达指定时间自动结束阻塞或通过被notify唤醒,结束阻塞
sleep():到达指定时间自动结束阻塞。
消费者与生产者练习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| public class ProducerConsumerTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer pro1 = new Producer(clerk); Consumer con1 = new Consumer(clerk); pro1.setName("生产者1"); con1.setName("消费者"); pro1.start(); con1.start();
} }
class Clerk{ private int ProductNum = 0;
public synchronized void addProduct(){ if(ProductNum >= 20){ try { wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } ProductNum++; System.out.println(Thread.currentThread().getName()+"生产了第"+ProductNum+"个产品"); notify(); }
public synchronized void minusProduct(){ if(ProductNum<=0){ try { wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }else { ProductNum--; System.out.println(Thread.currentThread().getName()+"消费了第"+ProductNum+"个产品"); notify(); }
} }
class Producer extends Thread{ private Clerk clerk; public Producer(Clerk clerk){ this.clerk = clerk; } @Override public void run() { while (true) { System.out.println("生产者生产产品"); try{ Thread.sleep(20); }catch (InterruptedException e){ e.printStackTrace(); } clerk.addProduct(); } } }
class Consumer extends Thread{ private Clerk clerk; public Consumer(Clerk clerk){ this.clerk = clerk; }
@Override public void run() { while (true) { System.out.println("生产者生产产品"); try{ Thread.sleep(50); }catch (InterruptedException e){ e.printStackTrace(); } clerk.minusProduct(); } } }
|
线程创建方式三-Callable()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class NumThread implements Callable { @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 CallableTest{ public static void main(String[] args) { NumThread numThread = new NumThread();
FutureTask futureTask = new FutureTask(numThread);
Thread t1 = new Thread(futureTask); t1.start();
try { Object sum = futureTask.get(); System.out.println("总和为" + sum); } catch (Exception e) { e.printStackTrace();
} } }
|
与Runnable方式的对比
- call()可以有返回值,更灵活
- call()可以使用throws方式处理异常,更灵活
- Callable使用了泛型参数。可以指明具体的call()的返回值类型,更灵活
缺点:
如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的
线程创建方式四-使用线程池
此方式的好处:
- 提高了程序执行的效率(因为线程已经提前创建好了)
- 提高了资源的复用率(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
- 可以设置相关的参数,对线程池的线程的使用进行管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class ThreadPool { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setMaximumPoolSize(50);
service.execute(new NumberThread()); service.execute(new NumberThread1());
service.shutdown(); } }
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); } } } }
|
常见问题
1.线程和进程相关定义
2.多线程使用场景
- 手机app应用图片的下载
- 迅雷的下载
- Tomcat服务器上web应用,多个客户端发起请求,Tomcat针对多个请求开辟多个线程
3.如何实现多线程
4.Thread类中的Start()和run()的区别?
start():1.开启线程 2.调用线程的run()
5.sleep()和yield()区别?
sleep():一旦调用,就进入阻塞状态(或TiMED_WAITING状态)
yield():释放cpu的执行权,处在runnable的状态
6.线程生命周期
ex:线程有哪些状态,如何让线程进入阻塞
ex线程有几个状态,就绪和阻塞有什么不同
7.线程优先级怎么定义
8.stop()和suspend()方法为何不推荐使用
stop():一旦执行,线程就结束了。导致run()有未执行结束的代码。stop()会导致释放同步监视器,导致线程安全
suspend():与resume()搭配使用,导致死锁
9.线程安全的理解,线程安全如何造成的
10.多线程公用一个数据变量需要注意什么
11.多线程保证线程安全一般有几种方式
同步机制
Lock接口
12.用什么关键字修饰同步方法?
13.synchronized加在静态方法和普通方法的区别
同步监视器不同。静态:类本身 非静态:this
14.当一个线程进入一个对象的一个synchronized方法后,其他线程是否可以进入此对象的其他方法?
需要看其他方法是否使用synchronized修饰,同步监视器的this是否是同一个。
只有当使用了synchronized且this是同一个的情况下,就不能访问
15.线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
同步一定阻塞。 阻塞不一定同步
16.wait和notify方法为什么一定要在同步块中调用?
调用者必须是同步监视器。
17.生产者、消费者问题
18.wait和sleep的区别
19.手写单例模式。