15、线程
一、线程(Thread)——进程(process)
线程是程序类的控制流,线程和进程非常相似。
多进程:在操作系统能同时运行多个任务(程序、软件)
多线程:在同一个应用程序中有多个顺序流任务同时执行。
二、Thread类和Runnable接口
Thread实现了Runnable接口
在A1类下继承Thread类,重写run方法之后的单线程串行运行:
public class A { public static void main(String[] args) { new A1().run(); new A1().run(); } } class A1 extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(i); } } }
多线程方法:
1、使用Thread类下的start()方法可以实现简单的多线程(创建两个对象),Thread.currentThread()指当前线程:sleep()方法可以使当前线程睡眠指定毫秒数后唤醒。
public class A {
public static void main(String[] args) {
A1 a1=new A1();
a1.setName("A");
a1.start();
A1 a2=new A1();
a2.setName("B");
a2.start();
}
}
class A1 extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++){
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+i);
}
}
}
2、使用Runnable接口,向Thread构造器传入实现了Runnable接口的对象:
public class A { public static void main(String[] args) { A2 a1=new A2(); Thread t1=new Thread(a1); t1.setName("A");//如果不设置名字,将按照JDK给定的Thread1和Thread2来命名 t1.start(); A2 a2=new A2(); Thread t2=new Thread(a2); t2.setName("B"); t2.start(); } } class A2 implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+i); } } }
3、也可以使用匿名内部类:(即用即调)
public class B { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + i); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + i); } } }).start(); } }
4、使用FutureTask类,因为FutureTask类实现了Runnable接口,所以,间接地实现了Runnable接口。使用回调式接口,可以返回值。
FutureTask类实现Runnable接口,而FutureTask构造器中需要的是Callable接口对应的对象,所以创建Callable接口对应的对象,传递给FutrueTask,创建一个FutrueTask对象,然后将这个实现了Runnable接口的对象传给Thread类的构造器,实现线程创建。
public class C { public static void main(String[] args) { C1 c1=new C1(); FutureTask<Integer> task=new FutureTask(c1); Thread t=new Thread(task); t.start(); try { System.out.println("总计是"+task.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } C1 c2=new C1(); FutureTask<Integer> task2=new FutureTask(c2); Thread t2=new Thread(task2); t2.start(); try { System.out.println("总计是"+task.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class C1 implements Callable<Integer>{ @Override public Integer call() throws Exception { int sum=0; for(int i=0;i<=100;i++){ sum+=i; } return sum; } }
注意:1、start方法不要重复使用,一旦线程start,必须执行完或中止才会执行下一个start
2、stop方法可以使线程急刹车。
三、三种基本创建线程的区别
--、使用Runable接口
可以将线程代码和线程数据分开,形成清晰的模型。
可以继承其他类。
--、直接继承Thread类
不能再从其他类继承;
编写简单,可以直接操纵线程。
1、使用Callable接口规定的方法是call(),而Runnable规定的方法是run()。
2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
3、call()方法可抛出异常,而run()方法是不能抛出异常的。
4、运行Callable任务可拿到一个Future对象,Future表示异步计算的结果。
它提供了检查计算时候完成的方法,以等待计算的完成,并检索计算的结果。
通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
Thread类构造器中填入的是Runnable接口实现类对象;FutureTask类构造器中填入的是Callable接口实现类对象。而FutureTask类实现了Runnable接口,这样就可以间接地使用Thread构造器调用FutureTask类的对象创建线程了。
四、线程池创建线程
通过Executor来启动线程比用Thread的start()要好。在新特性中,可以很容易控制线程的启动、执行和关闭过程,还可以很容易使用线程池的特性。
1)、减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2)、可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开越多,消耗的内存越大,最后死机)
Executors下的方法:newFixedThreadPool方法是创建n个线程的线程池
newSingleThreadExecutor方法是创建单个线程
newCacheThreadPool()方法是缓存线程池,以最快速度创建线程池,不管创建多少内存。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * Created by Administrator on 2018/2/10 0010. */ public class D { public static void main(String[] args) { ExecutorService service= Executors.newFixedThreadPool(8); for(int i=0;i<100;i++){ service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+"启动线程"); } }); } service.shutdown(); try { service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); System.out.println("线程结束"); } catch (InterruptedException e) { e.printStackTrace(); } } }
未来式、Callable接口、线程池混合编写多线程
线程池下的两个方法:
submit方法形参是callable回调式接口对象,返回未来式(下面的例子)
execute方法的形参是Runnable接口对象(上面的例子)
import java.util.concurrent.*; /** * Created by Administrator on 2018/2/10 0010. */ public class E { public static void main(String[] args) { ExecutorService service= Executors.newFixedThreadPool(10); for(int i=0;i<20;i++){ Future<String> futrue= service.submit(new E1(i)); try { System.out.println(futrue.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } service.shutdown(); try { service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("结束"); } } class E1 implements Callable<String>{ public E1(){} private int id; public E1(int i){ this.id=i; } @Override public String call() throws Exception { return Thread.currentThread().getName()+"你好"+id; } }
五、线程状态
状态图
线程可以一直执行下去,直到下面的某件事情发生才停止。
-明确地从目标run()方法返回,run方法结束。
-遇到一个无法捕获的运行时异常。
-调用了不推荐使用的stop()、Interrupt()方法。
如果上面事情都没发生,run()方法就会一直运行下去,甚至在创建他的程序结束之后,线程还会一直运行。
线程优先级设立:
Runnable下对象使用setPriority方法,其中包含三个等级的默认优先级:MAX(10)、NORMAL、MIN(1)
可以通过yield()方法明确地让出当前线程的控制权,通过完成他的目标方法或调用stop()方法终止线程。
六、join线程
join线程:Thread提供了让一个线程等待另一个线程完成的方法:join()方法,当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join方法加入的join线程完成为止。
该线程可以实现将一个大问题划分为若干小线程逐击破的想法。程序可以由三个线程组成:主线程、新线程、join线程,开始时候主线程和新线程交叉允许,当主线程的循环变量i等于20时,启动了join线程,该线程不会和主线交叉运行,而主线程必须等到该线程结束后才可以向下执行,但是由于没有控制新线程,所以新线程将还和join线程一同并发执行,而仅仅是主线程处于等待状态。
package com.zxc.Q; /** * Created by Administrator on 2018/2/10 0010. */ public class G { public static void main(String[] args) { G1 g1=new G1(); g1.setName("G1:"); g1.start(); } } class G1 extends Thread{ public void run() { for(int i=0;i<200;i++){ if(i==50){ G2 g2=new G2(); g2.setName("G2:"); g2.start(); try { g2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+i); } } } class G2 extends Thread{ public void run() { for(int i=0;i<200;i++){ System.out.println(Thread.currentThread().getName()+i); } } }
七、守护线程
在多数情况下,实际上先创建的是一个能够在应用程序中做一些简单的、周期性任务的后台线程。可以调用setDaemon()方法标记一个线程是守护线程。
下面的例子是h1对象为守护线程,守护h2,h3线程。
public class H { public static void main(String[] args) { H1 h1=new H1(); h1.setName("H1"); h1.setDaemon(true); h1.start(); H2 h2=new H2(); h2.setName("H2"); h2.start(); H2 h3=new H2(); h3.setName("H3"); h3.start(); } } class H1 extends Thread{ @Override public void run() { for(int i=0;i<1000;i++){ try { sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+i); } } } class H2 extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
七、总结方法
Runnable Thread start stop interrupt sleep join
Executorservice Exectors submit Callable executor shutdown
awaitTermination
newFixedThreadPool newSingleThreadPool newCacheThreadPool FutureTask Future
yield setPriority notify notifyall wait
中断异常的三个:wait sleep interrupt
八、Sychronized同步(效率低)
银行取钱问题:******用Sychronized来锁定方法并不安全,最好用来锁定对象线程。
//Account账户类 public class Account { private String accountNo; private double balance; public Account(){} public Account(String accountNo , double balance) { this.accountNo = accountNo; this.balance = balance; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public String getAccountNo() { return this.accountNo; } public void setBalance(double balance) { this.balance = balance; } public double getBalance() { return this.balance; } } //DrawAccount 存款类 public class DrawAccount extends Thread{ private Account account; private double drawAmount; public DrawAccount(String name,Account account,double drawAmount){ super(name); this.account=account; this.drawAmount=drawAmount; } @Override public void run() { synchronized (this.account) { //给this.account上锁 if (account.getBalance() >= drawAmount) { account.setBalance(account.getBalance() - this.drawAmount); System.out.println(this.getName() + "取了" + this.drawAmount + "你的余额是:" + account.getBalance()); } else { System.out.println("余额不足"); } } } } //TestDraw测试类 public class TestDraw { public static void main(String[] args) { Account acct=new Account("NO123456789",800); new DrawAccount("甲",acct,800).start(); new DrawAccount("乙",acct,800).start(); } }
九、synchronized和volatile和Lock区别
volatile关键字:保证内存的可见性
在每次访问变量时都会进行一次刷新,因此每次访问都是主内存中最新版本。所以volatile作用之一是保证变量修改的实时可见性。
并不保证原子性
防止指令重排
sychronized比volatile更安全,而后者比前者效率高,后者是轻量级的同步机制,前者为重量级的同步机制。有了sychronized就不需要用voaltile了。前者可以保证原子性和可见性,而后者不能保障原子性,从而就不能保证完全锁定。用volatile修饰的变量不能再更改:count++、count+=1等都不能;
论锁定功效:Lock>sychronized>volatile
Lock:
lock和Synchronized的区别: