线程通信
传统的线程通信
传统的线程通信中可以使用Object中的wait()和notify()、notifyAll()方法,这三个方法并不属于Thread类,而是属于Objecet类。同时,三个方法的调用必须使用同步监视器的。那么就分成两种情况,同步方法和同步代码块。
void wait (long timeout)
//在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。
void notify ()
//唤醒在此对象监视器上等待的单个线程。 如果所有线程都在同步监视器中等待,随机选择一个唤醒。
void notifyAll ()
//唤醒在此对象监视器上等待的所有线程。
举例:
public class SetThread extends Thread{
private Student student;
int i=0;
//不停地存学生对象
public SetThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true){
//同步代码块
synchronized (student){
if(student.flag){
//表示有资源了,生成者就等待唤醒
try {
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产了资源
if (i % 2 == 0) {
student.name = "张三";
student.age = 23;
} else {
student.name = "李四";
student.age = 24;
}
//flag置为真,消费者去消费,生产者等待
student.flag=true;
student.notify(); //唤醒正在等待的线程,唤醒之后,他们还要再次争抢。
}
i++;
}
}
}
使用Condition控制线程通信
如果程序不使用synchronized关键字保持同步,直接使用Lock保持同步,这时候就不存在隐式的同步监视器,就不能使用Object自带的wait()和notify()notifyAll()方法,这时候Java提供了Condition类来保证通信。它也提供三种方法:
void await():当前线程等待,直到其他线程调用singal()和singalAll()方法唤醒。
void singal():唤醒等待的单个线程,也是如果所有线程都等待,随意唤醒一个。
void singalAll():唤醒所有等待线程。
两者差距不大,以下仅展示重写的run()方法。
public void run() {
while (true){
//同步代码块
lock.lock(student){
if(student.flag){
//表示有资源了,生成者就等待唤醒
try {
student.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产了资源
if (i % 2 == 0) {
student.name = "张三";
student.age = 23;
} else {
student.name = "李四";
student.age = 24;
}
//flag置为真,消费者去消费,生产者等待
student.flag=true;
student.signal(); //唤醒正在等待的线程,唤醒之后,他们还要再次争抢。
}
i++;
finally{
lock.unlock();
//释放锁
}
}
}
}
线程池
系统每次启动一个新线程成本较高,一般情况下会创建一个线程池,适用于线程多且短时间运行的情况,也就是伪IO情况。
线程池在系统启动时创建大量空闲的线程,程序将一个Runnable对象或Callable对象传入线程池中,线程池启动一个线程来执行它的的run()或start()方法。
public static ExecutorService newCachedThreadPool(): //根据任务的数量来创建线程对应的线程个数
public static ExecutorService newFixedThreadPool(int nThreads):
//固定初始化几个线程
public static ExecutorService newSingleThreadExecutor(): //初始化一个线程的线程池,这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
举例:
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//根据任务的数量来创建线程对应的线程个数
ExecutorService executorService = Executors.newCachedThreadPool();
MyRunnable myRunnable = new MyRunnable();
executorService.submit(myRunnable);
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
MyCallable myCallable = new MyCallable();
Future<Integer> future=executorService.submit(myCallable);
Integer integer = future.get();
System.out.println(integer);
//关闭线程池
executorService.shutdown();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了");
}
}
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int num=10+10+10;
return num;
}
}
多线程-----定时器
定时器可以调度多个定时任务且以后台线程的方式执行。java中,可以通过Timer和TimerTask类实现丁一调度功能。常用方法:
public Timer()
public void schedule(TimerTask task, long delay):
public void schedule(TimerTask task,long delay,long period);
public void schedule(TimerTask task, Date time):
public void schedule(TimerTask task, Date firstTime, long period):
TimerTask:定时任务
public abstract void run()
public boolean cancel()
举例:定时删除文件夹及文件
public class MyTest {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
DelFolderTask myTimerTask2 = new DelFolderTask(timer);
String dateStr = "2020-08-21 14:53:00";
timer.schedule(myTimerTask2, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateStr));
}
}
class DelFolderTask extends TimerTask{
private Timer timer;
public DelFolderTask(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
File file = new File("C:\\Users\\ShenMouMou\\Desktop\\demo");
delFolder(file);
//取消定时器
timer.cancel();
}
private void delFolder(File file) {
File[] files = file.listFiles();
for (File f : files) {
if (f.isFile()) {
f.delete();
}else{
delFolder(f);
}
}
file.delete();
}
}
线程相关类
线程安全类:ThreadLocal类
局部变量类ThreadLocal,就是为每一个使用该变量的线程都提供一个变量值的副本,使每个线程可以独立的改变自己的副本,不与其它的副本产生冲突。方法非常简单:
public T get():返回此线程局部变量中的当前线程副本的值。
public void remove:删除当前线程副本的值
public void set(T value):设置当前线程副本中的值。
//与同步锁不同,ThreadLocal将需要并发访问的资源赋值多份,每个线程拥有一份资源,每个线程都拥有自己的资源副本,所以就不存在同步的问题。所以编程的时候可以吧不安全的变量直接封装到ThreadLocal中,
//局部变量类并不能替代同步锁,只是说涉及线程通信的问题使用同步锁,只是隔离多个线程的共享,避免共享冲突时可以使用局部变量类封装。
包装线程不安全的集合:
ArrayList、Linkedlist、HashSet、TreeSet、HashMap、TreeMap都是线程不安全的集合,如果程序总有多个线程访问这些集合的时候可以用Collection类方法把它们包装成安全的集合。举例:
ArrayList<String> list = new ArrayList<>();
//把线程不安全的集合转换为线程安全的集合
List<String> strings = Collections.synchronizedList(list);
当然,Java 5开始就提供一些线程安全的类,例如ConcurrentMap、CopyOnWriteArrayList、ConcurrentSkiplistSetConcurrentHashMap等,大概分为两类,Concurrent开头表示支持并发访问的集合,支持多个线程访问写入,在默认情况下ConcurrentHashMap支持16个线程并发写入,当然可以扩容,通过设置ConcurrencyLevel构造参数来设置更多的并发写入线程。
注意:ConcurrentHashMap这类支持并发访问的集合在使用迭代器的时候,该迭代器可能反映不出创建迭代器之后的修改,且不报错。