DAY21

来源:互联网 发布:centos 移除文件 编辑:程序博客网 时间:2024/06/06 02:40

 

回顾内容:

1:线程和进程的理解?

进程,正在运行的应用程序(由系统分配资源和调用的独立的单位)

线程,依赖于进程存在,理解某一个进程的某一个任务

多个线程抢占CPU的执行权,并且线程的执行具有随机性

多线程:如果程序的执行路径有多条,就是多线程程序

单线程:如果程序的执行路径只有一条,就是单线程程序

2:Jvm是多线程程序吗?

,至少开启了两条线程

主线程执行当前的代码

垃圾回收线程及时回收掉不用的变量或者没有更多引用的

3:多线程的实现方式: 对象

1)继承自Thread类实现

 A:自定义一个类,该类继承自Thread

 B:重写该Thread类中的run()方法

 C:在主线程中创建该类对象,启动线程

启动线程:start()方法启动,交给JVM调用底层run()方法

一个线程对象只能启动一次,多次的启动,会出现:非法线程状态异常

2)实现Runnable接口的方式

A:自定义一个类,该类实现Runnable接口

B:实现该Runnable接口中的run()方法

C:在主线程中创建该类的实例(资源对象)

D:创建Thread类对象,将该类的实例作为参数进行传递

E:启动线程.

 

第二种方式优于第一种方式,采用Java设计原则:数据分离原则,而第一种单继承具有局限性.

 

校验一个多线程是否有安全问题的标准:

A:当前环境是否是多线程环境

B:是否有共享数据

C:是否多条语句对共享数据进行操作

 

使用Java同步机制:采用synchronized(同步锁对象){//解决线程安全问题

多条语句对共享数据的操作的代码;

}

 

同步锁对象:把它理解为门的开和关(该锁对象是多个线程对象要使用的同一个锁对象)

同步锁对象可以是Object(一般情况就是Object)类型以及任意的Java

同步方法:(非静态的),默认的锁对象:this

如果一个方法进来之后直接是同步代码块,那么可以将该方法定义为一个同步方法

public/private synchronized 返回值类型 方法名(参数){

 

}

 

静态的同步方法:默认的锁对象:当前类名.class属性

public static synchronized 返回值类型 方法名(){....}

 

线程安全的类:

Vector (在合并流(SequenceInputStream)中用的比较多的)

StringBuffer

Hashtable

集合操作的工具类:Collections里面有一个方法

public static List<E> synchronizedList(List<E> list) :支持同步的(安全)列表

 

 

 

DAY21

 使用同步机制的这种方式解决线程安全问题,但是不知道具体的锁对象在哪里添加,并且锁对象在哪里释放锁对象,对于这种情况

 Jdk5以后Java提供了一个更具体的锁对象:Lock

 Lock 实现提供了比使用 synchronized方法和语句可获得的更广泛的锁定操作

 Lock是一个接口,所以它在使用的是ReentrantLock子实现类

public void lock()获取锁。

  public void unlock()试图释放此锁

public class SellTicketDemo {

public static void main(String[] args) {

//创建资源对象

SellTicket st = new SellTicket() ;

//创建线程对象

Thread t1 = new Thread(st, "窗口1") ;

Thread t2 = new Thread(st, "窗口2") ;

Thread t3 = new Thread(st, "窗口3") ;

//启动线程

t1.start() ;

t2.start() ;

t3.start() ;

}

}

 

 

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {

//定义一个100张票

private int tickets = 100 ;

//Jdk5以后Java提供了更具的锁定操作:加锁和释放锁

//定义一个具体锁对象

private  Lock lock = new ReentrantLock() ;//具体的lock

public void run() {

//模拟电影院一直有票

while(true){

try{

//获取锁

lock.lock() ;

if(tickets>0){

//加入延迟操作

try {

Thread.sleep(100) ;

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+正在出售第"+(tickets--)+"张票");

}

}finally{

//试图释放锁对象

lock.unlock() ;

}

}

}

 

}

 

 

使用同步机制可以解决多线程的安全问题,但是自身也会有弊端:

1)同步---->执行效率低(每一个线程在抢占到CPU的执行权,会去将()关闭,别的线程进不来)

