这一块的内容主要是有关死锁、线程间通信、线程组、线程池以及定时器的内容。
这一部分的内容,如果想搞得比较明白,最好先看一下上一篇大数据进阶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);
}
}
/*
学生类:Student
设置学生信息类:SetStudent(生产者)
获取学生信息类:GetStudent(消费者)
测试类
问题1:按照思路去写,发现每次出现的都是null---0
原因:我们在每个线程中都创建了新对象,而我们要求的是设置学生信息和获取学生信息的对象应该是同一个
如何实现:在外界把学生对象创建出来,通过构造方法传递给其他类
*/
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);
}
}
}
}
/*
学生类:Student
设置学生信息类:SetStudent(生产者)
获取学生信息类:GetStudent(消费者)
测试类
问题2:为了数据好看一些,更容易出现结果,我们加入了循环和判断,给出不同的值,但
又出现了新的问题
1、同一个数据出现了多次
2、姓名和年龄不匹配
原因:
1、同一个数据出现了多次
CPU的一点点时间片的执行权,就足够执行很多次
2、姓名和年龄不匹配
线程运行的随机性
线程安全问题:
1、是否是多线程环境 是
2、是否又共享数据 是
3、是否有多条语句操作共享数据 是
解决:
加锁
注意:
1、不同种类的线程都要加锁
2、不同种类的线程加的锁必须是同一把
*/
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;//默认情况是没有数据的,默认是false,如果是true,说明由数据
}
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();
// fun2();
}
private static void fun2() {
//创建新的线程组
//ThreadGroup(String name)
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 getThreadGroup()
//返回此线程所属的线程组
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
//String getName()
//返回此线程组的名称
System.out.println(tg1.getName());//main
System.out.println(tg2.getName());//main
System.out.println(tg1.getMaxPriority());//10
//通过验证发现,线程默认情况下属于main线程组
}
}
使用线程组写一个最终代码
1、把Student的成员变量都变成私有的
2、把生产和消费的操作封装成两个方法,并加入同步和唤醒机制
3、生产和消费的线程只需要调用方法即可
class Student4{
private String name;
private int age;
private boolean flag;//默认是false
//设置数据,相当于生产者,没有数据(false)就生产,有数据(true)就等待
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();
}
//获取数据,相当于消费者,没有数据(false)就等待(wait),有数据(true)就消费
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);
//使用匿名内部类的形式将Runnable对象或者Callable对象放进去并执行
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) {
//继承Thread类来实现多线程
// new Thread(){
// @Override
// public void run() {
// for(int i=0;i<10;i++){
// System.out.println("继承Thread类:"+Thread.currentThread().getName()+
// "---"+i);
// }
// }
// }.start();
//
// //实现Runnable接口
// new Thread(new Runnable() {
// @Override
// public void run() {
// for(int i=0;i<10;i++){
// System.out.println("实现Runnable接口:"+Thread.currentThread().getName()+
// "---"+i);
// }
// }
// }).start();
//执行的是Thread本身的fun方法,
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);
//void schedule(TimerTask task,long delay,long period)
//在指定的延迟之后开始 ,重新执行固定延迟执行的指定任务。
timer.schedule(new MyTask(timer),3000);
}
}
感谢阅读,我是啊帅和和,一位大数据专业即将大四学生,祝你快乐。