这一块的内容主要是有关死锁、线程间通信、线程组、线程池以及定时器的内容。
这一部分的内容,如果想搞得比较明白,最好先看一下上一篇大数据进阶25的内容。地址如下: 大数据进阶25-多线程
Lock
在上一篇大数据 进阶25-多线程 里面,虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock
Lock(接口): void lock() 获得锁,加锁 void unlock() 释放锁 子类: ReentrantLock 这也是解决线程同步安全的第二种方式
class SellTick1 implements Runnable{
private 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();
}
}
}
}
public class SellTicketDemo1 {
public static void main(String[] args) {
SellTick1 s = new SellTick1();
Thread t1 = new Thread(s, "窗口一");
Thread t2 = new Thread(s, "窗口二");
Thread t3 = new Thread(s, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
死锁
同步的弊端: - 效率低 - 如果出现了同步嵌套,就容易产生死锁问题 死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
class MyLock{
public static final Object lockA = new Object();
public static final Object lockB = new Object();
}
class DeadLock extends Thread{
private boolean flag;
public DeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (MyLock.lockA){
System.out.println("if lockA");
synchronized (MyLock.lockB){
System.out.println("if lockB");
}
}
}else {
synchronized (MyLock.lockB){
System.out.println("else lockB");
synchronized (MyLock.lockA){
System.out.println("else lockA");
}
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock foreigner = new DeadLock(true);
DeadLock chinese = new DeadLock(false);
foreigner.start();
chinese.start();
}
}
线程间通信
我们之前写的电影票程序不是特别符合真是情况,所以我们在这之上对其做一个改进,引入线程间通信 示例代码1:
class Student1{
String name;
int age;
}
class SetThread implements Runnable{
private Student1 s;
public SetThread(Student1 s){
this.s = s;
}
@Override
public void run() {
s.name = "A";
s.age = 21;
}
}
class GetThread implements Runnable{
private Student1 s;
public GetThread(Student1 s){
this.s = s;
}
@Override
public void run() {
System.out.println(s.name+"---"+s.age);
}
}
public class StudentDemo1 {
public static void main(String[] args) {
Student1 s = new Student1();
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
示例代码2:
class Student2{
String name;
int age;
}
class SetThread2 implements Runnable{
private Student2 s;
private int x = 0;
public SetThread2(Student2 s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (Student2.class){
if(x%2==0){
s.name = "A";
s.age = 21;
}else {
s.name = "B";
s.age = 12;
}
x++;
}
}
}
}
class GetThread2 implements Runnable{
private Student2 s;
public GetThread2(Student2 s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (Student2.class){
System.out.println(s.name+"---"+s.age);
}
}
}
}
public class StudentDemo2 {
public static void main(String[] args) {
Student2 s = new Student2();
SetThread2 st = new SetThread2(s);
GetThread2 gt = new GetThread2(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
等待唤醒机制
问题3:虽然我们的数据是安全的,但是,每一次我们都是一次获取一大片数据,我想依次一个一个的输出,如何实现呢? 通过Java提供的等待唤醒机制解决
等待唤醒: Object类中提供了三个方法:
- wait() 等待
- notify() 唤醒正在等待对象监视器的单个进程
- notifyAll() 唤醒正在等待对象监视器的所有进程
为什么这些方法不定义在Thread类中呢? 这些方法的调用必须由锁对象调用,而我们刚刚使用的是synchronized里面的锁对象与调用wait和notify的对象不一致,会出现异常IllegalMonitorStateException,只要锁对象一致,就不会出错。这个锁对象可以是任意对象,而你不确定是哪一个对象,但是我们知道所有类的父类是Object,所以,这些方法就定义在Object中
class Student3{
String name;
int age;
boolean flag;
}
class SetThread3 implements Runnable{
private Student3 s;
private int x = 0;
public SetThread3(Student3 s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (Student3.class){
if (s.flag){
try {
Student3.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x%2==0){
s.name = "A";
s.age = 21;
}else {
s.name = "B";
s.age = 12;
}
x++;
s.flag = true;
Student3.class.notify();
}
}
}
}
class GetThread3 implements Runnable{
private Student3 s;
public GetThread3(Student3 s){
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (Student3.class) {
if (!s.flag) {
try {
Student3.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
s.flag = false;
Student3.class.notify();
}
}
}
}
public class StudentDemo3 {
public static void main(String[] args) {
Student3 s = new Student3();
SetThread3 st = new SetThread3(s);
GetThread3 gt = new GetThread3(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
线程转换的几种情形
线程组
Java可以使用 ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
简单来说:线程组就是把多个线程组合到一起
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadGroupDemo {
public static void main(String[] args) {
fun1();
}
private static void fun2() {
ThreadGroup tg = new ThreadGroup("第一个线程组");
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "A");
Thread t2 = new Thread(my, "B");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
tg.setDaemon(true);
t1.start();
t2.start();
}
private static void fun1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "A");
Thread t2 = new Thread(my, "B");
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg1.getName());
System.out.println(tg2.getName());
System.out.println(tg1.getMaxPriority());
}
}
使用线程组写一个最终代码 1、把Student的成员变量都变成私有的 2、把生产和消费的操作封装成两个方法,并加入同步和唤醒机制 3、生产和消费的线程只需要调用方法即可
class Student4{
private String name;
private int age;
private boolean flag;
public synchronized void set(String name,int age){
if(this.flag){
try {
this.wait();
}catch (Exception e){
e.printStackTrace();
}
}
this.name = name;
this.age = age;
this.flag = true;
this.notify();
}
public synchronized void get(){
if(!this.flag){
try {
this.wait();
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println(this.name+"---"+this.age);
this.flag = false;
this.notify();
}
}
class SetThread4 implements Runnable{
private Student4 s;
private int x = 0;
public SetThread4(Student4 s){
this.s = s;
}
@Override
public void run() {
while (true){
if(x%2==0){
s.set("A",21);
}else{
s.set("B",12);
}
x++;
}
}
}
class GetThread4 implements Runnable{
private Student4 s;
public GetThread4(Student4 s){
this.s = s;
}
@Override
public void run() {
while (true){
s.get();
}
}
}
public class StudentDemo4 {
public static void main(String[] args) {
Student4 s = new Student4();
SetThread4 st = new SetThread4(s);
GetThread4 gt = new GetThread4(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
线程池
线程池的好处: 线程池的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用 如何实现线程池: 1、创建一个线程池对象,控制要创建几个线程对象 public static ExecutorServe newFixedThreadPool(int nThreads) 创建一个线程池,指定线程池大小 2、哪些线程可以放到线程池执行 可以执行Runnable对象或者Callable对象代表的线程 3、如何执行 Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 Future submit(Callable task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。 4、想结束任务怎么办 void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但是不会接受任何新任务
class MyRunnable2 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
public class ExecutorsDemo {
public static void main(String[] args) {
ExecutorService fixpool = Executors.newFixedThreadPool(2);
fixpool.submit(new MyRunnable2());
fixpool.submit(new MyRunnable2());
fixpool.shutdown();
}
}
多线程的第三种实现方式
第三种方式:实现Callable接口,必须和线程池结合使用
class MyCallable implements Callable {
@Override
public Object call() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
return null;
}
}
public class CallableDemo {
public static void main(String[] args) {
ExecutorService fixpool = Executors.newFixedThreadPool(2);
MyCallable c1 = new MyCallable();
MyCallable c2 = new MyCallable();
fixpool.submit(c1);
fixpool.submit(c2);
fixpool.shutdown();
}
}
匿名内部类的方式实现多线程
匿名内部类的格式: new 类名或者接口名(){ 重写方法 }; 本质:该类或者是接口的子类对象
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println("实现Runnable接口2:"+Thread.currentThread().getName()+
"---"+i);
}
}
}){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("继承自Thread类2:"+Thread.currentThread().getName()+
"---"+i);
}
}
}.start();
}
定时器
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行,在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
Timer:定时 Timer() 创建一个新的计时器 void schedule(TimerTask task,long delay)在指定的延迟之后安排指定的任务执行 void schedule(TimerTask task,long delay,long period)在指定的延迟之后开始,重写执行 void cancel()终止此计数器,丢弃任何当前计划的任务 TimerTask:任务
class MyTask extends TimerTask{
private Timer timer;
public MyTask(Timer timer){
this.timer = timer;
}
@Override
public void run() {
System.out.println("计时结束" );
timer.cancel();
}
}
public class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new MyTask(timer),3000);
}
}
感谢阅读,我是啊帅和和,一位大数据专业即将大四学生,祝你快乐。
|