2)容易出现死锁现象

死锁线程:两个或者两个以上的线程出现了互相等待的情况,就会出现死锁!

线程通信的问题:使用消费者和生产者模式

public class DieLock extends Thread {

//定义一个成员变量

private boolean flag ;

public DieLock(boolean flag){

this.flag = flag ;

}

//重写run()方法

public void run() {

if(flag){

synchronized(MyLock.objA){

System.out.println("if objA");

synchronized (MyLock.objB) {

System.out.println("if objB");

}

}//代码执行完毕,objA锁相当于才能被释放掉

}else {

synchronized (MyLock.objB) {

System.out.println("else objB");

synchronized(MyLock.objA){

System.out.println("else objA");

}

}

}

}

}

 

public class DieLockDemo {

public static void main(String[] args) {

//创建线程类对象

DieLock dl1 = new DieLock(true) ;

DieLock dl2 = new DieLock(false);

//启动线程

dl1.start() ;

dl2.start() ;

}

}

public class MyLock {

//创建两把锁对象

public static final Object objA = new Object() ;

public static final Object objB = new Object() ;

}

 

 

 

 

 

提供:资源对象:Student  提供一些成员变量:姓名 和年龄

生产者线程:SetThread:  生产一些学生数据(设置学生数据)

消费者线程:GetThread:  输出学生数据

测试类:StudentDemo,实现多线程环境

使用刚才的这几个类:模拟生产消费者模式,生产者SetThread产生学生数据,GetThread消费者线程输出学生数据,发现一个问题,

输出学生数据的时候,null--0

 

 对于每一个线程都在创建自己的学生对象,两个线程操作的两个对象而不是同一个对象,所以应该解决:

 将学生对象成员变量,然后通过构造方法进行传递,在测试类中,创建学生对象(同一个资源对象)让多个线程对这个学生对象进行操作

 改进:为了数据多并且效果更明显,加入循环语句进行操作,给生产者线程和消费者线程分别加入循环语句(while循环)

 

public class StudentDemo {

public static void main(String[] args) {

//创建一个资源对象

Student s = new Student() ;

//创建资源对象

SetThread st = new SetThread(s) ;

GetThread gt = new GetThread(s) ;

//创建线程类对象

Thread t1 = new Thread(st) ;

Thread t2 = new Thread(gt) ;

//分别启动线程

t1.start() ;

t2.start() ;

}

}

 

public class Student {

String name;

int age ;

}

 

 

//生产者线程

public class SetThread implements Runnable {

private Student s ;

public SetThread(Student s){

this.s = s ;

}

public void run() {

//设置数据

s.name = "RNG" ;

s.age = 666 ;

}

 

}

 

 

//消费者线程

public class GetThread implements Runnable {

private Student s ;

public GetThread(Student s){

this.s = s ;

}

public void run() {

//输出语句

System.out.println(s.name+"---"+s.age);

}

}

 

 

 

 

改进:为了数据多并且效果更明显,加入循环语句进行操作,给生产者线程和消费者线程分别加入循环语句(while循环)

改进之后出现两个问题:

1)同一个数据打印多次

//CPU一点点时间片足够执行很多次

2)并且年龄和姓名不符合

线程的随机性导致的

优化改进之后,这些问题就说明当前多线程有安全问题:

多线程安全问题的标准:

1)是否是多线程环境

2)是否有共享数据

3)是否有多条语句对共享数据操作

当前是多线程环境

有共享数据有多条语句对共享数据:s.name,s.age

使用同步机制来解决这个问题:将多条语句对共享数据进包装

使用同步机制去解决线程的安全问题,但是又有一个新的问题:

测试的时候,数据打印一打一大片,体验不好

需求:

