一.Lock(接口)
1.使用同步机制的方式解决线程安全问题,但是不知道具体的锁对象在哪里添加,并且锁对象在哪里释放锁对象,对于这种情况,jdk5以后java提供了一个更具体的锁对象
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
Lock是一个接口,所以它在使用时用 ReentrantLock子实现类
2.加锁和释放锁的方法:
public void lock()获取锁
public void unlock()试图释放此锁
try{
锁对象.lock()
..........
}finally{
锁对象.unlock() //释放资源
}
package lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTickets implements Runnable{
private static int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
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();
}
}
}
}
package lock;
public class SellTicketsDemo {
public static void main(String[] args) {
// 创建SynRunnable对象
SellTickets st = new SellTickets();
//创建线程类对象
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
3.同步机制的优缺点
优点:解决多线程的安全问题
缺点:1)同步,执行效率低(每个线程在抢占到CPU的执行权后,会将门关闭,其他线程不能进来)
2)容易出现死锁:两个或者两个以上的线程出现了互相等待的情况,容易死锁
4.线程间的通信:生产者消费者模式(多个线程对同一资源进行操作)
例子: 1)资源对象:Student类 提供一些成员变量:姓名 和年龄
2)生产者线程:SetThread类: 生产一些学生数据(设置学生数据)
3)消费者线程:GetThread类: 输出学生数据
4)测试类:StudentDemo类,实现多线程环境
对于生产者来说,产生数据,如果处于等待(wait)状态就说名有数据,通知消费者有线程,赶紧输出数据
对于消费者线程,输出数据,如果没有数据,应该通知生产者产生数据。
以上是一个多线程环境,会出现线程不安全:
1)同一数据打印一片(CPU的一点点时间片足够执行很多此)
2)年龄和姓名不符合(线程的随机性导致的)
使用等待唤醒机制可以解决问题1);使用同步机制可以解决问题2)
package waitnotify;
public class Student {
String name;
int age;
//声明一个变量
boolean flag;//默认没有数据,如果是true,则说明有数据
}
package waitnotify;
//生产者线程
public class SetThread implements Runnable{
private Student s;
public SetThread(Student s){
this.s = s;
}
private int i = 0;
@Override
public void run() {
while(true){
//同步机制可以解决数据不一致
synchronized(s){
//等待唤醒机制
//判断有没有数据
if(s.flag){ //对于生产者来说,没有数据,则处于等待状态,
//处于等待状态
try {
s.wait();//阻塞方式
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i%2==0){
//设置学生数据
s.name = "韩庚";
s.age = 18;
}else{
//设置学生数据
s.name = "张靓颖";
s.age = 16;
}
i++;
//修改标记
s.flag = true;//有数据
//通知gt:消费者线程来消费
s.notify();//唤醒等待状态
}
}
}
}
package waitnotify;
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;//消费者线程没有可输出的数据
//通知对方st线程,消费者线程没有数据,赶紧产生数据
s.notify(); //唤醒st线程
}
}
}
}
package waitnotify;
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();
}
}
以上例子的成员变量不是私有化,一般成员变量私有化,可以对上面的进行改进
package lastwaitnotify;
public class Student {
private String name;
private int age;
//声明一个变量
private boolean flag; //默认没有数据,如果是true,则说明有数据
//set(String name,int age)方法,产生数据
//同步机制可以解决数据不一致
public synchronized void set(String name,int age){
//等待唤醒机制
//判断有没有数据
if(this.flag){ //对于生产者来说,没有数据,则处于等待状态,
//处于等待状态
try {
this.wait();//阻塞方式,立即释放锁 notify
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//设置学生数据
this.name = name;
this.age = age;
//修改标记
this.flag = true;//有数据
//通知gt:消费者线程来消费
this.notify();//唤醒等待状态
}
//get()输出数据
public synchronized void get(){
if(!this.flag){
try {
this.wait() ;//调用的时候,会立即释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//输出语句
System.out.println(this.name+"---"+this.age);
//修改标记
this.flag = false ;//消费者线程
//通知对方(st线程),消费者线程没有数据类,赶紧来消费
this.notify() ;//唤醒t1线程....
}
}
package lastwaitnotify;
//生产者线程
public class SetThread implements Runnable{
private Student s;
public SetThread(Student s){
this.s = s;
}
private int i = 0;
@Override
public void run() {
while(true){
if(i%2==0){
//设置学生数据
s.set("韩庚", 18);
}else{
//设置学生数据
s.set("张靓颖", 16);
}
i++;
}
}
}
package lastwaitnotify;
public class GetThread implements Runnable{
private Student s;
public GetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
s.get();
}
}
}
package lastwaitnotify;
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();
}
}
二.线程组(ThreadGroup)
表示一个线程的集合
1.public final ThreadGroup getThreadGroup()返回该线程的线程组
2.public ThreadGroup(String name)设置线程组名称
3.public Thread(ThreadGroup,Runnable target,String name)给每一个子线程设置线程组名称
package threadgroup;
public class ThreadGroupDemo {
public static void main(String[] args) {
//线程组:一个线程的集合
method1();
method2();
}
//给每一个子线程设置线程名称
private static void method2() {
//public ThreadGroup(String name)构造一个新线程组
ThreadGroup tg = new ThreadGroup("这是一个新的线程组") ;
//创建资源对象
MyRunnable t1 = new MyRunnable();
//创建线程类对象,并且将线程组对象作为参数进行传递,就使用Thread类的构造方法
//public Thread(ThreadGroup group,Runnable target ,String name){}
Thread th1 = new Thread(tg, t1, "线程1") ;
Thread th2 = new Thread(tg, t1, "线程2") ;
//public final ThreadGroup getThreadGroup()返回该线程的线程组
ThreadGroup tg1 = th1.getThreadGroup() ;
ThreadGroup tg2 = th2.getThreadGroup() ;
System.out.println(tg1.getName());
System.out.println(tg2.getName());
tg.setDaemon(true) ;//将线程组中的所有的线程都设置为守护线程(后台线程)
}
private static void method1() {
// 获取多个线程所在的线程组名称
//创建资源对象
MyRunnable tg = new MyRunnable();
//创建线程类对象
Thread t1 = new Thread(tg);
Thread t2 = new Thread(tg);
//获取t1和t2所在的线程名称
//获取线程组对象
//public final ThreadGroup getThreadGroup()返回该线程所属的线程组
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
String name1 = tg1.getName();
String name2 = tg2.getName();
//子线程默认的线程组名称:main线程
System.out.println(name1);//main
System.out.println(name2);//main
//所有的线程它的默认线程组名称就是main
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
}
package threadgroup;
public class MyRunnable implements Runnable {
@Override
public void run() {
while(true){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
}
三.线程池
线程池的好处:节约成本,很多子线程调用完毕不会立即被回收掉,而是会回到线程池中被多次利用!
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
1)public static ExecutorService newFixedThreadPool(int nThreads)
Executors工厂类中的这个方法参数直接指定在当前线程池中有多少个线程
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
ExecutorsService :接口中的方法
2)Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
package threadpool;
public class MyRunnable implements Runnable {
@Override
public void run() {
while(true){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
package threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorDemo {
public static void main(String[] args) {
//创建线程池对象
//public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
//使用submit(Runnable target):提交多个任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
多线程实现的方式三(实际开发中很少用)
创建线程池对象 实现callable接口
package callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 多线程程序的实现方式3:(实际开发中很少用到!)
* public static ExecutorService newFixedThreadPool(int nThreads)
Executors工厂类中的这个方法参数直接指定在当前线程池中有多少个线程
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnabl e对象或者Callable对象代表的线程。它提供了如下方法
ExecutorsService :接口中的方法
<T> Future<T> submit(Callable<T> task)
* @author Apple
*/
public class CallableDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService threadpool = Executors.newFixedThreadPool(2);
//提交Callable任务
threadpool.submit(new MyCallable());
threadpool.submit(new MyCallable());
//结束线程池
threadpool.shutdown();
}
}
package callable;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
}
四.多线程中的匿名内部类的方式
格式:new 类名(具体类,抽象类),接口(){
重写/实现方法;
}
匿名内部类的本质:继承了该类或者是实现该接口的子类对象!
public class ThreadDemo {
public static void main(String[] args) {
//继承自Thread类
new Thread(){
@Override
public void run() {
//for循环
for(int x = 0 ; x <100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}.start() ;//启动线程
//Runnable接口的方式
new Thread(new Runnable() {
@Override
public void run() {
//for循环
for(int x = 0 ; x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+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()终止此计时器,丢弃所有当前已安排的任务
TimeTask是一个抽象类,自定义一个类实现它
练习:在指定的时间内删除当前项目下Demo文件夹中的内容
package timer;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) throws ParseException {
//创建Timer定时器
Timer t = new Timer();
//定义一个文本日期格式
String dateStr = "2017-12-8 16:50:00";
//解析成Date对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//解析方法
Date date = sdf.parse(dateStr);
//public void schedule(TimerTask task,Date time)安排在指定的时间执行指定的任务
//调用定时器在指定的时间内执行某个任务
t.schedule(new DeleteFolder(), date);
}
}
//删除Demo文件夹
class DeleteFolder extends TimerTask{
@Override
public void run() {
// 封装当前项目下的Demo文件
File srcFolder = new File("demo");
deleteFolder(srcFolder);
}
//递归删除
private void deleteFolder(File srcFolder) {
// 获取当前strFolder下面的所以文件及文件夹
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());
}
}
}