1:线程(理解)
(1)死锁
概念:
同步中,多个线程使用多把锁之间存在等待的现象。
原因分析:
a.线程1将锁1锁住,线程2将锁2锁住,而线程1要继续执行锁2中的代码,线程2要继续执行锁1中的代码,
但是此时,两个锁均处于锁死状态。最终导致两线程相互等待,进入无限等待状态。
b.有同步代码块的嵌套动作。
解决方法:
不要写同步代码块嵌套。
例子:cn.itcast2.DeadLockThread; cn.itcast2.demo
package cn.itcast2; public class DeadLockThread extends Thread { boolean flag;//定义标记,用来指定要执行的代码 public DeadLockThread(boolean flag) { this.flag = flag; } @Override public void run() { if(flag) {//flag赋值为true时,执行的代码 synchronized (Demo.LOCK1) { System.out.println("if中锁1"); /*try { //加上睡眠时间,就不正常了,有可能会出现死锁现象 sleep(100l); } catch (InterruptedException e) { e.printStackTrace(); }*/ synchronized (Demo.LOCK2) { System.out.println("if中锁2"); } } } else {//flag赋值为false时,执行的代码 try { //加上睡眠时间,等线程1执行完,就正常了,不会出现死锁现象 sleep(100l); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (Demo.LOCK2) { System.out.println("else中锁2"); synchronized (Demo.LOCK1) { System.out.println("else中锁1"); } } } } }
package cn.itcast2; /* * 线程死锁: * 线程锁相互等待 */ public class Demo { public static final Object LOCK1 = new Object(); public static final Object LOCK2 = new Object(); public static void main(String[] args) { DeadLockThread dlt = new DeadLockThread(true); DeadLockThread dlt2 = new DeadLockThread(false); dlt.start(); dlt2.start(); } }
(2)JDK5的新特性:Lock锁
它把什么时候获取锁,什么时候释放锁的时间给明确了。
例子:cn.itcast.demo3 cn.itcast.MyTicket
package cn.itcast; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * Lock锁: * 就是实现同步的另外的一种锁。另外的一种同步操作。 * Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 * * 子类对象:ReentrantLock * * public void lock() 上锁 * public void unlock() 解锁 */ public class Demo3 { public static final Object MY_LOCK = new Object(); public static final Lock MY_LOCK2 = new ReentrantLock(); public static void main(String[] args) { MyTicket mt = new MyTicket(); Thread thread = new Thread(mt,"唐嫣"); Thread thread2 = new Thread(mt,"柳岩"); Thread thread3 = new Thread(mt,"高圆圆"); thread.start(); thread2.start(); thread3.start(); } }
package cn.itcast; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyTicket implements Runnable { private int number = 100; Lock lock = new ReentrantLock(); @Override public void run() { //卖票的动作 while(true) { // synchronized(Demo3.MY_LOCK){ lock.lock(); // Demo3.MY_LOCK2.lock(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if(number>0) { System.out.println(Thread.currentThread().getName()+":"+number--); } lock.unlock(); // Demo3.MY_LOCK2.unlock(); // } } } }
(3)线程间的通信(生产者与消费者问题)
不同种类的线程针对同一个资源的操作。生产者线程生产与消费者线程消费,操作同一数据。
例子:cn.itcast3.*
package cn.itcast3; public class CRunnable implements Runnable { Person p; public CRunnable(Person p) { this.p = p; } @Override public void run() { while (true) { synchronized (p) { //c System.out.println(p); } } } }
package cn.itcast3; /* * 生产者消费者问题: * * 共享数据:一个Person对象 * 生产者:为该对象属性赋值 * 消费者:获取对象属性值 * * 采取第二种方式完成。 */ public class Demo { public static void main(String[] args) { Person person = new Person(); //生产线程运行目标 PRunnable p = new PRunnable(person); //消费线程运行目标 CRunnable c = new CRunnable(person); //创建生产线程 Thread pThread = new Thread(p); //创建消费线程 Thread cThread = new Thread(c); //开启消费与生产 pThread.start(); cThread.start(); } }
package cn.itcast3; /* * 被共享的对象对应的类 */ public class Person { private String name; private int age; public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
package cn.itcast3; /* * 生产者线程执行目标 */ public class PRunnable implements Runnable { Person p; int x = 0; public PRunnable(Person p) { this.p = p; } @Override public void run() { while(true) { synchronized (p) { if (x % 2 == 0) { p.setName("唐嫣"); p.setAge(28); } else { p.setName("刘正风"); //p p.setAge(29); } x++; } } } }
(4)等待唤醒机制
涉及并非是Thread类的方法,而是Object类的两个方法:因为锁可以为共享数据本身可以是任意的对象,在runnable中进行等待唤醒当前所在线程。
等待:
public final void wait() throws InterruptedException
让当前线程进入等待状态,如果线程进入该状态,不唤醒或打断,不会解除等待状态。
进入等待状态时会释放锁。
唤醒:
public final void notify()
唤醒正在等待的线程。
继续等待之后的代码执行。
sleep与wait的区别:
sleep指定时间,wait可指定可不指定。
sleep释放执行权,不释放锁。因为一定可以醒来。
wait释放执行权与锁。
例子:cn.itcast4.*
package cn.itcast4; /* * 消费者线程执行目标 */ public class CRunnable implements Runnable { Person p; public CRunnable(Person p) { this.p = p; } // 判断>>生产/消费>>修改标志位>>唤醒 @Override public void run() { while (true) { synchronized (p) { // 判断是否有数据 if (!p.flag) {// 如果没有数据,消费者等待 try { p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 消费 // System.out.println(p); System.out.println(p.getName() + ":" + p.getAge()); // 将数据标志位,置为没有值的false p.flag = false; // 唤醒正在等待的其他线程 p.notify(); } } } }
package cn.itcast4; /* * 生产者消费者问题: * * 共享数据:一个Person对象 * 生产者:为该对象属性赋值 * 消费者:获取对象属性值 * * 采取第二种方式完成。 */ public class Demo { public static void main(String[] args) { Person person = new Person(); //生产线程运行目标 PRunnable p = new PRunnable(person); //消费线程运行目标 CRunnable c = new CRunnable(person); //创建生产线程 Thread pThread = new Thread(p); //创建消费线程 Thread cThread = new Thread(c); //开启消费与生产 pThread.start(); cThread.start(); } }
package cn.itcast4; /* * 被共享的对象对应的类 */ public class Person { private String name; private int age; //标记是否有数据。true为有数据,需要消费。false没有数据,需要生产。 boolean flag = false; public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
package cn.itcast4; /* * 生产者线程执行目标 */ public class PRunnable implements Runnable { Person p; int x = 0; public PRunnable(Person p) { this.p = p; } // 判断>>生产/消费>>修改标志位>>唤醒 @Override public void run() { while (true) { synchronized (p) { // 判断是否有数据 if (p.flag) {// 如有有数据,生产者等待 try { p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 生产 if (x % 2 == 0) { p.setName("唐嫣"); p.setAge(28); } else { p.setName("刘正风"); p.setAge(29); } x++; // 将数据标志位,置为有值的true p.flag = true; // 唤醒正在等待的其他线程 p.notify(); } } } }
(5)线程组
多个线程出现时,可以将线程分组,统一处理。
涉及线程组类:ThreadGroup
注意:
线程组可以包含线程或者线程组。
当前线程只能访问所在线程组或者子组。不能访问父组或者兄弟组。
如果没有指定线程组,则属于main线程组。
主要方法:
public final String getName()
public final void setDaemon(boolean?daemon)
无线程添加方法,设置线程组在Thread构造方法中。
例子:cn.itcast5.demo
package cn.itcast5; /* * 线程组: * 多个线程出现时,可以将线程分组,统一处理 * Thread相关线程组的方法: * public Thread(ThreadGroup group,Runnable target,String name) * public final ThreadGroup getThreadGroup() * ThreadGroup: * public final String getName() */ public class Demo { public static void main(String[] args) { ThreadGroup tg = new ThreadGroup("唐嫣的小组"); Thread thread = new Thread(tg, new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(i); } } },"线程1"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(i); } } },"线程1"); System.out.println("thread:"+thread.getThreadGroup().getName()); //thread:唐嫣的小组 System.out.println("thread2:"+thread2.getThreadGroup().getName()); //thread2:main System.out.println("main:"+Thread.currentThread().getThreadGroup().getName()); //main:main } }
(6)线程池
将线程放置到同一个线程池中,其中的线程可以反复使用,无需反复创建线程而消耗过多资源。
Executors:线程池创建工厂类
返回线程池方法(还有其他方法) : public static ExecutorService newFixedThreadPool(int?nThreads)
ExecutorService:线程池类
主要方法:
Future<?> submit(Runnable?task)
<T> Future<T> submit(Callable<T>?task)
void shutdown()
注意:
线程池提交方法后,程序并不终止,是因为线程池控制了线程的关闭等。
Callable:相当于有返回值类型的runnable。
Future是将结果抽象的接口。可以将线程执行方法的返回值返回。
例子:cn.itcast5.demo2
package cn.itcast5; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /* * 线程池:用来存放线程。 * 一、Executors:线程池工厂类 * 主要方法: * public static ExecutorService newFixedThreadPool(int nThreads) * *二、 ExecutorService接口 * 主要方法: * 1、Future<?> submit(Runnable task) * 2、<T> Future<T> submit(Callable<T> task) * 3、void shutdown() */ public class Demo2 { public static void main(String[] args) { //使用线程池工厂创建线程池对象 ExecutorService es = Executors.newFixedThreadPool(2); //使用线程池,第一次提交runnable es.submit(new Runnable(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("第一次:"+i); } }}); //使用线程池,第二次提交runnable es.submit(new Runnable(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("第二次"+i); } }}); //不一定要关闭,但是这里关闭了线程池 es.shutdown(); } }
(7)线程实现的第三种方式
a.使用线程池工厂创建线程池对象 : ExecutorService es = Executors.newFixedThreadPool(2);
b.实现Callable接口,public public class MyCallable implements Callable<String>{},然后重写其中的call()方法,相当于runnable中的run()方法
c.创建Callable类的实例对象 : MyCallable mc = new MyCallable();
d.线程池提交Callable,返回Future结果 : Future<String> submit2 = es.submit(mc);
e.通过Future对象获取call方法的返回结果: System.out.println(submit2.get());
例子:cn.itcast5.MyCallable cn.itcast5.demo3
package cn.itcast5; import java.util.concurrent.Callable; /* * 定义一个Callable类。 * 实现多线程的第三种方式。 */ public class MyCallable implements Callable<String>{ //重写call方法,相当于runnable中的run方法 @Override public String call() throws Exception { return "经过多线程的学习,我发现,编程真的很有意思!"; } }
package cn.itcast5; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /* * * Future<?> submit(Runnable task) * <T> Future<T> submit(Callable<T> task) * * Future:线程执行返回结果 * V get() throws InterruptedException, ExecutionException 返回计算结果 Callable: */ public class Demo3 { public static void main(String[] args) throws Exception { //使用线程池工厂创建线程池对象 ExecutorService es = Executors.newFixedThreadPool(2); //使用线程池,提交runnable Future<?> submit = es.submit(new Runnable(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(i); } }}); Object object = submit.get(); System.out.println(object); //输出结果 null //实现多线程的第三种方式。 //创建Callable类的实例对象 MyCallable mc = new MyCallable(); //线程池提交Callable,返回Future结果 Future<String> submit2 = es.submit(mc); System.out.println(submit2); //打印的是地址 //通过Future对象获取call方法的返回结果。 System.out.println(submit2.get()); // call()方法返回的内容:经过多线程的学习,我发现,编程真的很有意思! } }
(8)定时器
定时器:Timer类
构造方法:
public Timer()
主要方法:
public void schedule(TimerTask task, Date time) 指定时间点完成指定任务
public void schedule(TimerTask task,Date firstTime,long period) 指定第一次开始时间和后边重复执行的时间完成指定任务
public void schedule(TimerTask task, long delay) 在指定时间后执行指定任务一次
public void schedule(TimerTask task,long delay, long period) 指定时间后执行指定任务第一次,再在每指定时间后,执行指定任务
定时器任务类:TimerTask (实现了Runnable接口)
public abstract void run() 要重写的真正执行的代码
针对于cancel()方法:
定时器的该方法终止定时器的。
定时器任务的该方法用来取消该定时器任务的任务。
例子:cn.itcast5.demo4 ; cn.itcast5.MyTask
package cn.itcast5; import java.util.Date; import java.util.Timer; /* * 定时器:Timer类 * 构造方法: * public Timer() * 主要方法: * public void schedule(TimerTask task, Date time) 指定时间点完成指定任务 * public void schedule(TimerTask task,Date firstTime,long period) 指定第一次开始时间和后边重复执行的时间完成指定任务 * public void schedule(TimerTask task, long delay) 在指定时间后执行指定任务一次 * public void schedule(TimerTask task,long delay, long period) 指定时间后执行指定任务第一次,再在每指定时间后,执行指定任务 * 定时器任务类:TimerTask 实现了Runnable接口 * public abstract void run() 要重写的真正执行的代码 * * 针对于cancel()方法: * 定时器的该方法终止定时器的。 * 定时器任务的该方法用来取消该定时器任务的任务。 */ public class Demo4 { public static void main(String[] args) { //创建定时器对象 Timer timer = new Timer(); //创建定时器任务对象 MyTask myTask = new MyTask(); //调用计划执行方法 //定时器不可以重复schedule任务 // timer.schedule(myTask, new Date(), 3000); // timer.schedule(myTask, new Date(new Date().getTime()+3000), 5000); // myTask.cancel(); 任务不能先取消再执行。 timer.schedule(myTask, 3000, 5000); // myTask.cancel(); //定时器任务的该方法用来取消该定时器任务的任务,定时器不会终止。 // timer.cancel(); //定时器的该方法终止定时器的。 } }
package cn.itcast5; import java.util.TimerTask; /* * 定义搞一个TimerTask实际类 */ public class MyTask extends TimerTask { @Override public void run() { System.out.println("地震了!"); } }
(9)最简单的线程程序代码:
new Thread() {
public void run() {
for(int x=0; x<100; x++) {
System.out.println(x);
}
}
}.start();
new Thread(new Runnable(){
public void run() {
for(int x=0; x<100; x++) {
System.out.println(x);
}
}
}).start();
2:单例设计模式(理解 面试)
(1)保证类在内存中只有一个对象。
(2)怎么保证:
A:构造私有
B:自己造一个对象
C:提供公共访问方式
(3)两种方式:
A:懒汉式(面试)
public class Student {
private Student(){}
private static Student s = null;
public synchronized static Student getStudent() {
if(s == null) {
s = new Student();
}
return s;
}
}
B:饿汉式(开发)
public class Student {
private Student(){}
private static Student s = new Student();
public static Student getStudent() {
return s;
}
}
(4)JDK的一个类本身也是单例模式的。
Runtime
(5)例子:cn.itcast5.Demo5 cn.itcast5.SinglePerson2 cn.itcast5.SinglePerson
package cn.itcast5; public class Demo5 { public static void main(String[] args) { // SinglePerson sp = new SinglePerson("唐嫣", 28); // SinglePerson sp2 = new SinglePerson("唐嫣", 28); //饿汉式对象 SinglePerson sp = SinglePerson.getInstance(); SinglePerson sp2 = SinglePerson.getInstance(); System.out.println(sp==sp2); //懒汉式对象 SinglePerson2 sp3 = SinglePerson2.getInstance(); SinglePerson2 sp4 = SinglePerson2.getInstance(); System.out.println(sp3==sp4); } }
package cn.itcast5; /* * 懒汉式单例 */ public class SinglePerson2 { private static final Object LOCK = new Object(); private String name; private int age; //定义一个private,static修饰的成员变量,类型是自己。 private static SinglePerson2 sp; //经构造方法私有化,外界无法创建对象,只能自己创建自己 private SinglePerson2(String name, int age) { super(); this.name = name; this.age = age; } //定义一个公共的,静态的,返回值类型为自己的的获取实例对象的方法。不考虑多线程 // public static SinglePerson2 getInstance() { // if(sp==null) { // System.out.println("没有对象"); // sp = new SinglePerson2("唐嫣", 28); // } // return sp; // } //定义一个公共的,静态的,返回值类型为自己的的获取实例对象的方法。考虑多线程,使用代码块,不考虑效率 // public static SinglePerson2 getInstance() { // // synchronized (SinglePerson2.LOCK) { // if (sp == null) { // System.out.println("没有对象"); // sp = new SinglePerson2("唐嫣", 28); // } // } // // return sp; // } //定义一个公共的,静态的,返回值类型为自己的的获取实例对象的方法。考虑多线程,使用同步方法,不考虑效率 // public static synchronized SinglePerson2 getInstance() { // // if (sp == null) { // System.out.println("没有对象"); // sp = new SinglePerson2("唐嫣", 28); // } // // return sp; // } //定义一个公共的,静态的,返回值类型为自己的的获取实例对象的方法。考虑多线程,使用代码块,考虑效率 public static SinglePerson2 getInstance() { if(sp==null) { //如果没有对象,才进行同步操作 synchronized (SinglePerson2.LOCK) { if (sp == null) { //同步操作过程中,必须判断的条件 System.out.println("没有对象"); sp = new SinglePerson2("唐嫣", 28); } } } return sp; } @Override public String toString() { return "SinglePerson [name=" + name + ", age=" + age + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
package cn.itcast5; /* * 饿汉式单例 */ public class SinglePerson { private String name; private int age; //定义一个private,static修饰的成员变量,类型是自己。 private static SinglePerson sp = new SinglePerson("唐嫣", 28); //经构造方法私有化,外界无法创建对象,只能自己创建自己 private SinglePerson(String name, int age) { super(); this.name = name; this.age = age; } //定义一个公共的,静态的,返回值类型为自己的的获取实例对象的方法。 public static SinglePerson getInstance() { return sp; } @Override public String toString() { return "SinglePerson [name=" + name + ", age=" + age + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
3、习题
(1)、使用定时器完成下列需求:某地区每隔1秒地震一次,当震过5次时,不再地震。
import java.util.Date; import java.util.Timer; import java.util.TimerTask; /* * 使用定时器完成下列需求:让某地区每隔1秒地震一次,当震过5次时,不再地震。(将地震打印五次,停止定时器,要用到定时器任务) */ public class Test { public static void main(String[] args) { //定义计时器 Timer timer = new Timer(); //创建定时器任务对象 MyTask mt = new MyTask(5,timer); //计划执行任务 timer.schedule(mt, new Date(), 1000); // //关闭计时器 // timer.cancel(); //不能在此处直接关 } } //定义计时器任务类 class MyTask extends TimerTask { private int times; private Timer timer; public MyTask(int times, Timer timer) { super(); this.times = times; this.timer = timer; } @Override public void run() { if(times>0) { System.out.println("地震了!"); }else { timer.cancel(); } times--; } }