让这个数据依次进行打印控制台,要使用这种方式去解决,利用Java等待唤醒机制

public class StudentDemo {

public static void main(String[] args) {

//创建一个资源对象

Student s = new Student() ;

//创建资源对象

SetThread st = new SetThread(s) ;

GetThread gt = new GetThread(s) ;

//创建线程类对象

Thread t1 = new Thread(st) ;

Thread t2 = new Thread(gt) ;

//分别启动线程

t1.start() ;

t2.start() ;

}

}

 

public class Student {

String name;

int age ;

//声明一个变量

boolean flag ; //默认没有数据,如果true,则说明有数据

}

 

 

 

 

//生产者线程

public class SetThread implements Runnable {

private Student s ;

public SetThread(Student s){

this.s = s ;

}

private int x = 0 ;

public void run() {

while(true){

//同步机制进行操作

synchronized (s) {

//判断有没有数据

if(s.flag){

//处于等待状态

try {

s.wait() ;//阻塞式方法,立即释放锁

} catch (InterruptedException e) {

e.printStackTrace();

}

}

if(x%2==0){

//x = 0

//设置数据

s.name = "RNG" ;

s.age = 666;

}else{

//设置数据

s.name = "EDG" ;

s.age =233 ;

}

x ++ ;

//修改标记

s.flag = true ;//有数据了

//通知t2:消费者线程来消费数据

s.notify() ;//唤醒等待这种状态...

}

}

}

 

}

 

//消费者线程

public class GetThread implements Runnable {

private Student s ;

public GetThread(Student s){

this.s = s ;

}

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 ;//消费者线程

//通知对方(t1线程),消费者线程没有数据类,赶紧来消费

s.notify() ;//唤醒t1线程....

}

}

}

 

}

 

 

线程组

线程组表示一个线程的集合:Java允许一个线程中有多个线程

public class ThreadGroupDemo {

public static void main(String[] args) {

method1() ;

method2() ;

}

//给每一个子线程可以设置线程名称

private static void method2() {

//public ThreadGroup(String name)构造一个新线程组

ThreadGroup tg = new ThreadGroup("这是一个新的线程组") ;

//创建线程类对象,并且将线程组对象作为参数进行传递,就使用Thread类的构造方法

//public Thread(ThreadGroup group,Runnable target ,String name){}

//创建资源对象

MyRunnable my = new MyRunnable() ;

Thread t1 = new Thread(tg, my, "线程1") ;

Thread t2 = new Thread(tg, my, "线程2") ;

ThreadGroup tg1 = t1.getThreadGroup() ;

ThreadGroup tg2 = t2.getThreadGroup() ;

System.out.println(tg1.getName());

System.out.println(tg2.getName());

tg.setDaemon(true) ;//将线程组中的所有的线程都设置为守护线程(后台线程)

}

 

private static void method1() {

//如何获取多个线程所在的线程组名称呢?

//创建资源对象

MyRunnable my = new MyRunnable() ;

//创建线程类对象

Thread t1 = new Thread(my) ;

Thread t2 = new Thread(my) ;

t1.start() ;

t2.start() ;

//public final ThreadGroup getThreadGroup()返回该线程所属的线程组

ThreadGroup tg1 = t1.getThreadGroup() ;

ThreadGroup tg2 = t2.getThreadGroup() ;

//public final ThreadGroup getThreadGroup()返回该线程所属的线程组

String name1 = tg1.getName() ;

String name2 = tg2.getName() ;

//子线程默认的线程组名称:main线程

System.out.println(name1);

System.out.println(name2);

//所有的线程它的默认线程组名称就是main

System.out.println(Thread.currentThread().getThreadGroup().getName());

}

}

 

 

 

 

 

 

 

import java.util.concurrent.Executors;

线程池的好处:节约成本,

很多子线程调用完毕不会立即被回收掉,而是会回到线程池中被多次利用

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

public static ExecutorService newFixedThreadPool(int nThreads)

