1.异常
1.1 异常处理
Scanner input = new Scanner(System.in);
try {
System.out.print("被除数:");
int num1 = input.nextInt();
System.out.print("除数:");
int num2 = input.nextInt();
System.out.println(num1/num2);
}catch(InputMismatchException e){
//整数
System.out.println("请输入整数");
//return;//还会执行finally
}catch(ArithmeticException e){
System.out.println("除数不能为零");
//System.exit(0);//不会执行finally
}catch(Exception e) {
//所有异常
System.out.println("异常");
}finally{
//最终执行的代码
System.out.println("finally");
}
//多重catch子类在前父类在后
//finally:无论如何都会执行的代码,
//---可靠的资源释放---连接
//return 会让finally执行,return是结束当前方法,
//其他方法继续执行,需要释放其他资源
//System.exit(0):退出虚拟机,所有方法都不执行,没有资源可以释放
throws //声明异常
//final finally finalize
//先把异常捕获,然后抛出异常---业务异常
//异常
try,catch,finally,throw,throws
执行正常代码,捕获异常,释放资源,抛出异常,声明异常
1.2自定义异常
//类方法
public class UserEcp extends UserException{
private String message;
//名字为message可以异常输出,继承了父类
/**
父类方法
public String toString() {
String s = getClass().getName();
String message = getLocalizedMessage();
return (message != null) ? (s + ": " + message) : s;
}
*/
public UserEcp(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
//执行代码
public class TestSex {
public static void main(String[] args) {
try {
ecp();
} catch (UserEcp e) {
e.printStackTrace();
//System.out.println(e.getMessage());
}
System.out.println("结束");
}
public static void ecp() throws UserEcp{
Scanner input = new Scanner(System.in);
System.out.println("请输入性别:");
char gender = input.next().charAt(0);
if(gender=='男'||gender=='女') {
Student s = new Student("coco", gender);
System.out.println(s.toString());
}else {
throw new UserEcp("请输入正确的性别");
}
}
}
2.多线程
2.1进程和线程
进程:
独立的应用程序,有独立内存空间
线程:
一个进程多个线程组合在一起
Thread main = Thread.currentThread();//当前线程
main.setName("主线程");
System.out.println(main.getId());//线程编号
System.out.println(main.getName());//线程名字
main.setPriority(Thread.MAX_PRIORITY);//1-10 优先级范围是1-10,默认是5
System.out.println(main.getPriority());//线程优先级
//main.setDaemon(false);//自己创建的其他线程可以设为守护线程,但是线程不可以
System.out.println(main.getState());//线程状态--RUNNABLE
System.out.println(main.isAlive());//线程是否还活着
System.out.println(main.isDaemon());//是否是守护线程
System.out.println(main.hashCode());//hashcode
//注意这个不是单线程程序,只能说只有一个主线程,还有很多守护线程
while(true) {
}
2.2 为什么要编写多线程
1.让cpu更好的工作
2.多线程可以简化编程模型
2.3 如何编写多线程程序
创建线程四种方案:
继承类
public class MyThread extends Thread{
public void run() {
for(int i=1;i<10;i++) {
System.out.println(Thread.currentThread()+"线程----"+i);
}
}
}
//
public class TestMythread {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
}
实现Runnable接口
public class MyThread implements Runnable{
public void run() {
for(int i=1;i<10;i++) {
System.out.println(Thread.currentThread()+"线程----"+i);
}
}
}
继承Thread
public class TestMythread {
public static void main(String[] args) {
MyThread mth = new MyThread();
Thread th = new Thread(mth);
th.start();
}
}
2.4线程的生命周期
1.阻塞之后不是进到运行状态,而是就绪状态,等资源
2.一个线程只能start一次,启动之后只能等消失
2.5线程调度
1.setPriority(1-10)通过设值来设置线程执行概率
2.Thread.sleep():线程休眠
3.线程礼让:Thread.yeild()
4.线程加入:th.join() :让这个线程执行完再执行被阻塞的线程
5.线程中断:
public class MyThread extends Thread{
public void run() {
//任务是死循环,不断检测中断信号,别人发中断信号了,我们就结束循环
while(true) {
if(this.isInterrupted()==true) {//检测中断信号
break;
}
}
}
}
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
MyThread th = new MyThread();
th.start();
System.out.println("是否中断?y/n");
char choice = input.next().charAt(0);
if(choice=='y') {
//调用线程中断的代码
th.interrupt();//中断线程---不能改变线程目前运行状态,只能改变线程的一个中断标记
}
}
}
守护线程:
如果不是守护线程,run方法要执行完才能退出虚拟机
是守护线程,其余的非守护线程执行完毕之后就会退出虚拟机
serDaemon(true):设置线程为守护线程
2.6 Callable
1.计算一个包含一万个元素数组,多线程并行计算数组元素的和
方案一 Runnable
缺陷:无返回值,不能抛异常
//任务类
public class ArrayThread implements Runnable{
int[] nums;
int sum = 0;
public void run() {
for(int i=0;i<nums.length;i++) {
sum += nums[i];
}
}
//构造方法来接收任务数组
public ArrayThread(int[] nums) {
super();
this.nums = nums;
}
}
//循环计算,创建十个线程
public class Test2 {
public static void main(String[] args) throws InterruptedException {
int[] nums = new int[10000];
for (int i = 0; i < nums.length; i++) {
nums[i] = i + 1;// 1-10000-----50005000
}
int sum =0;
int count= 10;
Map<Thread, ArrayThread> map = new HashMap<Thread, ArrayThread>();
for(int i=0;i<count;i++) {
int[] num =new int[nums.length/count];
for(int j=0;j<num.length;j++) {
num[j]=nums[j+nums.length/count*i];
}
ArrayThread st = new ArrayThread(num);
Thread th = new Thread(st);
th.start();//启动会自动计算第i个数组的结果
map.put(th,st);
}
for(Thread th:map.keySet()) {
th.join();//效率取决与最慢的线程
sum+=map.get(th).sum;
}
System.out.println(sum);
}
}
方案二 Callable
//类方法
public class ArrayThread2 implements Callable<Integer>{
int[] nums;
public Integer call() {
int sum = 0;
for(int i=0;i<nums.length;i++) {
sum += nums[i];
}
return sum;
}
public ArrayThread2(int[] nums) {
super();
this.nums = nums;
}
public ArrayThread2() {
}
}
//计算
public class Test2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
int[] nums = new int[10000];
for (int i = 0; i < nums.length; i++) {
nums[i] = i + 1;// 1-10000-----50005000
}
int sum =0;
int count= 10;
List<FutureTask> fts = new ArrayList<FutureTask>();
for(int i=0;i<count;i++) {
//分配任务
int[] num =new int[nums.length/count];
for(int j=0;j<num.length;j++) {
num[j]=nums[j+nums.length/count*i];
}
ArrayThread2 st = new ArrayThread2(num);
//把Callable伪装成Runnable
FutureTask<Integer> ft = new FutureTask<Integer>(st);
Thread th = new Thread(ft);
th.start();
fts.add(ft);
}
for(FutureTask<Integer> ft:fts) {
sum+=ft.get(); //算完一个给一个,等到call方法执行
}
System.out.println(sum);
}
}
2.7 线程安全
铁道部发布了一个售票任务,要求销售1000张票,要求有3个窗口来进行销售,请编写多线程程序来模拟这个卖票效果
i. 窗口001正在销售第1000张票
ii. 窗口001正在销售第999张票
iii. 窗口002正在销售第998张票
iv. 。。。
v. 窗口002正在销售第1张票
vi. 票已经销售完毕
线程任务:
总票数-1,编号+1,窗口不断卖票
使用同步代码块
synchronized加锁
//方法类一
public class SaleTicke implements Runnable{
private String name;
int num = 10;
int count =0;
@Override
public void run() {
while(true) {
synchronized (this) {
if (num <= 0) {
System.out.println(Thread.currentThread().getName()+"售罄");
break;
}
num--;
count++;
System.out.println(Thread.currentThread().getName()+"正在销售第"+count+"张票,还剩"+num+"张");
}
}
}
}
//方法类二,同步方法
public class SaleMethods implements Runnable{
private String name;
int num = 10;
int count =0;
boolean flag = true;
@Override
public void run() {
while(flag) {
sale();
}
}
public synchronized void sale() {
synchronized (this) {
if (num <= 0) {
System.out.println(Thread.currentThread().getName()+"售罄");
flag = false;
return;
}
num--;
count++;
System.out.println(Thread.currentThread().getName()+"正在销售第"+count+"张票,还剩"+num+"张");
}
}
}
//执行方法
public class Test {
public static void main(String[] args) {
SaleTicke st = new SaleTicke();
// SaleMethods st = new SaleMethods();
Thread th1 = new Thread(st,"窗口001");
Thread th2 = new Thread(st,"窗口002");
Thread th3 = new Thread(st,"窗口003");
th1.start();
th2.start();
th3.start();
}
}
Lock加锁会导致效率降低
2.8 线程不安全
2.8.1 懒汉单例不安全
//懒汉单例模式
public class Student {
private Student() {}
//private static Student s=new Student();//饿汉模式,同一个对象
private static Student s = null;
public static Student getInstance() {
if(s==null) {
s = new Student();
}
return s;
}
}
public static void main(String[] args) {
for(int i=1;i<=3;i++) {
new Thread(new Runnable() {
public void run() {
System.out.println(Student.getInstance());
}
}).start();
}
}
2.8.2 ArrayList是线程不安全的
public class TestArrayList {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> list = new ArrayList<>();
Thread th1 = new Thread(new Runnable() {
public void run() {
list.add("a");
}
});
Thread th2 = new Thread(new Runnable() {
public void run() {
list.add("b");
}
});
Thread th3 = new Thread(new Runnable() {
public void run() {
list.add("c");
}
});
th1.start();
th2.start();
th3.start();
th1.join();
th2.join();
th3.join();
System.out.println(list);
}
}
会出现以下情况:
[b,a] [a,c],[null, c, a] [null,c] [null,null,c] [b] ...
//
Vector保证线程安全是通过所有代码都加synchronized
CopyOnWriteArrayList:线程安全的类,集合需要高并发
Hashtable---synchronized
HashMap---为同步---不安全
ConcurrentHashMap---安全
2.9 死锁
死锁:当线程需要多个资源的时候,一个线程获取到一部分资源,另外一个线程获取到另外一部分资源,这时候就会产生死锁
案例:
//类方法
public class Dead implements Runnable{
//设计两个资源
Integer a;//第一把锁
Integer b;//第二把锁
public Dead(Integer a,Integer b) {
this.a = a;
this.b = b;
}
public void run() {
synchronized (a) {
System.out.println(Thread.currentThread().getName()+"获取到"+a);
synchronized (b) {
System.out.println(Thread.currentThread().getName()+"获取到"+b);
System.out.println(a+"+"+b+"="+(a+b));
}
}
}
}
//执行方法
public class TestDead {
public static void main(String[] args) {
Dead d1 = new Dead(1,2);//a=1,b=2
Dead d2 = new Dead(2,1);//a=2,b=1
new Thread(d1,"线程1").start();
new Thread(d2,"线程2").start();
}
}
2.10 线程池
2.10.1 池的概念
线程池:装线程的池子
连接池:装连接的
为什么需要线程池:
线程最大的缺点就是:开启之后运行完就没了
2.10.2 自定义线程池的原理
自定义线程池
Executor
ThreadPoolExecutor
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
/*
int corePoolSize,--------核心线程个数
int maximumPoolSize,-----最大线程个数
long keepAliveTime,------保持存活时间
TimeUnit unit,-----------时间单位
BlockingQueue<Runnable> workQueue---工作队列--任务队列
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4));
pool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"--执行任务");
}
});
TreadFactory
2.10.3 自动创建线程池
1、ExecutorService pool = Executors.newSingleThreadExecutor();//创建一个单线程的线程池
new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
//特点:只有一个线程,队列无限大
public class TestPool2 {
public static void main(String[] args) {
ExecutorService pool = Executors.newSingleThreadExecutor();//创建一个单线程的线程池
//队列无限大,当任务很多,队列一直放,就会导致内存溢出
for (int i = 0; i < Integer.MAX_VALUE; i++) {
int temp = i;
pool.execute(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--"+temp);
}
});
}
}
}
2.ExecutorService pool = Executors.newFixedThreadPool(10);//创建一个单线程的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//特点:核心线程=最大线程=n,队列无线
public class TestFix {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);//创建一个单线程的线程池
for (int i = 1; i <=1000; i++) {
int temp = i;
pool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"----"+temp);
}
});
}
}
}
3、ExecutorService pool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//特点:核心线程为0,最大线程无线,60s回收无任务的线程,队列是同步队列(自身没有缓存功能)
public class TestCached {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();//
for (int i = 1; i <=1000; i++) {
int temp = i;
pool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"----"+temp);
}
});
}
}
}
4、定时执行的线程池
public class TestSchedule {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);
//后台定时器--以固定的频率
pool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("重复执行的代码");
}
}, 2, 1, TimeUnit.SECONDS);//第一次等两秒,后面每秒执行run里面的代码
}
}
/**
* 核心线程和最大线程指定,队列是可以按照任务等待时间排序,可以实现任务的循环调用
* @author Administrator
*
*/
2.10.4 源码分析
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//true代表核心线程以内
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);//false代表超过核心线程
}
else if (!addWorker(command, false))//开最大线程失败
reject(command);
}
核心点:
会new线程池,同时知道这些参数之间的关系
2.11 ThreadLocal
1-1000个时间需要输出来:一个时间需要单独用一个线程来完成输出。
/**
* 第一种方式
* @author Administrator
*
*/
public class Test {
public static void main(String[] args) {
for (int i = 1; i <=1000; i++) {
int temp = i;
new Thread(new Runnable() {
public void run() {
Date date = new Date(temp*1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date));
}
}).start();
}
}
}
/**
* 第二种:使用线程池来解决线程的开闭和开销
* @author Administrator
*
*/
public class Test1 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 1; i <=1000; i++) {
int temp =i;
pool.execute(new Runnable() {
public void run() {
Date date = new Date(temp*1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(Thread.currentThread().getName()+"--"+sdf.format(date));
}
});
}
pool.shutdown();
}
}
/**
* 第三种:把格式化的过程放到工具类中
* 问题sdf创建的太多了
* @author Administrator
*
*/
public class Test2 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i =1; i <=1000; i++) {
int temp =i;
pool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"--"+DateUtil.getDate(temp));
}
});
}
pool.shutdown();
}
}
class DateUtil{
public static String getDate(int i) {
Date date = new Date(i*1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
/**
* 第四种
* 把sdf设计为一个对象
* @author Administrator
*问题:线程不安全
*一个类如果没有成员变量,这个类一定是安全的
*做成成员变量就被所有的线程所共享,因此就不安全
*/
public class DateUtil2 {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String getDate(int i) {
Date date = new Date(i*1000);
return sdf.format(date);
}
}
/**
* 第五种:加锁
* 加锁实现了安全
* 问题: 所有的线程所有任务都会在格式化这行代码阻塞,效率会受到影响
* @author Administrator
*
*/
public class DateUtil3 {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String getDate(int i) {
Date date = new Date(i*1000);
String str=null;
synchronized (DateUtil3.class) {
str = sdf.format(date);
}
return str;
}
}
/**
* 第六种:使用ThreadLocal来做线程隔离,每个线程设置一个SimpleDateFormat
* @author Administrator
*
*/
public class DateUtil4 {
// private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};//创建一个匿名内部类
public static String getDate(int i) {
Date date = new Date(i*1000);
String str=null;
str = tl.get().format(date);
System.out.println(Thread.currentThread().getName()+"--"+System.identityHashCode(tl.get()));
return str;
}
}
2.12 ThreadLocal源码分析
public T get() {
//每个线程执行get方法的时候获取线程对象
Thread t = Thread.currentThread();//获取当前线程对象----
//获取ThreadLocalMap对象---入参是当前对象--根据当前线程获取一个map
ThreadLocalMap map = getMap(t);
if (map != null) {
//如果这个map里面有值,就从map中取给你
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//另外的渠道给你
return setInitialValue();
}
getMap(t):
ThreadLocalMap getMap(Thread t) {
//Thread类:
//ThreadLocal.ThreadLocalMap threadLocals = null;
return t.threadLocals;//t-当前线程 threadLocals:ThreadLocalMap类型的成员变量
}
//setInitialValue
private T setInitialValue() {
//这个方法是我们在new ThreadLocal对象的时候用了一个匿名内部类重写他
//第一次map里面没有这个值的这个时候会调用这个这个方法,如果后面这个线程再来获取
//会直接从map里面给他
T value = initialValue();//调到我们重写的方法---sdf
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取线程里面的map集合
if (map != null) {
map.set(this, value);//map已经有了,一定是已经放了其他的tl对象进去
} else {
createMap(t, value);//创建一个ThreadLocalMap threadLocals
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
void createMap(Thread t, T firstValue) {
//当前线程的map集合在这里创建---键是ThreadLocal对象,值是sdf
t.threadLocals = new ThreadLocalMap(this, firstValue);//this tl
}
set方法:修改ThreadLocal里面的值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
remove方法:删除键值对
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}