黑马程序员---多线程

2024-09-07 16:18
文章标签 程序员 黑马 多线程

本文主要是介绍黑马程序员---多线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

TraditionalTread 传统线程技术回顾

创建线程的两种方式:

thread3中,同时创建了Thread的子类和Runnable,那么会优先执行Thread的子类,因为Runnable的代码被当做参数传到了Thread类里,Thread子类的run方法又覆盖了父类的方法,所以会执行Thread子类的代码。

package cn.itcast.heima;public class TraditionalThread {public static void main(String[] args) {Thread thread1 = new Thread() {public void run() {int i = 9;while(i>0){System.out.println(i--+"....."+Thread.currentThread().getName());System.out.println(i--+"....."+this.getName());}}};thread1.start();Thread thread2 = new Thread(new Runnable() {public void run() {int i = 9;while(i>0){System.out.println(i--+"......"+Thread.currentThread().getName());}}});thread2.start();new Thread(new Runnable() {public void run() {System.out.println("Runnable..."+Thread.currentThread().getName());}}) {public void run() {System.out.println("Thread..."+Thread.currentThread().getName());}}.start();}
}

运行结果:

9.....Thread-0
8.....Thread-0
7.....Thread-0
6.....Thread-0
5.....Thread-0
4.....Thread-0
3.....Thread-0
2.....Thread-0
1.....Thread-0
0.....Thread-0
9......Thread-1
8......Thread-1
7......Thread-1
6......Thread-1
5......Thread-1
4......Thread-1
3......Thread-1
2......Thread-1
1......Thread-1
Thread...Thread-2


 

多线程机制会提高程序的运行效率吗?为什么会有多线程下载呢?

多线程并不会提高程序的运行效率,反而会降低,因为就好比一个人在一张桌子上做馒头和这个人在十张桌子上做馒头,哪个会快呢?多线程下载是因为要去抢占服务器端的资源。

 

定时器的应用 

ØTimer类
java.util.Timer
voidschedule(TimerTask task,Date time)
          安排在指定的时间执行指定的任务。
ØTimerTask类
java.util.TimerTask
public abstract class TimerTask   implements  Runnable

abstract  voidrun()
          此计时器任务要执行的操作。