Executors工厂类中的这个方法参数直接指定在当前线程池中有多少个线程

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法

ExecutorsService :接口中的方法

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T> task)

 

import java.util.concurrent.Executors;

public class ExecutorsDemo {

public static void main(String[] args) {

//创建线程池对象,使用Executors工厂类

//public static ExecutorService newFixedThreadPool(int nThreads)

ExecutorService pool = Executors.newFixedThreadPool(2) ;

//下来使用ExecutorService(跟踪多个异步任务)一些方法

//使用submit(Runnable target):提交多个任务

pool.submit(new MyRunnable()) ;

pool.submit(new MyRunnable()) ;

//结束线程池

pool.shutdown() ;

}

}

 

 

public class MyRunnable implements Runnable {

public void run() {

//for循环

for(int x = 0 ; x < 100 ; x ++){

System.out.println(Thread.currentThread().getName()+":"+x);

}7

}

 

}

 

 

 

 

多线程程序的实现方式3:(实际开发中很少用到!)

public static ExecutorService newFixedThreadPool(int nThreads)

Executors工厂类中的这个方法参数直接指定在当前线程池中有多少个线程

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法

ExecutorsService :接口中的方法

<T> Future<T> submit(Callable<T> task)

该返回值表示:异步计算的结果!

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class CallableDemo {

public static void main(String[] args) {

//创建线程池对象,利用工厂类

ExecutorService Threadpool = Executors.newFixedThreadPool(2) ;

//提交Callable任务(异步任务)

Threadpool.submit(new MyCallable()) ;//相当于线程中的start()方法

Threadpool.submit(new MyCallable()) ;

//结束线程池

Threadpool.shutdown() ;

}

}

 

 

 

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

public class CallableDemo {

public static void main(String[] args) throws InterruptedException, ExecutionException {

//创建线程池对象,利用工厂类:Executors

ExecutorService ThreadPool = Executors.newFixedThreadPool(2) ;

//提交2异步任务,分别计算1-100,1-200之间的和

Future<Integer> f1 = ThreadPool.submit(new MyCallable(100)) ;

Future<Integer> f2 = ThreadPool.submit(new MyCallable(200)) ;

//分别调用Future接口中  get()方法,返回具体的结果

Integer v1 = f1.get() ;

Integer v2 = f2.get() ;

//输出结果

System.out.println("v1:"+v1);

System.out.println("v2:"+v2);

}

}

 

import java.util.concurrent.Callable;

//线程求和

public class MyCallable implements Callable<Integer> {

private int number ;

public MyCallable(int number){

this.number = number ;

}

public Integer call() throws Exception {

//定义最终结果变量

int sum = 0 ;

for(int x = 1 ; x <=number;  x ++ ){

sum += x ;

}

return sum;

}

 

多线程中匿名内部类的方式

格式:new 类名(具体类,抽象类),接口(){

 重写/实现方法;

}

 匿名内部类的本质:

 继承了该类或者是实现该接口的子类对象

public class ThreadDemo {

public static void main(String[] args) {

//继承自Thread

new Thread(){

public void run() {

//for循环

for(int x = 0 ; x <100 ; x ++){

System.out.println(Thread.currentThread().getName()+":"+x);

}

}

}.start() ;//启动线程

//Runnable接口的方式

new Thread(new Runnable() {

public void run() {

//for循环

for(int x = 0 ; x < 100 ; x ++){

System.out.println(Thread.currentThread().getName()+":"+x);

}

}

}).start() ;

new Thread(new Runnable() {

public void run() {

for(int x = 0 ; x <100 ; x ++){

System.out.println("hello"+x);

}

}

}){

public void run() {

for(int x = 0 ; x <100 ; x ++){

System.out.println("world"+x);

}

}

}.start() ;

}

}

 

 

 

 

定时器:Timer:

常用的几个方法:

public void schedule(TimerTask task,Date time)安排在指定的时间执行指定的任务

public void schedule(TimerTask task, long delay)在多少毫秒后执行指定任务

public void schedule(TimerTask task, long delay, long period)

在多少毫秒后,执行任务,并且每个多少毫秒重复执行

public void cancel()终止此计时器,丢弃所有当前已安排的任务

//需求:3秒后执行爆炸任务

import java.util.Timer;

import java.util.TimerTask;

public class TimerDemo {

public static void main(String[] args) {

//public Timer()创建一个新计时器。

Timer t = new Timer() ;

//调用public void schedule(TimerTask task, long delay)在多少毫秒后执行指定任务

//TimerTask:需要被执行的任务类是一个抽象类,所以不能直接实例化

t.schedule(new MyTask(t), 3000) ;

}

}

 

//定义一个任务类:MyTask

//TimerTask中的一个抽象方法:public abstract void run():执行定时器的任务

class MyTask extends TimerTask{

private Timer t ;

public MyTask(){

}

public MyTask(Timer t){

this.t = t ;

}

public void run() {

System.out.println("bom,爆炸了...");

t.cancel() ;//取消任务...

}

}

 

 

