这里写目录标题
一、线程安全同步问题1
1、问题一:
分析:
共享数据:同一个学生对象Student
生产者:SetThread给学生对象的成员变量进行赋值操作
消费者:GetThread获取学生对象的成员变量的值
测试者:StudentDemo创建两个线程,一个设置值线程,一个获取值线程
问题:
按照我们分析的步骤去写代码,运行后发现,每一次的结果都是null---0,这是必然的。
原因:设置值的线程与获取值的线程的操作的不是同一个学生对象
如何解决呢?
目的:让设置值的线程与获取值的线程的操作的是同一个学生对象
解决方式:在外界创建出一个学生对象,通过参数的形式让两个线程操作同一个对象
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
//创建自定义类对象
SetThread setThread = new SetThread(s);
GetThread getThread = new GetThread(s);
//使用Thread类创建线程对象,将自定义对象作为参数传递
Thread proThread = new Thread(setThread);
Thread cusThread = new Thread(getThread);
proThread.setPriority(1);
cusThread.setPriority(10);
//启动线程
proThread.start();
cusThread.start();
}
}
public class SetThread implements Runnable{
private Student s;
SetThread(Student student){
s = student;
}
@Override
public void run() {
// Student s = new Student();
s.name = "王宇";
s.age = 18;
}
}
public class GetThread implements Runnable{
private Student s;
GetThread(Student student){
s = student;
}
@Override
public void run() {
// Student s = new Student();
System.out.println(s.name+"---"+s.age);
}
}
public class Student {
String name;
int age;
}
2、问题二:
问题二:我们为了方便观察更好的结果来修改,于是我们加入了循环和判断,不同的判断给同一个学生对象赋不同的值
但是呢,我们在运行的时候出现新的问题
1、同一条数据出现了多次
2、姓名和年龄与代码中不匹配
原因:
1、同一条数据出现了多次
由于CPU小小的时间片就可以执行很多次
2、姓名和年龄与代码中不匹配
这是由于线程的执行具有随机性导致的
产生线程同步安全问题:
1、是否存在多线程环境 是
2、是否存在共享数据 是
3、是否有很多条语句操作着共享数据 是
既然都满足,那就说明线程不安全
解决方案:
1、关键字加锁
2、Lock锁
注意事项:
1、不同种类的线程对象都需要加锁
2、不同种类的线程对象拥有的必须是同一把锁
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
Lock lock = new ReentrantLock();
//创建自定义类对象
SetThread setThread = new SetThread(s, lock);
GetThread getThread = new GetThread(s, lock);
//使用Thread类创建线程对象,将自定义对象作为参数传递
Thread proThread = new Thread(setThread);
Thread cusThread = new Thread(getThread);
proThread.setPriority(1);
cusThread.setPriority(10);
//启动线程
proThread.start();
cusThread.start();
}
}
import java.util.concurrent.locks.Lock;
public class SetThread implements Runnable{
private Student s;
private Lock lock;
private int i = 0;
public SetThread(Student student){
s = student;
}
SetThread(Student student, Lock lock){
s = student;
this.lock = lock;
}
@Override
public void run() {
// Student s = new Student();
// s.name = "王宇";
// s.age = 18;
// while(true){
// synchronized(s){
// if (i % 2 == 0){
// s.name = "王宇";
// s.age = 18;
// }else{
// s.name = "张保桂";
// s.age = 19;
// }
// i++;
// }
// }
while (true){
lock.lock();
if(i % 2 == 0){
s.name = "王宇";
s.age = 18;
}else{
s.name = "张保桂";
s.age = 19;
}
i++;
lock.unlock();
}
}
}
import java.util.concurrent.locks.Lock;
public class GetThread implements Runnable {
private Student s;
private Lock lock;
public GetThread(Student student) {
s = student;
}
GetThread(Student student, Lock lock) {
s = student;
this.lock = lock;
}
@Override
public void run() {
// Student s = new Student();
// while (true){
// synchronized(s){
// System.out.println(s.name+"---"+s.age);
// }
// }
while (true) {
lock.lock();
System.out.println(s.name + "---" + s.age);
lock.unlock();
}
}
}
public class Student {
String name;
int age;
}
3、问题三:
问题3:我们虽然解决了线程安全的问题,但是观察结果后发现,同一条数据连续消费了很多次,而这样的问题叫做生产者
消费者的问题,使用Java提供的等待唤醒机制去解决。
如何添加等待唤醒机制呢?
所需要调用的方法在Object类中,Object类中有三个方法需要我们学习
void notify()
唤醒正在等待对象监视器的单个线程
void notifyAll()
唤醒正在等待对象监视器的所有线程
void wait()
导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
为什么这三个方法不定义在Thread类中呢?
这些方法要想调用,必须通过锁对象调用,因为如果连锁对象都不一样了,就没必要等待唤醒了,直接执行逻辑代码
而说到现在同步代码块的锁对象是任意对象,类型无法确定,所以这些方法都定义在Object类中,引Java所有的类都有一个共同父类Object
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
//创建自定义类对象
SetThread setThread = new SetThread(s);
GetThread getThread = new GetThread(s);
//使用Thread类创建线程对象,将自定义对象作为参数传递
Thread proThread = new Thread(setThread);
Thread cusThread = new Thread(getThread);
//启动线程
proThread.start();
cusThread.start();
}
}
import java.util.concurrent.locks.Lock;
public class SetThread implements Runnable{
private Student s;
private Lock lock;
private int i = 0;
SetThread(Student student){
s = student;
}
SetThread(Student student,Lock lock){
s = student;
this.lock = lock;
}
@Override
public void run() {
// Student s = new Student();
s.name = "王宇";
s.age = 18;
while (true){
synchronized(s){
//判断学生对象有没有值
//flag初始的值是false,表示没有值,如果是true表示生产者已经生产了值
//有值对于生产者来说,等待消费者消费数据
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 = 19;
}
i++;
//生产者生产完数据后,将flag值改为true
//通知消费者消费数据
s.notify();
s.flag = true;
}
}
// while (true){
// lock.lock();
// //先看看数据有没有被消费
// if (i % 2 == 0){
// s.name = "王宇";
// s.age = 18;
// }else{
// s.name = "张保桂";
// s.age = 19;
// }
// //通知消费者来消费数据
// i++;
// lock.unlock();
// }
}
}
import java.util.concurrent.locks.Lock;
public class GetThread implements Runnable{
private Student s;
private Lock lock;
GetThread(Student student){
s = student;
}
GetThread(Student student,Lock lock){
s = student;
this.lock = lock;
}
@Override
public void run() {
// Student s = new Student();
while (true){
synchronized(s){
//判断学生对象的成员变量是否有值
//如果flag的值是true,表示可以进行取值,反之,等待生产者生产数据
if (!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name+"---"+s.age);
//消费者消费完数据后,通知生产者生产数据
s.notify();
s.flag = false;
}
}
// while (true){
// lock.lock();
// //先看看数据有没有存在
// System.out.println(s.name+"---"+s.age);
// //通知生产者去生产数据
// lock.unlock();
// }
}
}
public class Student {
String name;
int age;
boolean flag;
}
二、死锁
1、定义:
死锁:
指的是两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象
2、举例:
中国人和美国人吃饭的案例
假设中国人吃饭的时候,必须要有两根筷子才能吃饭
美国人吃饭的时候,必须要有一把刀和一把叉才能吃饭
正常情况下:
中国人:两根筷子
美国人:一把刀,一把叉
发生死锁的情况:
中国人:一根筷子,一把刀
美国人:一根筷子,一把叉
死锁的现象更容易出现在同步嵌套情况下,所以今后开发的时候,尽量避免同步嵌套
3、代码
public class DieLockDemo {
public static void main(String[] args) {
//创建两个线程对象
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
//启动线程
d1.start();
d2.start();
}
}
public class DieLock extends Thread{
private boolean flag;
public DieLock(boolean flag){
this.flag = flag;
}
/**
* 现象1:
* if lock1
* else lock2
*
* 现象2:
* else lock2
* if lock1
*/
@Override
public void run() {
if (flag){
synchronized(MyLock.lock1){
System.out.println("if lock1");
synchronized(MyLock.lock2){
System.out.println("if lock2");
}
}
}else{
synchronized(MyLock.lock2){
System.out.println("else lock2");
synchronized(MyLock.lock1){
System.out.println("else lock1");
}
}
}
}
}
三、线程同步
1、同步安全解法一:synchronized关键字
1、线程同步的好处
解决了多线程的安全问题
2、线程同步的弊端
加了一个同代码块后,就相当于加了一把锁,每次进入同步代码块的时候都会去判断一下
无形之中,降低了我们执行效率
3、代码
public class SellTicketDemo1 {
public static void main(String[] args) {
TicketWindow1 ticketWindow1 = new TicketWindow1();
//使用Thread类创建多个线程对象
Thread window1 = new Thread(ticketWindow1);
Thread window2 = new Thread(ticketWindow1);
Thread window3 = new Thread(ticketWindow1);
//给线程起名字
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
//启动线程
window1.start();
window2.start();
window3.start();
}
}
public class TicketWindow1 implements Runnable{
private int tickets = 100;
private Object object = new Object();
@Override
public void run() {
while(true){
synchronized(object){
if (tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
}
}
2、synchronized改进
1、同步代码块上的锁对象是什么呢?
任意对象,但是需要注意的是,多个线程之间的锁对象要一样
2、同步方法呢?
将synchronized关键字放在方法的定义上
同步方法的锁对象是this
3、静态的同步代码块的锁对象是谁呢?
class文件对象,是线程类的字节码文件对象,其他的字节码文件对象不行
public class SellTicketDemo2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow1 = new TicketWindow2();
//使用Thread类创建多个线程对象
Thread window1 = new Thread(ticketWindow1);
Thread window2 = new Thread(ticketWindow1);
Thread window3 = new Thread(ticketWindow1);
//给线程起名字
window1.setName("窗口1");
window2.setName("窗口1");
window3.setName("窗口1");
//启动线程
window1.start();
window2.start();
window3.start();
// Hashtable<String,String> stringStringHashtable = new Hashtable<>();
}
}
public class TicketWindow2 implements Runnable{
// private int tickets = 100;
private static int tickets = 100;
private Object object = new Object();
private Demo demo = new Demo();
int i = 0;
@Override
public void run() {
// while(true){
// synchronized(demo){
// if(tickets>0){
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
// }
// }
// }
while(true){
if(i % 2 == 0){
synchronized(TicketWindow2.class){
if (tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}else{
sellTickets();
}
i++;
}
}
//同步方法
// public synchronized void sellTickets(){
// if (tickets>0){
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
// }
// }
//静态同步方法
public synchronized static void sellTickets(){
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
class Demo{
}
3、解决同步安全问题第二种解法:加lock锁
在此之前我们解决同步线程安全的时候,都是使用synchronized关键字,通过一顿分析后,将需要被包裹的代码给包起来,但是呢我们并没有
实际看到在哪里上了锁,又或是在哪里释放了锁,让其他线程获取到锁对象,执行线程。
Lock(接口)
具体的子类:Class ReentrantLock
lock()加锁
unlock()释放锁
public class SellTicketDemo3 {
public static void main(String[] args) {
TicketWindow3 ticketWindow1 = new TicketWindow3();
//使用Thread类创建多个线程对象
Thread window1 = new Thread(ticketWindow1);
Thread window2 = new Thread(ticketWindow1);
Thread window3 = new Thread(ticketWindow1);
//给线程起名字
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
//启动线程
window1.start();
window2.start();
window3.start();
// Hashtable<String,String> stringStringHashtable = new Hashtable<>();
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketWindow3 implements Runnable{
private int tickets = 100;
private Object object = new Object();
//创建锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if (tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
lock.unlock();
}
}
}
4、线程安全的类
import java.util.*;
/*
回忆下到目前为止,我们学习哪些线程安全的类
*/
public class ThreadDemo {
public static void main(String[] args) {
//StringBuffer
StringBuffer sb = new StringBuffer();
//Vector
Vector<String> strings = new Vector<>();
//Hashtable
Hashtable<String,String> stringStringHashtable = new Hashtable<>();
//虽然Vector这个类是线程安全的,但是今后我们也不用
//Collections工具类将线程不安全的集合转成线程安全的
ArrayList<String> strings1 = new ArrayList<>();
List<String> strings2 = Collections.synchronizedList(strings1);
}
}
5、匿名内部类的形式创建线程对象
/*
匿名内部类的形式创建线程对象
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//1、继承Thread类,重写run方法,start()启动线程
new Thread("王宇"){
@Override
public void run() {
for(int i = 1;i<=200;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}.start();
//2、实现Runnable接口,实现run方法
new Thread(new Runnable(){
@Override
public void run() {
for (int i =1;i <= 200;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
},"小虎").start();
}
}
四、线程组
1、线程组
java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
public class ThreadGroupDemo {
public static void main(String[] args) {
//创建自定义对象
MyRunnable myRunnable = new MyRunnable();
//通过Thread类创建的多个线程对象
Thread t1 = new Thread(myRunnable, "王宇");
Thread t2 = new Thread(myRunnable, "张保桂");
//默认情况下,所有的线程都属于主线程组
//public final ThreadGroup geThreadGroup();
ThreadGroup tg1 =t1.getThreadGroup();
System.out.println(tg1);
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg2);
//如何获取线程组的名字
//public final String getName()返回此线程组的名称
String s1 = tg1.getName();
String s2 = tg2.getName();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1.equals(s2));
//需求:给线程分组
//Thread(ThreadGroup group,Runnable target,String name)
//分配一个新的Thread对象,使其具有target作为其运行对象,具有指定的name作为其名称,属于group引用的线程组
//创建一个新的线程组
//ThreadGroup(String name)
//构造一个新的线程组
ThreadGroup tg3 = new ThreadGroup("帅哥组");
//创建线程对象并分组
Thread t3 = new Thread(tg3, myRunnable, "李雨阳");
Thread t4 = new Thread(tg3, myRunnable, "小虎");
Thread t5 = new Thread(tg3, myRunnable, "刘志成");
//获取到目前位置所有线程的名字以及所属线程组的名字
System.out.println(t1.getName()+"属于线程组:"+t1.getThreadGroup().getName());
System.out.println(t2.getName()+"属于线程组:"+t2.getThreadGroup().getName());
System.out.println(t3.getName()+"属于线程组:"+t3.getThreadGroup().getName());
System.out.println(t4.getName()+"属于线程组:"+t4.getThreadGroup().getName());
System.out.println(t5.getName()+"属于线程组:"+t5.getThreadGroup().getName());
//java允许程序直接对线程组进行控制
//需求:将一个组内的所有线程设置为守护线程
//直接通过组名进行设置,组里面的线程都是属于守护线程
tg3.setDaemon(true);
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0;i<=200;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
五、线程池
1、线程池
程序启动一个新的线程成本是比较高的,因为它涉及到要与操作系统进行交互
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑线程池
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
在JDk5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
2、如何使用代码实现线程池呢?
1、创建线程池对象,Executors工厂类下有很多获取线程池的静态方法
newFixedThreadPool是其中的一种线程池
public static ExecutorService newFixedThreadPool(int nThreads)
2、如何在线程池中放线程呢?(可以存放哪些线程呢?)
3、在线程池中的线程如何去运行呢?
4、我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//Future<?> submit(Runnable task)
//提交一个可运行的任务执行,并返回一个表示该任务的未来
//提交即运行
MyRunnable myRunnable = new MyRunnable();
pool.submit(myRunnable);//底层封装成了一个线程对象并启动执行
pool.submit(myRunnable);
//提交的线程数超过线程池的数量也会执行,只不过是当有空闲线程位置的时候再去执行
pool.submit(myRunnable);
//我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
//void shutdown()
//启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务
pool.shutdown();
//RejectedExecutionException
//线程池已经被关闭了,不能再继续提交任务
pool.submit(myRunnable);
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0;i<=200;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
六、实现多线程的第三种方式
自定义类实现Callable接口,实现call方法,该线程的启动必须与线程池结合,单独无法创建线程对象启动
<T> Future<T> submit(Callable<T> task)
提交值返回任务可以执行,并返回代表任务待处理结果的Future
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//创建自定义Callable对象
MyCallable myCallable = new MyCallable();
//提交到线程池中执行
pool.submit(myCallable);
//手动停止线程池
pool.shutdown();
}
}
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
Object o = new Object();
for(int i = 1;i <= 200;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
return o;
}
}
七、定时器
1、Demo1
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行
在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
如何实现创建定时器呢?
Java提供了一个类给我们使用实现定时器:Timer
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
//创建定时器对象
//Timer
//创建一个新的计时器
Timer timer = new Timer();
//调度任务执行
//void schedule(TimerTask task,long delay)
//在指定的延迟之后安排指定的任务执行
timer.schedule(new MyTask(timer),3000);
// timer.cancel();
// timer.schedule(new MyTask(timer),3000);//IllegalStateException
}
}
class MyTask extends TimerTask {
private Timer timer;
public MyTask(Timer timer){
this.timer = timer;
}
@Override
public void run() {
System.out.println("beng!!!!!爆炸了");
timer.cancel();
}
}
2、Demo2
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo2 {
public static void main(String[] args) {
//创建定时器对象
Timer timer = new Timer();
//void schedule(TimerTask task,long delay,long period)
//在指定的延迟之后开始,重新执行,固定延迟执行的指定任务
//3秒后执行任务,并且之后每隔两秒执行一次
timer.schedule(new MyTask2(),3000,2000);
}
}
class MyTask2 extends TimerTask {
@Override
public void run() {
try {
FileReader fr = new FileReader("ss.txt");
BufferedReader br = new BufferedReader(fr);
String s = br.readLine();
System.out.println(s);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}