package cn.itcast.heima;import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;public class TraditionalTimerTest {private static int count = 0;public static void main(String[] args) {class MyTimerTask extends TimerTask{@Overridepublic void run() {count = (count+1)%2;System.out.println("bombing");new Timer().schedule(new MyTimerTask(), 2000+2000*count);}}new Timer().schedule(new MyTimerTask(),2000);//过2秒炸,过4秒炸,过2秒炸,过4秒再炸,循环。。。//秒表while(true) {System.out.println(new Date().getSeconds());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}//星期一到星期五每天凌晨三点收邮件,周六周日不收。这种问题可以用工具:quartz 来解决。

 线程的同步互斥与通信 Synchronized

 题目:使用两个线程打印两个字符串:“lihuoming”、“bixiangdong”,不加同步的话,会出现不完整名字。

package cn.itcast.heima;public class TraditionalThreadSynchronized {public static void main(String[] args) {//1.碰到多线程要操作同一份数据的时候,就要考虑线程安全的问题。/*2.静态方法不可以new内部类的实例对象。* 因为内部类是可以访问外部类的成员变量的。* 而成员变量一定是外部类产生实例对象的时候才分配内存空间的。* 而静态方法的使用可以不new对象,* 这时候内部类要访问外部类的成员变量,* 现在都没有产生外部类实例对象,* 去哪找成员变量的值呢?*/new TraditionalThreadSynchronized().init();}public void init() {final Outputer out = new Outputer();new Thread(new Runnable() {@Overridepublic void run() {while(true) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}out.output("lihuoming");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {while(true) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}out.output2("bixiangdong");}}}).start();}class Outputer {public void output(String name) {int len = name.length();synchronized (this) {for (int i = 0; i < len; i++) {System.out.print(name.charAt(i));}System.out.println();}}public synchronized void output2(String name) {int len = name.length();synchronized (this) {for (int i = 0; i < len; i++) {System.out.print(name.charAt(i));}System.out.println();}}	}
}
//方法上的synchronized,锁是this对象。
//静态方法上的synchronized,锁是类的字节码对象。

线程的同步互斥的图文解说
 

 线程面试题:

子线程循环10次,主线程循环10次,子线程再循环10次,主线程再循环10次,如此循环50次。

package cn.itcast.heima;public class TraditionalThreadCommunication {public static void main(String[] args) {final Business business = new Business();new Thread(new Runnable() {@Overridepublic void run() {for(int j=1; j <= 50; j++) {business.sub(j);}}}).start();for(int j=1; j <= 50; j++) {business.main(j);}}
}class Business {private boolean bShouldSub = true;public synchronized void sub(int j) {//用while更健壮,防止伪唤醒。while(!bShouldSub) {try {this.wait();//这里synchronized的锁是什么对象,wait就用什么对象调用。} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 1; i <= 10; i++) {System.out.println("sub thread sequence of"+ i + ", loop of" + j);}bShouldSub = false;this.notify();//这里synchronized的锁是什么对象,notify就用什么对象调用。}public synchronized void main(int j) {while(bShouldSub) {try {this.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 1; i <= 10; i++) {System.out.println("main thread sequence of" + i+ ", loop of" + j);}bShouldSub = true;this.notify();}
}
//同步互斥的代码不是写在线程上,而是写在线程要访问的那个资源类的方法内部的。
//好处是以后任何线程要访问这个资源类,拿到以后就可以编译通过,很方便。


 线程范围内共享数据 ThreadLocal

java.lang
类 ThreadLocal<T>

ThreadLocal 相当于一个 Map 集合。以线程名为key,线程内数据为value。ThreadLocal可以省去key,保证每个线程操作的都是属于这个线程自己的数据。

访问某个变量(通过其getset 方法)的每个线程都有自己的局部变量。

构造方法摘要
ThreadLocal()
          创建一个线程本地变量。
 
方法摘要
 Tget()
          返回此线程局部变量的当前线程副本中的值。
protected  TinitialValue()
          返回此线程局部变量的当前线程的“初始值”。
 voidremove()
          移除此线程局部变量当前线程的值。
 voidset(T value)
          将此线程局部变量的当前线程副本中的值设置为指定值。

package cn.itcast.heima;import java.util.Random;public class ThreadLocalTest {private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();public static void main(String[] args) {for(int i=0;i<2;i++) {new Thread(new Runnable() {@Overridepublic void run() {int data = new Random().nextInt();//给全局静态变量data赋一个随机数。System.out.println(Thread.currentThread().getName()+ " has put data " + data);x.set(data);Student.getThreadInstance().setName("name"+data);Student.getThreadInstance().setAge(data);new A().get();new B().get();}}).start();}}static class A {public void get() {int data = x.get();System.out.println("A from " + Thread.currentThread().getName()+ " get data " + data);System.out.println("A from " + Thread.currentThread().getName()+ " get Student " + Student.getThreadInstance().getName()+ ", age" + Student.getThreadInstance().getAge());}}static class B {public void get() {int data = x.get();System.out.println("B from " + Thread.currentThread().getName()+ " get data " + data);System.out.println("B from " + Thread.currentThread().getName()+ " get Student " + Student.getThreadInstance().getName()+ ", age" + Student.getThreadInstance().getAge());}}static class Student {private Student() {}private static ThreadLocal<Student> threadLocalStu = new ThreadLocal<Student>();public static Student getThreadInstance() {Student stu = threadLocalStu.get();if(stu == null) {stu = new Student();threadLocalStu.set(stu);}return stu;}String name;int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}}
//线程范围内的共享变量。这个学生去了不同的线程有不同的名字和年龄,入乡随俗。

运行结果:

Thread-1 has put data -661697870
Thread-0 has put data 863811802
A from Thread-0 get data 863811802
A from Thread-1 get data -661697870
A from Thread-0 get Student name863811802, age863811802
A from Thread-1 get Student name-661697870, age-661697870
B from Thread-0 get data 863811802
B from Thread-1 get data -661697870
B from Thread-1 get Student name-661697870, age-661697870
B from Thread-0 get Student name863811802, age863811802

 多个线程之间共享数据

 面试题:设计四个线程,其中两个线程每次对 j 增加一,另外两个线程每次对 j 减一。

package cn.itcast.heima;
/*一、如果线程执行代码相同,就可以把共享数据封装在一个Runnable里。例如:卖票。
public class MultiThreadShareData {public static void main(String[] args) {MyData data = new MyData(100);new Thread(data).start();new Thread(data).start();}
}
class MyData implements Runnable {private int count;MyData(int count){this.count = count;}@Overridepublic void run() {while(count>0){sale();}}private synchronized void sale(){if(count>0){count--;System.out.println(Thread.currentThread().getName()+"---sale---"+count);}}
}*//*二、如果线程执行代码不一样,例如:一个加加,一个减减。* 可以将数据封装在一个外部类里,并提供两个同步方法:一个加加方法和一个减减方法。* 创建两个Runnable类,一个调用加加方法,一个调用减减方法。
public class MultiThreadShareData {public static void main(String[] args) {MyData data = new MyData();new Thread(new Decrease(data)).start();new Thread(new Increase(data)).start();}
}
class Decrease implements Runnable{private MyData data;Decrease(MyData data){this.data = data;}@Overridepublic void run() {// TODO Auto-generated method stubfor(int i=0;i<2;i++){data.decrease();}}
}
class Increase implements Runnable{private MyData data;Increase(MyData data){this.data = data;}@Overridepublic void run() {// TODO Auto-generated method stubfor(int i=0;i<2;i++){data.increase();}}
}
class MyData {private int j;public synchronized void decrease(){j--;System.out.println(Thread.currentThread().getName()+"---dec---"+j);}public synchronized void increase(){j++;System.out.println(Thread.currentThread().getName()+"---inc---"+j);}
}*//*三、
public class MultiThreadShareData {//位置一:外部类的成员变量,要想被静态方法中的内部类所使用需加static关键字修饰。//private static MyData data = new MyData();public static void main(String[] args) {//位置二:局部变量,要想被方法中内部类使用,需加final关键字修饰。final MyData data = new MyData();new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<2;i++){data.decrease();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<2;i++){data.increase();}}}).start();}
}
class MyData {private int j;public synchronized void decrease(){j--;System.out.println(Thread.currentThread().getName()+"---dec---"+j);}public synchronized void increase(){j++;System.out.println(Thread.currentThread().getName()+"---inc---"+j);}
}*//*四、四个线程共享一个数据j,两个线程j++,两个线程j--*/
public class MultiThreadShareData {private int j;public static void main(String[] args){MultiThreadShareData tt = new MultiThreadShareData();Dec dec = tt.new Dec();Inc inc = tt.new Inc();for(int i=0; i<2; i++){Thread t = new Thread(dec);t.start();t = new Thread(inc);t.start();}}private synchronized void dec(){j--;System.out.println(Thread.currentThread().getName()+"---dec---"+j);}private synchronized void inc(){j++;System.out.println(Thread.currentThread().getName()+"---inc---"+j);}class Dec implements Runnable{@Overridepublic void run() {dec();}}class Inc implements Runnable{@Overridepublic void run() {inc();}}
}

运行结果:

Thread-0---dec----1
Thread-1---inc---0
Thread-2---dec----1
Thread-3---inc---0

 Java5中的线程并发库

1.java.util.concurrent及子包的API帮助文档
Øconcurrent包的帮助文档页面,对并发库中涉及的内容有一个总体上的介绍
 
2.了解java.util.concurrent.atomic
Ø查看atomic包文档页下面的介绍
Ø通过如下两个方法快速理解atomic包的意义:
     •AtomicInteger类的boolean compareAndSet(expectedValue, updateValue);
     •AtomicIntegerArray类的intaddAndGet(int i, int delta);
Ø顺带解释volatile类型的作用,需要查看java语言规范。
 
3.了解java.util.concurrent.lock
Ø在下页通过案例详细讲解

例如: 

 java.util.concurrent.atomic
类 AtomicInteger

加了同步以后的整数。只有在n个线程共同访问该整数变量的时候使用。不涉及到多线程时不需要使用。

 

构造方法摘要
AtomicInteger()
          创建具有初始值 0 的新 AtomicInteger。
AtomicInteger(int initialValue)
          创建具有给定初始值的新 AtomicInteger。
 
方法摘要
 intaddAndGet(int delta)
          以原子方式将给定值与当前值相加。

 intdecrementAndGet()
          以原子方式将当前值减 1。

 int

incrementAndGet()
          以原子方式将当前值加 1。

 java.util.concurrent.atomic 包中的类:

AtomicInteger
AtomicIntegerArray (把整数放在数组中)
AtomicIntegerFieldUpdater (把整数放在成员变量的位置)

线程池 与 定时器

我们写一个tomcat服务器,难点就在于性能的快慢,线程的并发访问。为了每一个客户端连接进来服务器都可以接待,所以每一个客户端连接进来,我们都要启动一个线程。

 
1.线程池的概念与Executors类的应用
Ø创建固定大小的线程池
Ø创建缓存线程池//线程数可随需求变化
Ø创建单一线程池
 
2.关闭线程池
Øshutdown与shutdownNow的比较
 
3.用线程池启动定时器
Ø调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
Ø支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。
    TimeUnit 是一个时间单位的枚举,它里面包含很多时间单位。
 
package cn.itcast.heima;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ThreadPoolTest {public static void main(String[] args) {//ExecutorService threadPool = Executors.newFixedThreadPool(3);//固定线程的线程池。//ExecutorService threadPool = Executors.newCachedThreadPool();//有几个任务就创建几个线程的线程池。//单线程的线程池,它的特殊之处在于线程死了以后还会生成一个替补线程。永远保持有一个线程在那里。ExecutorService threadPool = Executors.newSingleThreadExecutor();for(int i=1;i<=6;i++){final int task = i; threadPool.execute(new Runnable(){@Overridepublic void run() {for(int j=1;j<=5;j++){System.out.println(Thread.currentThread().getName()+", is looping of "+j+", for task of "+task);}}});}System.out.println("All of 6 tasks hava committed.");threadPool.shutdown();//可被定时调度的线程池:ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);//线程池的延期调度:scheduledThreadPool.schedule(new Runnable(){@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+", bombing! task-1");}}, 5/*delay延期*/, TimeUnit.SECONDS);//线程池的固定频率调度:scheduledThreadPool.scheduleAtFixedRate(new Runnable(){@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+", bombing! task-2");}}, 6/*delay*/, 2/*period周期*/, TimeUnit.SECONDS);}}

java.util.concurrent
接口 ScheduledExecutorService 中:
 所有的 schedule 方法都接受相对 延迟和周期作为参数,而不是绝对的时间或日期。将以 Date 所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的某个Date 运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)

 Callable和Future

Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。

Callable要采用ExecutorSevicesubmit方法提交,返回的future对象可以取消任务。

{ [自我见解] Callable和Runnable差不多。是可被调用的。不同之处在于:实现Runnable的类要覆盖run方法。实现Callable的类要覆盖call方法。

Future是线程池中的线程在执行某个任务后返回的结果。使用execute执行任务没有返回值。使用submit执行任务返回Future。

Future的泛型类型和Callable的泛型类型一致。}

package cn.itcast.heima;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class CallableAndFuture {public static void main(String[] args) {ExecutorService threadPool = Executors.newSingleThreadExecutor();Future<String> future = threadPool.submit(new Callable<String>(){@Overridepublic String call() throws Exception {Thread.sleep(2000);return "hello";}});try {System.out.println("future: "+future.get());} catch (InterruptedException | ExecutionException e) {// TODO Auto-generated catch blocke.printStackTrace();}		}
}
运行结果:

future: hello

CompletionService:

用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。

              好比我同时种了几块地的麦子,然后就等待收割。收割时,则是哪块先成熟了,则先去收割哪块麦子。

package cn.itcast.heima;import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class CallableAndFuture {public static void main(String[] args) {ExecutorService threadPool2 = Executors.newFixedThreadPool(10);//定义10个线程的线程池。//将线程池交给收割机调度。CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool2);//提交10个任务for(int i=1; i<=10; i++) {final int sequence = i;//由收割机代替线程池提交任务。completionService.submit(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {Thread.sleep(new Random().nextInt(5000));//每个任务小睡的时间不一样,最多不超过5秒。return sequence;//返回任务的序号。}});}for(int i=1; i<=10; i++) {try {Future<Integer> future = completionService.take();//谁先熟了,先收割谁。System.out.println(future.get());} catch (InterruptedException | ExecutionException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}

Lock&Condition实现线程同步通信

1.Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

java.util.concurrent.locks 

接口Lock

所有已知实现类:ReentrantLock

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

package cn.itcast.heima;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockTest {public static void main(String[] args) {new LockTest().init();}public void init() {final Outputer out = new Outputer();new Thread(new Runnable() {@Overridepublic void run() {while(true) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}out.output("lihuoming");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {while(true) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}out.output("bixiangdong");}}}).start();}class Outputer {Lock lock = new ReentrantLock();public void output(String name) {int len = name.length();lock.lock();//以防万一在执行里面程序的时候抛异常了,这个方法就退出了,不会执行到unlock方法,其他线程就进不去了。try{for (int i=0; i<len; i++) {System.out.print(name.charAt(i));}System.out.println();}finally{lock.unlock();}}}
}
运行结果:

lihuoming

bixiangdong

lihuoming

bixiangdong

bixiangdong

lihuoming

bixiangdong

lihuoming

bixiangdong

lihuoming

bixiangdong

lihuoming

bixiangdong


2.读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

java.util.concurrent.locks 接口ReadWriteLock
所有已知实现类:ReentrantReadWriteLock

ReentrantReadWriteLock.ReadLock 
          readLock() 方法返回的锁。

ReentrantReadWriteLock.WriteLock 
          writeLock() 方法返回的锁。

package cn.itcast.heima;import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockTest {//static Queue queue = new Queue();public static void main(String[] args) {final Queue queue = new Queue();for(int i=0;i<3;i++){ //共开启3个读线程,3个写线程。new Thread(){public void run(){while(true){ //每开启一个线程,它就在不停的读。queue.get();}}}.start();new Thread(){public void run(){while(true){ //每开启一个线程,它就在不停的写。queue.set(new Random().nextInt(10000));}}}.start();}}
}
//因为读和写要操作的是同一个数据,那么读和写方法就得在同一个类里。
class Queue {private Object data = null;private ReadWriteLock rwl = new ReentrantReadWriteLock();public void get(){rwl.readLock().lock();try {System.out.println(Thread.currentThread().getName()+" be ready to read data!");Thread.sleep((long)(Math.random()*1000));System.out.println(Thread.currentThread().getName()+" hava read data "+data+" !");}catch (InterruptedException e) {e.printStackTrace();}finally {rwl.readLock().unlock();}}public void set(Object data){rwl.writeLock().lock();try {System.out.println(Thread.currentThread().getName()+" be ready to write data!!");Thread.sleep((long)(Math.random()*1000));this.data = data;System.out.println(Thread.currentThread().getName()+" hava written data "+data+" !!");}catch (InterruptedException e) {e.printStackTrace();}finally {rwl.writeLock().unlock();}}
}


看API文档里的一段代码:
java.util.concurrent.locks 
类 ReentrantReadWriteLock 里面的:

class CachedData {Object data;volatile boolean cacheValid;ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();void processCachedData() {rwl.readLock().lock();if (!cacheValid) {// Must release read lock before acquiring write lockrwl.readLock().unlock();rwl.writeLock().lock();// Recheck state because another thread might have acquired//   write lock and changed state before we did.if (!cacheValid) {data = ...cacheValid = true;}// Downgrade by acquiring read lock before releasing write lockrwl.readLock().lock();rwl.writeLock().unlock(); // Unlock write, still hold read}use(data);rwl.readLock().unlock();}}


一个面试题:写一个缓存类:

package cn.itcast.heima;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;/* 缓存类:* 你要找数据的时候先别找数据库,先找我,如果我没有你再找数据库。*/public class CacheDemo {//先定义一个Map,用来存放缓存数据。用户通过对象的名字进行查找。private Map<String,Object> cache = new HashMap<String,Object>();public static void main(String[] args) {}/*如果A、B、C三个线程同时来这里查,走到value==null的时候,要查几遍数据库啊,所以这里应该加上锁。* 多个读的时候,应该可以并发的,不会造成数据的破坏,不会冲突。所以它们进来读的时候,都给它们上读锁。* 但是一旦发现没有数据可读,就要有一个线程负责去读数据了,* 这时候它去数据库把数据读出来了,在它填充数据的时候就要上写锁。* 写之间不能被读走,所以这时候上写锁。* 数据填充完了,把写锁释放掉,再给它还原成为读锁。* 这样就读与写、写与写之间进行了互斥。又可以有多个并发的读。系统性能就很高了。*/private ReadWriteLock rwl = new ReentrantReadWriteLock();public Object getData(String key){rwl.readLock().lock();Object value = null;try{value = cache.get(key);if(value == null) {rwl.readLock().unlock(); //假如三个线程都走到这里了rwl.writeLock().lock(); //只有第一个线程可以进入,等这个线程将写锁释放掉,其它两个线程又进入。try{if(value==null){ //这里为了避免重复,加上一句判断。就算其它两个线程接着想来写的时候,它们发现这个数据不用再写了。value = "aaaa";//实际失去queryDB();}}finally{rwl.writeLock().unlock();}rwl.readLock().lock();}}finally{rwl.readLock().unlock();}return value;}
}


3.在等待Condition时,允许发生虚假唤醒,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

package cn.itcast.heima;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*子线程循环10次,主线程循环10次,子线程再循环10次,主线程再循环10次,如此循环50次。*/public class ConditionCommunication {public static void main(String[] args) {final Business business = new Business();new Thread(new Runnable() {@Overridepublic void run() {for(int j=1; j <= 50; j++) {business.sub(j);}}}).start();for(int j=1; j <= 50; j++) {business.main(j);}}//静态方法不能创建内部类的实例,所以在类前加static。它的作用类似于外部类。它现在的完整名称是ConditionCommunication.Business。static class Business {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();private boolean bShouldSub = true;public void sub(int j) {lock.lock();try{while(!bShouldSub){try {condition.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 1; i <= 10; i++) {System.out.println("sub thread sequence of"+ i + ", loop of" + j);}bShouldSub = false;condition.signal();}finally{lock.unlock();}}public synchronized void main(int j) {lock.lock();try{while(bShouldSub){try {condition.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 1; i <= 10; i++) {System.out.println("main thread sequence of" + i+ ", loop of" + j);}bShouldSub = true;condition.signal();}finally{lock.unlock();}}}
}//本例中主要演示:Condition的await和signal方法。
//必须在synchronized同步监视器内部才可以用wait和notify。

4.一个锁内部可以有多个 Condition ,即有多路等待和通知,可以参看 jdk1.5 提供的 Lock Condition 实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个 Condition ,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)

 

package cn.itcast.heima;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull  = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock();try{while(count == items.length) notFull.await();items[putptr] = x; if (++putptr == items.length) putptr = 0;++count;notEmpty.signal();} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try{while(count == 0) notEmpty.await();Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0;--count;notFull.signal();return x;} finally {lock.unlock();}} 
}
package cn.itcast.heima;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*三个线程,老大循环10次,老二循环10次,老三循环10次,再老大循环10次...如此循环50次。*/public class ThreeConditionCommunication {static Business business = new Business();public static void main(String[] args) {new Thread(new Runnable(){@Overridepublic void run() {for(int j=1; j <= 50; j++) {business.first(j);}}}).start();new Thread(new Runnable() {@Overridepublic void run() {for(int j=1; j <= 50; j++) {business.second(j);}}}).start();new Thread(new Runnable() {@Overridepublic void run() {for(int j=1; j<=50;j++) {business.third(j);}}}).start();}//静态方法不能创建内部类的实例,所以在类前加static。它的作用类似于外部类。它现在的完整名称是ConditionCommunication.Business。static class Business {Lock lock = new ReentrantLock();Condition condition1 = lock.newCondition();Condition condition2 = lock.newCondition();Condition condition3 = lock.newCondition();private int runptr = 1;public void first(int j) {lock.lock();try{while(runptr!=1){try {condition1.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 1; i <= 10; i++) {System.out.println("first thread sequence of"+ i + ", loop of" + j);}runptr = 2;condition2.signal();}finally{lock.unlock();}}public void second(int j) {lock.lock();try{while(runptr!=2){try {condition2.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 1; i <= 10; i++) {System.out.println("second thread sequence of" + i+ ", loop of" + j);}runptr = 3;condition3.signal();}finally{lock.unlock();}}public void third(int j) {lock.lock();try{while(runptr!=3){try {condition3.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for(int i=1;i<=10;i++){System.out.println("third thread sequence of" + i+ ", loop of" + j);}runptr = 1;condition1.signal();}finally{lock.unlock();}}}
}//老大只可以唤醒老二,老二只可以唤醒老三,老三只可以唤醒老大。


Semaphore实现信号灯

java.util.concurrent 类 Semaphore

构造方法摘要
Semaphore(int permits) 
          创建具有给定的许可数和非公平的公平设置的 Semaphore
Semaphore(int permits, boolean fair) 
          创建具有给定的许可数和给定的公平设置的 Semaphore
方法摘要
 voidacquire() 
          从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
  intavailablePermits() 
          返回此信号量中当前可用的许可数。
 voidrelease() 
          释放一个许可,将其返回给信号量。

1.Semaphore可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。

   (1) Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。

package cn.itcast.heima;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;public class SemaphoreTest {public static void main(String[] args) {ExecutorService service = Executors.newCachedThreadPool();final  Semaphore sp = new Semaphore(3);for(int i=0;i<10;i++){Runnable runnable = new Runnable(){@Overridepublic void run() {try {sp.acquire();//这里} catch (InterruptedException e1) {e1.printStackTrace();}System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有" + (3-sp.availablePermits()) + "个并发");try {Thread.sleep((long)(Math.random()*10000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程" + Thread.currentThread().getName() + "即将离开");	sp.release();//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元System.out.println("线程" + Thread.currentThread().getName() + "已离开,当前已有" + (3-sp.availablePermits()) + "个并发");					}};service.execute(runnable);//此cached线程池,是往线程池里丢几个runnable,就开启几个线程。		}}}

运行结果:

线程pool-1-thread-2进入,当前已有3个并发

线程pool-1-thread-1进入,当前已有3个并发

线程pool-1-thread-3进入,当前已有3个并发

线程pool-1-thread-1即将离开

线程pool-1-thread-1已离开,当前已有2个并发

线程pool-1-thread-4进入,当前已有3个并发

线程pool-1-thread-4即将离开

线程pool-1-thread-4已离开,当前已有2个并发

线程pool-1-thread-5进入,当前已有3个并发

线程pool-1-thread-2即将离开

线程pool-1-thread-2已离开,当前已有2个并发

线程pool-1-thread-6进入,当前已有3个并发

线程pool-1-thread-3即将离开

线程pool-1-thread-3已离开,当前已有2个并发

线程pool-1-thread-7进入,当前已有3个并发

线程pool-1-thread-5即将离开

线程pool-1-thread-5已离开,当前已有2个并发

线程pool-1-thread-8进入,当前已有3个并发

线程pool-1-thread-6即将离开

线程pool-1-thread-6已离开,当前已有2个并发

线程pool-1-thread-9进入,当前已有3个并发

线程pool-1-thread-7即将离开

线程pool-1-thread-7已离开,当前已有2个并发

线程pool-1-thread-10进入,当前已有3个并发

线程pool-1-thread-9即将离开

线程pool-1-thread-9已离开,当前已有2个并发

线程pool-1-thread-8即将离开

线程pool-1-thread-8已离开,当前已有1个并发

线程pool-1-thread-10即将离开

线程pool-1-thread-10已离开,当前已有0个并发


     (2) 另外等待的 5 个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造 Semaphore 对象时传入的参数选项。

2.单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了,再由另一个线程释放,这可应用于死锁恢复的一些场合。



这篇关于黑马程序员---多线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145540

相关文章

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度

Java 多线程的基本方式

Java 多线程的基本方式 基础实现两种方式: 通过实现Callable 接口方式(可得到返回值):

JAVA- 多线程

一,多线程的概念 1.并行与并发 并行:多个任务在同一时刻在cpu 上同时执行并发:多个任务在同一时刻在cpu 上交替执行 2.进程与线程 进程:就是操作系统中正在运行的一个应用程序。所以进程也就是“正在进行的程序”。(Windows系统中,我们可以在任务管理器中看 到进程) 线程:是程序运行的基本执行单元。当操作系统执行一个程序时, 会在系统中建立一个进程,该进程必须至少建立一个线

多线程篇(阻塞队列- LinkedBlockingDeque)(持续更新迭代)

目录 一、LinkedBlockingDeque是什么 二、核心属性详解 三、核心方法详解 addFirst(E e) offerFirst(E e) putFirst(E e) removeFirst() pollFirst() takeFirst() 其他 四、总结 一、LinkedBlockingDeque是什么 首先queue是一种数据结构,一个集合中

多线程篇(阻塞队列- LinkedBlockingQueue)(持续更新迭代)

目录 一、基本概要 1. 构造函数 2. 内部成员 二、非阻塞式添加元素:add、offer方法原理 offer的实现 enqueue入队操作 signalNotEmpty唤醒 删除线程(如消费者线程) 为什么要判断if (c == 0)时才去唤醒消费线程呢? 三、阻塞式添加元素:put 方法原理 图解:put线程的阻塞过程 四、非阻塞式移除:poll方法原理 dequ

LabVIEW程序员是怎样成长为大佬

成为一名LabVIEW编程领域的“大佬”需要时间、实践、学习和解决复杂问题的经验。尽管LabVIEW作为一种图形化编程语言在初期可能相对容易上手,但要真正成为精通者,需要在多个层面上深入理解。以下是LabVIEW程序员如何逐步成长为“大佬”的路径: 1. 打好基础 LabVIEW的大佬们通常在初期会打下非常坚实的基础,理解LabVIEW编程的核心概念,包括: 数据流编程模型:Lab

spring笔记 多线程的支持

spring的工作机制 136  属性编辑器 140 spring事件的体系结构 168 Bean间的关系 109 继承 依赖 引用     Bean的继承          1 为了简化初始化的属性注入;          2 子Bean和父Bean相同的属性值,使用子Bean的     Bean的依赖 Srping控制相互依赖的Bean之间,属性注入的顺序,防止出错  depend-on

程序员必备心理学——心流

心理学之心流 前言一、“心流”是什么?二、心流的好处二、如何进入心流心流状态的四个阶段第一个阶段:挣扎第二个阶段:放松第三个阶段:心流第四个阶段:巩固 进入心流的技巧 总结题外话 前言 你是否常常感觉自己明明学习了一整天,但是就是感觉没有太多的收获。这个时候除了你的学习方向等问题之外,也可能是你的学习方法太低效了。作者本人就经常有这种情况,好在偶然间在b站刷到一个大佬的这个心

【编程底层思考】详解Java的JUC多线程并发编程底层组件AQS的作用及原理

Java中的AbstractQueuedSynchronizer(简称AQS)是位于java.util.concurrent.locks包中的一个核心组件,用于构建锁和其他同步器。AQS为实现依赖于FIFO(先进先出)等待队列的阻塞锁和相关同步器提供了一套高效、可扩展的框架。 一、AQS的作用 统一同步状态管理:AQS提供了一个int类型的成员变量state,用于表示同步状态。子类可以根据自己