 public void schedule(TimerTask task, long delay, long period)

在多少毫秒后,执行任务,并且每个多少毫秒重复执行

 需求:3秒后执行爆炸任务,每隔2秒重复爆炸

import java.util.Timer;

import java.util.TimerTask;

public class TimerDemo2 {

public static void main(String[] args) {

//创建定时器对象

Timer t = new Timer() ;

//调用方法

t.schedule(new MyTask2(), 3000, 2000) ;

}

}

class MyTask2 extends TimerTask {

public void run() {

System.out.println("bom,爆炸了....");

}

}

 

 

课堂练习

 

 需求:在指定的时间删除我们的指定目录

mport java.io.File;

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.Timer;

import java.util.TimerTask;

//删除的demo文件夹的任务

class DeleteFolder extends TimerTask{

public void run() {

//封装当前项目下的这个demo文件

File srcFolder = new File("demo") ;

deleteFolder(srcFolder) ;

}

//递归删除

private void deleteFolder(File srcFolder) {

//获取当前srcFolder下面的所有的文件以及文件夹的File数组

File[] fileArray = srcFolder.listFiles() ;

//对该对象非空判断

if(fileArray !=null){

//增强for遍历

for(File file :fileArray){

//继续判断file对象是否是文件夹

if(file.isDirectory()){

//继续回到删除目录的方法

deleteFolder(file) ;

}else{

//不是目录,是文件,直接删除

System.out.println(file.getName()+"---"+file.delete());

}

}

System.out.println(srcFolder.getName()+"----"+srcFolder.delete());

}

}

}

public class TimerTest {

public static void main(String[] args) throws ParseException {

//创建一个定时器对象

Timer t = new  Timer() ;

//public void schedule(TimerTask task,Date time)安排在指定的时间执行指定的任务

//定义一个文本日期格式

String dateStr = "2017-12-7 23:50:00" ;

//解析成Date对象

//创建SimpleDateFormat对象

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ;

//解析方法

Date date = sdf.parse(dateStr) ;

//调用定时器的这个在规定时间内执行某个任务的方法

t.schedule(new DeleteFolder(), date) ;

}

}

 

 

 

课堂练习 用户的注册登录

public class User {

//声明两个成员变量

private String username;

private String password ;

public User(){}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

}

 

 

 

import org.westos.user.User;

//用户的操作接口层

public interface UserDao {

//用户登录

public abstract boolean isLogin(String username,String password) ;

//这是用户注册功能

public abstract void regist(User user) ;

}

 

 

 

 

 

 

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

 

import org.westos.dao.UserDao;

import org.westos.user.User;

 

