使用同步机制的这种方式解决线程安全问题,但是不知道具体的锁对象在哪里添加,并且锁对象在哪里释放锁对象,对于这种情况
Jdk5以后Java提供了一个更具体的锁对象:Lock
Lock实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
Lock是一个接口,所以它在使用的是ReentrantLock子实现类
publicvoid 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());
}
}
线程池的好处:节约成本,
很多子线程调用完毕不会立即被回收掉,而是会回到线程池中被多次利用
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorServicenewFixedThreadPool(int nThreads)
Executors工厂类中的这个方法参数直接指定在当前线程池中有多少个线程
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
ExecutorsService:接口中的方法
Future<?>submit(Runnable task)
<T>Future<T> submit(Callable<T> task)
import java.util.concurrent.Executors;
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 ExecutorServicenewFixedThreadPool(int nThreads)
Executors工厂类中的这个方法参数直接指定在当前线程池中有多少个线程
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnabl e对象或者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,Datetime)安排在指定的时间执行指定的任务
public void schedule(TimerTask task, longdelay)在多少毫秒后执行指定任务
public void schedule(TimerTask task, longdelay, long period)
在多少毫秒后,执行任务,并且每个多少毫秒重复执行
public void cancel()终止此计时器,丢弃所有当前已安排的任务
//需求:3秒后执行爆炸任务
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
publicstatic void main(String[] args) {
//publicTimer()创建一个新计时器。
Timert = new Timer() ;
//调用public voidschedule(TimerTask task, long delay)在多少毫秒后执行指定任务
//TimerTask:需要被执行的任务类是一个抽象类,所以不能直接实例化
t.schedule(newMyTask(t), 3000) ;
}
}
//定义一个任务类:MyTask
//TimerTask中的一个抽象方法:public abstractvoid run():执行定时器的任务
class MyTask extends TimerTask{
privateTimer t ;
publicMyTask(){
}
publicMyTask(Timer t){
this.t= t ;
}
publicvoid run() {
System.out.println("bom,爆炸了...");
t.cancel();//取消任务...
}
}
public void schedule(TimerTask task, longdelay, 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 ;
}
}
}
}