本期知识点:
多线程
线程组
线程池
匿名内部类
定时器

多线程
线程组
线程池
匿名内部类
定时器
1.多线程
a.JDK5以后的针对线程的锁定操作和释放操作
使用同步机制解决了线程的安全问题,但是我们并没有看到具体的锁对象是谁,JDK5以后java提供了接口Lock里面又提供了一些方法:Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作由于该Lock接口不能实例化,提供了子实现类:ReentrantLockpublic void lock() 获取锁public void unlock() 释放锁
b.死锁
i.虽然使用Lock锁定操作或者是同步锁synchronized来解决线程安全的问题,线程安全解决了,线程安全的弊端:
1)效率低2)如果出现了同步嵌套,就容易产生死锁问题
ii.死锁问题的产生:
两个或两个以上的线程,抢占CPU的执行权,然后出现了互相等待的情况:使用线程间的通信问题解决!
iii.死锁问题及其代码
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
c.线程间通信问题:
不同种类的线程间针对同一个资源的操作。分析:当前的资源情况:
Student: 共同的资源setThread:设置学生数据(生成者)getThread:获取学生数据(消费者)StudentDemo(测试类)
问题1:
按照生产消费模式:分别进行产生数据和消费数据,通过测试打印出来:
null---0
问题2:
为了数据的效果好,加入循环和判断,给出不同的值,这个时候产生新的问题。a.同一个数据出现多次
原因: CPU的一点点时间片的执行权,就足够执行很多次。
b.年龄和姓名不匹配
原因:线程运行的随机性
解决方法:
加锁
注意:
i.不同种类的线程都要加锁ii.不同种类的线程必须加同一把锁
问题3:线程安全问题解决了,但是会存在如下问题
i.如果消费者先抢到了CPU的执行权,就会去消费数据,但是现在的数据是默认值。没有意义应该等待数据有意义再去消费。ii.如果生产者抢到CPU的执行权,就会去生产数据,但是它生产完数据后依然拥有执行权,那么它继续生产数据。这样就有问题,应该等消费者把数据消费完,再生产。
正常思路:
1)生产者:先看是否有数据,有就等待,没有就生产,生产完后通知消费者来消费数据。2)消费者:先看是否有数据,有就消费,没有就等待,通知生产者生产数据。
为了处理这样的问题,Java提供了一种机制:等待唤醒机制。
d.等待唤醒机制
i.Object类中提供了一些方法:
wait() 线程等待public final void notify() 唤醒正在等待的单个线程public final void notifyAll() 唤醒所有线程
ii.(面试题)这几个方法都是线程有关的方法,为什么把这个方法不定一在Thread类里面?
刚才这个案例,使用的锁对象进行调用,锁对象可以是任意对象.而Object本身就是代表所有的类根类:代表所有对象.
e.(问题)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
i.这些方法存在与同步中。ii.使用这些方法时必须要标识所属的同步的锁。iii.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
f.线程的状态转移图:
死锁:
public class MyLock {
public static final Object objectA = new Object();
public static final Object objectB = new Object();
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag=flag;
}
@Override
public void run() {
if(flag){
synchronized (MyLock.objectA) {
System.out.println("if ObjectA");
synchronized (MyLock.objectB){
System.out.println("if ObjectB");
}
}
}
else{
synchronized (MyLock.objectB){
System.out.println("else ObjectB");
synchronized (MyLock.objectA){
System.out.println("else Object");
}
}
}
}
}
public class DieLockDemo {
public static void main(String[] args) {
//创建线程对象
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
//启动线程
d1.start();
d2.start();
}
}
等待唤醒机制:
//生产者
public class SetThread implements Runnable {
private Student s ;
private int x = 0;
public SetThread(Student s ){
this.s=s;
}
@Override
public void run() {
while(true){
synchronized (s){
if(s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x%2==0){
s.name="Ash";
s.age=24;
}else{
s.name="Ying";
s.age=22;
}
x++;
//修改标记
s.flag=true;
//唤醒线程
s.notify();
}
}
}
}
//消费者
public class GetThread implements Runnable {
private Student s ;
public GetThread(Student s ){
this.s=s;
}
@Override
public void run() {
while(true){
synchronized(s){
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name+"---"+s.age);
//修改标记
s.flag=false;
//唤醒线程
s.notify();
}
}
}
}
public class Student {
String name;
int age;
boolean flag;
}
public class StudentDemo {
public static void main(String[] args) {
//创建共同资源对象
Student s = new Student();
//创建每个线程对象,针对同一个资源进行操作
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//创建Thread类对象
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//分别启动线程
t1.start();
t2.start();
}
}
2.线程组
a.概述
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
b.常用方法:
public ThreadGroup(String name) 构造一个新线程组。public Thread(ThreadGroup group,Runnable target, String name)public final void setDaemon(boolean daemon) 设置线程组是否是一个守护线程
public final ThreadGroup getThreadGroup() 返回该线程所在的线程组。默认情况下,所有的线程都属于主线程组。public final String getName() 返回此线程组的名称。
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadGroupDemo {
public static void main(String[] args) {
//fun1();
fun2();
}
private static void fun2() {
//不知道t1,t2线程是属于哪一个线程组的?
//如何该线程所在线程组的名称
MyRunnable my = new MyRunnable() ;
Thread t1 = new Thread(my, "张三") ;
Thread t2 = new Thread(my, "李四") ;
//Thread类中提供了另外一个方法:
// public final ThreadGroup getThreadGroup():返回该线程所在的线程组
//使用线程对象调用
ThreadGroup tg1 = t1.getThreadGroup() ;
ThreadGroup tg2 = t2.getThreadGroup() ;
//public final String getName()返回此线程组的名称。
String name1 = tg1.getName() ;
String name2 = tg2.getName() ;
//输出这两个线程所在的线程组名称
System.out.println(name1);
System.out.println(name2);
//通过测试:发现线程默认情况线程组属于main线程:
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
private static void fun1() {
//如何设置线程组名称?
// public ThreadGroup(String name)构造一个新线程组。
ThreadGroup tg = new ThreadGroup("这是一个新的线程组") ;
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my ,"Ash");
Thread t2 = new Thread(my ,"Glaz");
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
String name1 = tg1.getName() ;
String name2 = tg1.getName() ;
System.out.println(name1);
System.out.println(name2);
tg.setDaemon(true);
}
}
3.线程池
a.概述:
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
b.使用线程池可以解决很多问题:
1)如何创建线程池对象:
Executors工厂:专门用来创建线程池的:提供了一个方法public static ExecutorService newFixedThreadPool(int nThreads)
2)这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
Future<?> submit(Runnable task) Runnable接口作为一个参数:要该类的子实现类对象<T> Future<T> submit(Callable<T> task)
3)线程池可以结束吗?
void shutdown()
c.多线程程序实现方法三:
步骤:1)自定义一个类,实现Callable接口2)实现里面的call方法3)主线程中创建线程对象,4)用线程池对象提交任务(例如:MyCallable这个任务实现0--100之间的循环)5)提交后结束线程池
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(3);
//使用ExecutorsService接口中有要给方法:
//Future<?> submit(Runnable task):Runnable接口作为一个参数:要该类的子实现类对象
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
4.匿名内部类
匿名内部类的方式实现多线程程序:
new 类名或者接口名{(
重写/实现方法;
}
本质:继承该类或者或者实现该接口的子类对象!
5.定时器
a.概述:
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
b.构造方法:a
public Timer() 创建一个新计时器。public void schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务
参数1:task - 所要安排的任务参数2:time - 执行该任务的时间毫秒值
c.其他方法
public boolean cancel() 取消此计时器任务
public void schedule(TimerTask task, Date firstTime,long period) 每隔多少毫秒进行重复性的 任务
import java.util.Timer;
import java.util.TimerTask;
public class Demo01_无参调用 {
public static void main(String[] args) {
Timer t = new Timer();
t.schedule(new Fun(), 3000);
}
}
class Fun extends TimerTask{
public Fun(){
}
@Override
public void run() {
System.out.println("boom!!!");
}
}
import java.util.Timer;
import java.util.TimerTask;
public class Demo02_有参调用 {
public static void main(String[] args) {
//创建定时器对象
Timer t = new Timer();
t.schedule(new Hun(t), 3000);
}
}
class Hun extends TimerTask{
private Timer t;
public Hun(){
}
public Hun(Timer t){
this.t=t;
}
@Override
public void run() {
System.out.println("boom!!!");
t.cancel();
}
}
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
//在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)
public class test {
public static void main(String[] args) {
File f = new File("C:\\Users\\Administrator\\Desktop\\Demo");
Timer t = new Timer();
t.schedule(new Fun3(t,f), 3000);
}
}
class Fun3 extends TimerTask{
private Timer t ;
private File f;
public Fun3(Timer t, File f) {
this.t=t;
this.f=f;
}
@Override
public void run() {
hun(f);
t.cancel();
}
public void hun(File f){
File[] fa = f.listFiles();
//非空判断
if(fa!=null){
for (File file : fa) {
if(file.isDirectory()){
hun(file);
}else{
System.out.println(file.getName()+":"+file.delete());
}
}
System.out.println(f.getName()+":"+f.delete());
}
}
}