 // 这是用户操作的接口实现类(IO)

public class UserDaoImpl implements UserDao {

//在成员变量位置使用File对象封装这个文件

private static  File file = new File("user.txt") ;

static{

try {

file.createNewFile() ;

} catch (IOException e) {

// e.printStackTrace();

System.out.println("创建文件失败....");

}

}

//登陆功能

public boolean isLogin(String username, String password) {

//定义一个标记

boolean flag = false ;

///创建字符输入流,读取该文件中的内容

BufferedReader br = null ;

try {

    br = new BufferedReader(new FileReader(file)) ;

//一次可以读取一行

String line = null ;

while((line=br.readLine())!=null){

//刚才注册的时候是:用户名=密码写入的

String[] datas = line.split("=") ;

if(datas[0].equals(username) && datas[1].equals(password)){

//登陆成功,修改标记

flag = true ;

}

}

} catch (FileNotFoundException e) {

// e.printStackTrace();

System.out.println("用户登陆时找不到文件导致失败");

} catch (IOException e) {

// e.printStackTrace();

System.out.println("用户登陆失败...");

}finally{

//释放资源

if(br!=null){

try {

br.close() ;

} catch (IOException e) {

// e.printStackTrace();

System.out.println("用户登陆时释放资源失败...");

}

}

}

return flag;

}

 //用户注册:

// 必须约定以什么样的格式注册,使用流的形式将用户的用户名和密码输出到一个文件中,文件中就保存了用户名和用户密码,等会用户登录的时候只需要读取这个文件中内容就可以了

public void regist(User user) {

//注册的时候使用:字符缓冲输出流

//使用构造方式,追加写入

BufferedWriter bw = null ;

try {

   bw = new BufferedWriter(new FileWriter(file, true)) ;

//应该约定一种格式:用户名=密码

bw.write(user.getUsername()+"="+user.getPassword()) ;

//刷新缓冲区

bw.flush() ;

bw.newLine() ;

} catch (IOException e) {

// e.printStackTrace();

System.out.println("用户注册失败");

}finally{

//是否资源

//针对流对象进行判断,是否为空

if(bw !=null){

try {

bw.close() ;

} catch (IOException e) {

// e.printStackTrace();

System.out.println("用户注册释放资源失败...");

}

}

}

}

 

}

 

 

 

 

 

import java.util.Scanner;

 

import org.westos.dao.UserDao;

import org.westos.dao.impl.UserDaoImpl;

import org.westos.user.User;

 

 //用户登陆注册的测试类

 

public class UserTest {

 

public static void main(String[] args) {

//为了能够回到这个界面

while(true){

//选择界面

System.out.println("------------------欢迎光临---------------------");

System.out.println("1 登陆");

System.out.println("2 注册");

System.out.println("3 退出");

//创建键盘录入对象

Scanner sc = new Scanner(System.in) ;

//调用功能:

UserDao ud = new UserDaoImpl() ;

System.out.println("请您输入您的选择:");

String choiceString = sc.nextLine() ;

//使用选择结构语句之switch语句

switch(choiceString){

case "1" :

//登陆界面

System.out.println("--------------登陆界面---------------");

System.out.println("请输入用户名:");

String username = sc.nextLine() ;

System.out.println("请输入密码:");

String password = sc.nextLine() ;

//调用功能

boolean flag = ud.isLogin(username, password) ;

if(flag){

System.out.println("登陆成功");

//break:只是结束switch语句

System.exit(0) ;//终止Java虚拟机

}else{

System.out.println("登陆失败,用户名或者密码输入错误,请重新输入...");

}

break ;

case "2":

//注册界面

System.out.println("--------------注册界面---------------");

System.out.println("请输入用户名:");

String newUserName = sc.nextLine() ;

System.out.println("请输入密码:");

String newPassword = sc.nextLine() ;

//创建用户对象,封装用户名和密码

User u = new User() ;

u.setUsername(newUserName) ;

u.setPassword(newPassword) ;

//调用功能

ud.regist(u) ;

System.out.println("恭喜您,注册成功");

break ;

case "3":

//退出界面

default:

System.out.println("欢迎光临,下次再继续使用....");

System.exit(0) ;

break ;

}

}

}

}

 

原创粉丝点击